fix(code-review): resolve base helper from skill directory#11
Merged
fix(code-review): resolve base helper from skill directory#11
Conversation
Try ${CLAUDE_SKILL_DIR} first, then ${CLAUDE_PLUGIN_ROOT}/skills/ce-code-review,
before failing closed. Removes the implicit dependency on a single env var and
keeps the security stance: no bare relative-path fallback that could pick up a
scripts/resolve-base.sh from the reviewed repo's CWD.
Defense-in-depth for the harness-variable probe: even when CLAUDE_SKILL_DIR or CLAUDE_PLUGIN_ROOT is set, refuse to use a base that canonicalizes to or under the current working directory. Closes a residual channel where a hostile repo with direnv-style auto-loading could export CLAUDE_SKILL_DIR=$PWD/.evil and resurrect the same arbitrary-code-execution path the previous fix closed. Also fix a latent bug in the previous fail-closed message: the literal backticks in `base:<ref>` would have been command-substituted by bash. Use single-quoted printf instead. Add an execution-level contract test that extracts the probe snippet from SKILL.md and runs it across env-var combinations (both unset, each set alone, both set, skill dir inside cwd, empty-string skill dir, skill dir without script). The previous shape-matching test would have passed silently if probe order were swapped, the empty-string guard removed, or the bash invocation unquoted; this one would not. Tighten the prose around the threat: it was arbitrary code execution at review time, not just a corrupted merge-base.
…SKILL_DIR
AGENTS.md documents ${CLAUDE_SKILL_DIR} as the canonical primitive for
skill-bundled scripts and asserts it resolves correctly across both
`claude --plugin-dir` and standard marketplace-cached installs. The earlier
${CLAUDE_PLUGIN_ROOT} fallback was defensive belt-and-suspenders with no
concrete evidence of a real harness path where SKILL_DIR is missing but
PLUGIN_ROOT is exposed -- only hypothetical robustness.
Removing the second probe shrinks both the SKILL.md probe blocks (~13 lines
each -> ~9 lines) and the contract test surface, while preserving every
load-bearing security property: the in-CWD rejection still backstops a
hostile harness pointing SKILL_DIR inside the reviewed repo, and the
fail-closed posture still refuses any `git diff HEAD` fallback.
Contract test now exercises four scenarios end-to-end: unset, empty,
happy-path resolution despite a CWD decoy, and hostile harness pointing
inside CWD. All four assert no fall-through to the reviewed-repo script.
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ce-code-reviewis a skill, but its branch-mode and standalone-mode bash blocks invoked the merge-base helper asbash scripts/resolve-base.sh. The Bash tool runs from the user's project CWD — not the skill directory — so that path resolves against the reviewed repo, not the bundled skill. Two concrete failure modes:scripts/resolve-base.sh, bash exits non-zero, the existing||branch printsERROR: resolve-base.sh failed, and the skill aborts. Branch and standalone modes are effectively unusable on the common case — users have to fall back tobase:<ref>or PR mode.scripts/resolve-base.sh, the reviewer executes that repo-controlled script with no error and computes a plausible-looking but completely attacker-controlled merge-base. Low-likelihood, high-impact for a tool whose job is summarizing untrusted code.This PR resolves the helper from
${CLAUDE_SKILL_DIR}instead, with an in-CWD-rejection check and an explicit fail-closed branch when the variable is unavailable or hostile.Why this went unnoticed
/ce-code-review <NNN>resolves the base fromgh pr viewand never touchesresolve-base.sh, so most invocations don't hit the broken path.ERROR: resolve-base.sh failedlooks like "set up your repo" rather than "the skill is misrouting." Users likely worked around it withbase:<ref>and moved on.What changed
resolve-base.shfrom${CLAUDE_SKILL_DIR}/scripts/resolve-base.sh. AGENTS.md documents${CLAUDE_SKILL_DIR}as the canonical primitive for skill-bundled scripts and asserts it resolves correctly acrossclaude --plugin-dirand standard marketplace-cached installs.realpath'd and rejected if it resolves to the reviewed repo's CWD or any path inside it — so even a misconfigured or hostile harness that points${CLAUDE_SKILL_DIR}at a directory inside the reviewed repo cannot be used to execute a repo-controlled script.base:<ref>or use a harness that exposes the skill directory.ERROR:), the reviewer refuses to fall back togit diff HEAD, which would only show uncommitted changes and silently miss every committed change on the branch.scripts/resolve-base.shas a last-resort would re-introduce the original CWD-resolution bug and the script-shadowing security path, so the contract test now asserts that fallback is absent (expect(content).not.toMatch(/bash\s+scripts\/resolve-base\.sh/)).Cross-platform behavior
This skill is converted for non-Claude targets (Codex, Cursor, Gemini, etc.) and the
${CLAUDE_SKILL_DIR}variable remains literal in the converted output. On those platforms the probe finds no script and the skill fails closed with a clear instruction to passbase:<ref>— strictly safer than executing a CWD-relative script. PR mode (<NNN>) is unaffected and continues to resolve the base from PR metadata viagh.Empirical evidence
Reproduced in a throwaway repo with a decoy
scripts/resolve-base.shthat printsBASE:DECOYWASEXECUTEDand exits 0 — i.e., a worst-case reviewed repo carrying its own helper at the path the old code probed. Same CWD, same decoy, both invocations:The OLD path silently executed the reviewed repo's script and returned its forged base. The NEW path resolved through
${CLAUDE_SKILL_DIR}, ignored the decoy, and returned the realgit merge-basesha. The loud-fail case (no decoy present in the reviewed repo) producesbash: scripts/resolve-base.sh: No such file or directory(exit 127) under OLD and the documented fail-closed error under NEW with no harness variable set.Verification
bun test tests/review-skill-contract.test.ts— 26 pass, including the updatedfails closed when merge-base is unresolvedcontract (pins the new probe shape, asserts absence of the relative-path fallback) and a newresolve-base probe resolves CLAUDE_SKILL_DIR and rejects repo-controlled pathstest that exercises the probe end-to-end against synthetic harnesses (unset, empty, happy-path with CWD decoy present, and hostile harness pointing into the reviewed-repo CWD).bun test— full suite (1240 tests) passes.bun run release:validate— clean.Test plan
${CLAUDE_SKILL_DIR}set: helper resolves, diff is correct against merge-base.scripts/resolve-base.sh: decoy is not executed; bundled helper runs.${CLAUDE_SKILL_DIR}pointing inside the reviewed repo: rejected, fail-closed; decoy not executed.${CLAUDE_SKILL_DIR}exposed: skill prints the documented error and stops; nogit diff HEADfallback.<NNN>): unchanged — still resolves base from PR metadata.