Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ curl -fsSL https://raw.githubusercontent.com/Qiscus-Integration/integration-skil
irm https://raw.githubusercontent.com/Qiscus-Integration/integration-skills/main/install.ps1 | iex
```

> **Security note:** For maximum security, clone the repo first and run the installer locally instead of piping from curl. See [SECURITY.md](SECURITY.md) for details.

The installer will ask:

1. Which AI tool to install for — Claude Code, Codex, or both
Expand Down
35 changes: 35 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Security Policy

## Reporting Vulnerabilities

If you discover a security vulnerability, please report it responsibly by emailing the maintainers directly instead of opening a public issue.

## Security Considerations

### Installer Scripts

The `install.sh` and `install.ps1` scripts download skill files from GitHub. Users should be aware of the following:

- **Verify the source** before running `curl | bash`. Always inspect the script first or clone the repo and run locally.
- Skill names and file paths from GitHub API responses are validated against path traversal attacks (only alphanumeric, hyphens, and underscores are allowed in skill names).
- Downloaded files are not cryptographically verified. For maximum security, clone the repository and install from the local copy.

### Recommended Installation (Most Secure)

```bash
# Clone and inspect first, then install locally
git clone https://github.com/Qiscus-Integration/integration-skills.git
cd integration-skills
# Review the code, then:
./install.sh
```

### For Contributors

Before submitting a PR, verify:

- [ ] No `.env` files, API keys, tokens, or credentials are included
- [ ] No hardcoded internal URLs, IPs, or hostnames
- [ ] File paths in scripts are sanitized against traversal (`..`, absolute paths)
- [ ] No use of `eval`, `exec`, or unsanitized string interpolation in scripts
- [ ] Template files do not contain real configuration values
4 changes: 2 additions & 2 deletions docs/project-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Depending on the target agent setup, this skill can create:

- `AGENTS.md`
- `CLAUDE.md`
- Shared memory folders such as `.ai-memory/`, `.claude/memory/`, or `.codex/memory/`
- Shared memory folders such as `.project-memory/`, `.claude/memory/`, or `.codex/memory/`
- Task handoff files such as `active.md` and `done.md`

---
Expand All @@ -35,7 +35,7 @@ Depending on the target agent setup, this skill can create:
| ----- | ----------- | --------------- |
| Claude Code only | `CLAUDE.md` | `.claude/memory/` and `.claude/tasks/` |
| Codex only | `AGENTS.md` | `.codex/memory/` and `.codex/tasks/` |
| Mixed team | `CLAUDE.md` + `AGENTS.md` | Shared `.ai-memory/` |
| Mixed team | `CLAUDE.md` + `AGENTS.md` | Shared `.project-memory/` |

---

Expand Down
25 changes: 25 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ $Branch = "main"
$RawBase = "https://raw.githubusercontent.com/$Owner/$Repo/$Branch"
$ApiBase = "https://api.github.com/repos/$Owner/$Repo/contents/skills?ref=$Branch"

# ─── Security: Path sanitization ─────────────────────────────────────────────

function Test-SafeName {
param([string]$Name, [string]$Label = "name")
if ($Name -notmatch '^[a-zA-Z0-9_-]+$') {
Write-ErrorMessage "Invalid ${Label}: '$Name' — contains unsafe characters. Skipping."
return $false
}
return $true
}

function Test-SafePath {
param([string]$Path)
if ($Path -match '\.\.' -or $Path.StartsWith('/') -or $Path.StartsWith('\')) {
Write-ErrorMessage "Unsafe path detected: '$Path' — skipping."
return $false
}
return $true
}

function Write-Success($Message) {
Write-Host "[OK] $Message" -ForegroundColor Green
}
Expand Down Expand Up @@ -244,6 +264,7 @@ function Get-SkillNames {
$response = Invoke-RestMethod -Uri $ApiBase
return $response |
Where-Object { $_.type -eq "dir" -and $_.name -ne "_template" } |
Where-Object { Test-SafeName -Name $_.name -Label "skill name" } |
Sort-Object name |
Select-Object -ExpandProperty name
}
Expand All @@ -256,6 +277,10 @@ function Install-SkillFile {
[string]$ScriptRoot
)

# Validate inputs against path traversal
if (-not (Test-SafeName -Name $Skill -Label "skill name")) { return $false }
if (-not (Test-SafePath -Path $RelPath)) { return $false }

$dest = Join-Path $DestinationRoot "$Skill\$RelPath"
$destDir = Split-Path $dest -Parent
if (-not (Test-Path $destDir)) { New-Item $destDir -ItemType Directory -Force | Out-Null }
Expand Down
36 changes: 34 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,29 @@ RAW_BASE="https://raw.githubusercontent.com/$OWNER/$REPO/$BRANCH"
API_BASE="https://api.github.com/repos/$OWNER/$REPO/contents/skills?ref=$BRANCH"
TREE_API="https://api.github.com/repos/$OWNER/$REPO/git/trees/$BRANCH?recursive=1"

# ─── Security: Path sanitization ─────────────────────────────────────────────

# Validate that a name contains only safe characters (alphanumeric, hyphen, underscore)
# Prevents path traversal via malicious skill names from GitHub API responses
validate_safe_name() {
local name="$1" label="${2:-name}"
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
print_error "Invalid $label: '$name' — contains unsafe characters. Skipping."
return 1
fi
return 0
}

# Validate that a relative file path contains no traversal sequences
validate_safe_path() {
local path="$1"
if [[ "$path" == *".."* ]] || [[ "$path" == /* ]]; then
print_error "Unsafe path detected: '$path' — skipping."
return 1
fi
return 0
}

# ─── Colors ───────────────────────────────────────────────────────────────────

RED='\033[0;31m'
Expand Down Expand Up @@ -228,6 +251,7 @@ if [[ -d "$SCRIPT_DIR/skills" ]]; then
while IFS= read -r -d '' dir; do
name="$(basename "$dir")"
[[ "$name" == "_template" ]] && continue
validate_safe_name "$name" "skill name" || continue
available_skills+=("$name")
done < <(find "$SCRIPT_DIR/skills" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
print_info "Using local skills."
Expand All @@ -239,7 +263,9 @@ else
if [[ -n "$api_response" ]]; then
while IFS= read -r name; do
[[ "$name" == "_template" ]] && continue
[[ -n "$name" ]] && available_skills+=("$name")
[[ -z "$name" ]] && continue
validate_safe_name "$name" "skill name" || continue
available_skills+=("$name")
done < <(
printf '%s\n' "$api_response" \
| awk 'BEGIN { RS="}," } /"type": "dir"/ { if (match($0, /"name": "[^"]+"/)) { value = substr($0, RSTART + 9, RLENGTH - 10); print value } }' \
Expand Down Expand Up @@ -276,8 +302,10 @@ echo ""

_copy_local_skill_tree() {
local skill="$1" dest_dir="$2"
local source_dir="$SCRIPT_DIR/skills/$skill"

validate_safe_name "$skill" "skill name" || return 1

local source_dir="$SCRIPT_DIR/skills/$skill"
[[ -d "$source_dir" ]] || return 1

mkdir -p "$dest_dir"
Expand Down Expand Up @@ -309,10 +337,14 @@ _download_remote_skill_tree() {
local rel_path dest
local downloaded_any=false

validate_safe_name "$skill" "skill name" || return 1
mkdir -p "$dest_dir/$skill"

while IFS= read -r rel_path; do
[[ -n "$rel_path" ]] || continue
# Reject paths with traversal sequences or absolute paths
validate_safe_path "$rel_path" || continue

dest="$dest_dir/$skill/$rel_path"
mkdir -p "$(dirname "$dest")"

Expand Down
Loading