diff --git a/.agents/skills/gstack-benchmark/SKILL.md b/.agents/skills/gstack-benchmark/SKILL.md index 4557cfda..aa8551a4 100644 --- a/.agents/skills/gstack-benchmark/SKILL.md +++ b/.agents/skills/gstack-benchmark/SKILL.md @@ -290,7 +290,7 @@ When the user types `/benchmark`, run this skill. ### Phase 1: Setup ```bash -eval $(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") +source <(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") mkdir -p .gstack/benchmark-reports mkdir -p .gstack/benchmark-reports/baselines ``` diff --git a/.agents/skills/gstack-canary/SKILL.md b/.agents/skills/gstack-canary/SKILL.md index 416f8e5d..f1bb4ee5 100644 --- a/.agents/skills/gstack-canary/SKILL.md +++ b/.agents/skills/gstack-canary/SKILL.md @@ -308,7 +308,7 @@ When the user types `/canary`, run this skill. ### Phase 1: Setup ```bash -eval $(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") +source <(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") mkdir -p .gstack/canary-reports mkdir -p .gstack/canary-reports/baselines mkdir -p .gstack/canary-reports/screenshots @@ -458,7 +458,7 @@ Save report to `.gstack/canary-reports/{date}-canary.md` and `.gstack/canary-rep Log the result for the review dashboard: ```bash -eval $(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null) +source <(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null) mkdir -p ~/.gstack/projects/$SLUG ``` diff --git a/.agents/skills/gstack-land-and-deploy/SKILL.md b/.agents/skills/gstack-land-and-deploy/SKILL.md index d24d1191..e3106973 100644 --- a/.agents/skills/gstack-land-and-deploy/SKILL.md +++ b/.agents/skills/gstack-land-and-deploy/SKILL.md @@ -840,7 +840,7 @@ Save report to `.gstack/deploy-reports/{date}-pr{number}-deploy.md`. Log to the review dashboard: ```bash -eval $(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null) +source <(~/.codex/skills/gstack/bin/gstack-slug 2>/dev/null) mkdir -p ~/.gstack/projects/$SLUG ``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 95f8e422..bfd59433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -217,7 +217,7 @@ - **Browse no longer navigates to dangerous URLs.** `goto`, `diff`, and `newtab` now block `file://`, `javascript:`, `data:` schemes and cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`). Localhost and private IPs are still allowed for local QA testing. (Closes #17) - **Setup script tells you what's missing.** Running `./setup` without `bun` installed now shows a clear error with install instructions instead of a cryptic "command not found." (Closes #147) - **`/debug` renamed to `/investigate`.** Claude Code has a built-in `/debug` command that shadowed the gstack skill. The systematic root-cause debugging workflow now lives at `/investigate`. (Closes #190) -- **Shell injection surface removed.** All skill templates now use `source <(gstack-slug)` instead of `eval $(gstack-slug)`. Same behavior, no `eval`. (Closes #133) +- **Shell injection surface reduced.** gstack-slug output is now sanitized to `[a-zA-Z0-9._-]` only, making both `eval` and `source` callers safe. (Closes #133) - **25 new security tests.** URL validation (16 tests) and path traversal validation (14 tests) now have dedicated unit test suites covering scheme blocking, metadata IP blocking, directory escapes, and prefix collision edge cases. ## [0.8.2] - 2026-03-19 diff --git a/benchmark/SKILL.md b/benchmark/SKILL.md index c6845b2c..49e623f6 100644 --- a/benchmark/SKILL.md +++ b/benchmark/SKILL.md @@ -297,7 +297,7 @@ When the user types `/benchmark`, run this skill. ### Phase 1: Setup ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") mkdir -p .gstack/benchmark-reports mkdir -p .gstack/benchmark-reports/baselines ``` diff --git a/benchmark/SKILL.md.tmpl b/benchmark/SKILL.md.tmpl index 3d4efac8..d0c0ecbc 100644 --- a/benchmark/SKILL.md.tmpl +++ b/benchmark/SKILL.md.tmpl @@ -41,7 +41,7 @@ When the user types `/benchmark`, run this skill. ### Phase 1: Setup ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") mkdir -p .gstack/benchmark-reports mkdir -p .gstack/benchmark-reports/baselines ``` diff --git a/bin/gstack-review-log b/bin/gstack-review-log index ad29c172..816cfa46 100755 --- a/bin/gstack-review-log +++ b/bin/gstack-review-log @@ -3,7 +3,7 @@ # Usage: gstack-review-log '{"skill":"...","timestamp":"...","status":"..."}' set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -eval $("$SCRIPT_DIR/gstack-slug" 2>/dev/null) +source <("$SCRIPT_DIR/gstack-slug" 2>/dev/null) GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" mkdir -p "$GSTACK_HOME/projects/$SLUG" echo "$1" >> "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl" diff --git a/bin/gstack-review-read b/bin/gstack-review-read index 247c022f..578d7480 100755 --- a/bin/gstack-review-read +++ b/bin/gstack-review-read @@ -3,7 +3,7 @@ # Usage: gstack-review-read set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -eval $("$SCRIPT_DIR/gstack-slug" 2>/dev/null) +source <("$SCRIPT_DIR/gstack-slug" 2>/dev/null) GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" cat "$GSTACK_HOME/projects/$SLUG/$BRANCH-reviews.jsonl" 2>/dev/null || echo "NO_REVIEWS" echo "---CONFIG---" diff --git a/bin/gstack-slug b/bin/gstack-slug index 77824726..3ad30381 100755 --- a/bin/gstack-slug +++ b/bin/gstack-slug @@ -1,10 +1,10 @@ #!/usr/bin/env bash # gstack-slug — output project slug and sanitized branch name -# Usage: eval $(gstack-slug) → sets SLUG and BRANCH variables -# Or: gstack-slug → prints SLUG=... and BRANCH=... lines +# Usage: source <(gstack-slug) → sets SLUG and BRANCH variables +# Or: gstack-slug → prints SLUG=... and BRANCH=... lines # # Security: output is sanitized to [a-zA-Z0-9._-] only, preventing -# shell injection when consumed via eval $(gstack-slug). +# shell injection when consumed via source or eval. set -euo pipefail RAW_SLUG=$(git remote get-url origin 2>/dev/null | sed 's|.*[:/]\([^/]*/[^/]*\)\.git$|\1|;s|.*[:/]\([^/]*/[^/]*\)$|\1|' | tr '/' '-') RAW_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null | tr '/' '-') diff --git a/canary/SKILL.md b/canary/SKILL.md index f3f1c1ae..304c1427 100644 --- a/canary/SKILL.md +++ b/canary/SKILL.md @@ -315,7 +315,7 @@ When the user types `/canary`, run this skill. ### Phase 1: Setup ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") mkdir -p .gstack/canary-reports mkdir -p .gstack/canary-reports/baselines mkdir -p .gstack/canary-reports/screenshots @@ -465,7 +465,7 @@ Save report to `.gstack/canary-reports/{date}-canary.md` and `.gstack/canary-rep Log the result for the review dashboard: ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) mkdir -p ~/.gstack/projects/$SLUG ``` diff --git a/canary/SKILL.md.tmpl b/canary/SKILL.md.tmpl index 8c9089be..d0ddadfe 100644 --- a/canary/SKILL.md.tmpl +++ b/canary/SKILL.md.tmpl @@ -42,7 +42,7 @@ When the user types `/canary`, run this skill. ### Phase 1: Setup ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null || echo "SLUG=unknown") mkdir -p .gstack/canary-reports mkdir -p .gstack/canary-reports/baselines mkdir -p .gstack/canary-reports/screenshots @@ -192,7 +192,7 @@ Save report to `.gstack/canary-reports/{date}-canary.md` and `.gstack/canary-rep Log the result for the review dashboard: ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) mkdir -p ~/.gstack/projects/$SLUG ``` diff --git a/land-and-deploy/SKILL.md b/land-and-deploy/SKILL.md index 497fbc98..4a6369b6 100644 --- a/land-and-deploy/SKILL.md +++ b/land-and-deploy/SKILL.md @@ -847,7 +847,7 @@ Save report to `.gstack/deploy-reports/{date}-pr{number}-deploy.md`. Log to the review dashboard: ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) mkdir -p ~/.gstack/projects/$SLUG ``` diff --git a/land-and-deploy/SKILL.md.tmpl b/land-and-deploy/SKILL.md.tmpl index d1ddd7b7..0e84d859 100644 --- a/land-and-deploy/SKILL.md.tmpl +++ b/land-and-deploy/SKILL.md.tmpl @@ -542,7 +542,7 @@ Save report to `.gstack/deploy-reports/{date}-pr{number}-deploy.md`. Log to the review dashboard: ```bash -eval $(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) +source <(~/.claude/skills/gstack/bin/gstack-slug 2>/dev/null) mkdir -p ~/.gstack/projects/$SLUG ```