dirname() preserves embedded newlines, so a value like
`/usr/bin/claude\n/attacker/path` writes two lines to GITHUB_PATH,
injecting an attacker-controlled directory into PATH for all subsequent
workflow steps.
Validate the input immediately after reading it and throw if it
contains any control characters (0x00-0x1f, 0x7f). This is fail-closed
rather than silent stripping — a path with control characters is always
misconfigured or malicious.
Fixes#1160
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: fall back to repo default_branch instead of hardcoded "main"
When no explicit base_branch input is provided, the action previously
fell back to a hardcoded "main", which fails on repositories whose
default branch is named differently (e.g. "master", "develop").
This reads repository.default_branch from the GitHub event payload
(populated once in parseGitHubContext) and uses it as the fallback in
all three callsites: agent/index.ts, run.ts, and update-comment-link.ts.
Explicit env/input precedence is preserved; "main" remains only as a
last-resort defensive fallback if the payload somehow lacks the field.
* test: drop unused BASE_BRANCH env handling from default_branch test
agent/index.ts no longer reads process.env.BASE_BRANCH directly (it now
goes through context.inputs.baseBranch which is set on the mock context),
so saving/clearing/restoring that env var in the regression test is dead
code.
* Revert "chore: bump Claude Code to 2.1.89 and Agent SDK to 0.2.89"
This reverts commit bee87b3258c251f9279e5371b0cc3660f37f3f77.
* Revert "chore: bump Claude Code to 2.1.88 and Agent SDK to 0.2.88"
This reverts commit 7225f045c6219dd201504adc5534baf31024db31.
* Restore .claude/ and .mcp.json from PR base branch before CLI runs
The CLI's non-interactive mode trusts cwd: it reads .mcp.json and
.claude/settings{,.local}.json from the working directory and acts on
them before any tool-permission gating — executing hooks, setting env
vars (NODE_OPTIONS, LD_PRELOAD), running apiKeyHelper shell commands,
and auto-approving MCP servers. When this action checks out a PR head,
these files are attacker-controlled.
Rather than enumerate dangerous keys, replace the entire .claude/ tree
and .mcp.json with the versions from the PR base branch (which a
maintainer has reviewed). Paths absent on base are deleted. Uses local
git state, so no TOCTOU against the GitHub API.
* Read PR base ref from payload for config restore in agent mode
Agent mode's branchInfo.baseBranch defaults to "main" (or env/input
override) instead of the PR's actual target branch — it doesn't query
prData.baseRefName like tag mode does. This meant a PR targeting
develop would get .claude/ restored from main.
Fix by reading pull_request.base.ref directly from the webhook payload
for pull_request, pull_request_review, and pull_request_review_comment
events. For issue_comment on a PR (no base.ref in payload), fall back
to the mode-provided value — tag mode's value is correct (from GraphQL);
agent mode on issue_comment is an edge case that at worst restores from
the wrong trusted branch, which is still secure.
The payload value passes through validateBranchName for defense-in-depth
(GitHub enforces valid branch names server-side, but we validate anyway).
* Extend restored paths to .gitmodules, .ripgreprc, .claude.json
.gitmodules defines submodule URLs and paths; path-confusion attacks
against git submodule operations can write into .git/hooks. .ripgreprc
can set --pre (arbitrary command on each file) if RIPGREP_CONFIG_PATH
points at it. .claude.json is cheap defense-in-depth.
Documented why .git/ is excluded (not trackable in commits, and
restoring it would undo the PR checkout), along with .gitconfig
(git never reads it from cwd) and shell rc files (sourced from $HOME,
not cwd — checkout cannot reach $HOME).