Ensures later steps resolve standard tools like git and tar from /usr/bin
regardless of what setup actions added earlier in the job. Also strengthens
the PAT guidance in security.md.
🏠 Remote-Dev: homespace
Copies the bun binary into $GITHUB_ACTION_PATH/bin before the claude step
runs and uses that copy in the two post-steps that invoke bun. Falls back
to PATH-resolved bun when allowed_non_write_users is empty.
🏠 Remote-Dev: homespace
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>
These three env vars are read directly from process.env by the Claude CLI
subprocess to configure MCP server behavior. Users setting them in their
workflow had no reliable way to make them reach the CLI:
- Job-level env: shadowed by the step's explicit env: block
- Step-level env: on the calling workflow step is not inherited by
composite action steps
- GITHUB_ENV from a prior step: same shadowing problem
- settings input: writes to ~/.claude/settings.json, not process.env
The fix is to add explicit ${{ env.VAR }} passthrough lines for all three
vars, matching the existing pattern already used for OTEL_*, AWS_*, and
Vertex configuration (lines 271-317). No TypeScript changes are needed;
the forwarding chain in parse-sdk-options.ts is already correct.
Fixes#1152
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
When a PR modifies files under .claude/, the security restore in
restoreConfigFromBase() overwrites them with the base branch version —
correct for execution safety, but it means review agents never see what
the PR actually changes.
Before deleting the PR-controlled .claude/ tree, copy it to .claude-pr/.
Review agents can read .claude-pr/ to inspect the PR's hooks, MCP
configs, settings, and CLAUDE.md without those files ever being executed.
The snapshot is taken before the security delete so it captures the full
PR-authored version.
Fixes#1134.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
shell-quote treats # as a shell comment character, swallowing all
subsequent content including flags on new lines. Strip comment lines
(lines starting with #) before passing input to shell-quote.
Fixes#802
Co-authored-by: VoidChecksum <Admin@CyberNord>
When a PR head contains `.gitmodules`, git's default
`fetch.recurseSubmodules=on-demand` config causes `git fetch` to attempt
submodule object fetches. In CI (no credentials), this blocks indefinitely
waiting for auth — producing ~4-hour hangs reported in #1088.
Two changes, both defence-in-depth:
1. Delete SENSITIVE_PATHS *before* fetching. The attacker-controlled
`.gitmodules` is absent during the network operation, so git never
sees a submodule config to follow regardless of git settings.
2. Pass `--no-recurse-submodules` to the fetch. Suppresses submodule
fetching explicitly, independent of any git config on the runner.
The original order (fetch-then-delete) was a brief window where
`.gitmodules` from the PR head could influence the fetch. Reordering
also tightens the security property: if `git checkout` below fails, the
attacker-controlled file is already gone rather than present during fetch.
Fixes#1088.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
`validateBranchName` used a strict whitelist that excluded `#`,
causing the action to fail on PRs from branches like `put-back-arm64-#2`
with "Invalid branch name" — even though the branch already exists in
git and `#` is permitted by git-check-ref-format.
The validation was designed to prevent command injection. However, every
git call in the action uses `execFileSync`, which bypasses the shell
entirely and passes arguments directly to the kernel's execve. There is
no shell to interpret `#` as a metacharacter, so the strict whitelist was
over-blocking valid names with no security benefit.
Add `#` to the whitelist pattern, and update the JSDoc and error message
to reflect the allowed character set.
Fixes#1137.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
bun install --production strips execute bits from vendored binaries
(bun bug). The Claude Agent SDK ships rg binaries in:
node_modules/@anthropic-ai/claude-agent-sdk/vendor/ripgrep/
{x64,arm64}-{linux,darwin}/rg
{x64,arm64}-win32/rg.exe
After bun --production, all of these lose +x, causing EACCES when the
SDK tries to spawn ripgrep. The fix is a targeted find(1) that restores
+x on the rg binaries immediately after bun install.
Design notes:
- -type f excludes symlinks (symlink attack safety, no || true needed)
- -name "rg" naturally excludes rg.exe on Windows (find returns nothing,
chmod never called — safe and correct on all platforms)
- .node audio-capture files use dlopen, not exec — no +x needed there
- Fails loudly if the binary path is missing (no || true) so a SDK
packaging change is immediately visible rather than silently broken
Fixes#1140
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add shouldRetry predicate to RetryOptions so callers can abort retries
for errors that will never succeed (e.g. 401 WorkflowValidationSkipError).
Previously, retryWithBackoff retried all errors blindly, wasting ~35s on
deterministic failures like workflow validation 401s.
Fixes#1081
Co-authored-by: Claude <noreply@anthropic.com>
When id-token: write permission is enabled, ACTIONS_ID_TOKEN_REQUEST_URL
and ACTIONS_ID_TOKEN_REQUEST_TOKEN are passed to the Claude session via
the process.env spread in parseSdkOptions(). This allows Claude to mint
new OIDC tokens, which is an unintended capability.
This commit deletes these two variables from the env object before passing
it to the Claude SDK. The OIDC flow in token.ts reads directly from
process.env and runs before parseSdkOptions(), so it is unaffected.
Fixes#1010
The reviewData variable is typed as `{ nodes: GitHubReview[] } | null`,
but the fallback value was `[]` (a plain array). When
`pullRequest.reviews` is null/undefined, `reviewData` becomes `[]`,
causing `reviewData.nodes` to return `undefined` instead of `[]`.
This leads to silent failures in downstream code that iterates over
`reviewData.nodes`, such as `filterReviewsToTriggerTime` and
`filterCommentsByActor`.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* docs: document include_comments_by_actor and exclude_comments_by_actor inputs
These inputs were added in #812 but never documented in usage.md or
security.md. This adds them to the inputs table in usage.md and
references comment filtering as a prompt injection mitigation in
security.md.
Fixes#972
* docs: clarify wildcard support is limited to *[bot] pattern
Address review feedback: "Supports wildcards" was misleading since
only the *[bot] pattern is supported, not general glob matching.
Add a check for non-empty github_token output before attempting to
revoke the app token in the cleanup step. When the prepare phase fails
(e.g., unsupported event type with track_progress), no token is
acquired, causing the cleanup curl to send an empty Bearer token
and produce a confusing "Bad credentials" 401 error.
Fixes#858
Co-authored-by: Dave-London <hello@os4us.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Bun's execFileSync without an explicit env option spawns with the process
startup environment, dropping runtime process.env mutations. The credential
helper reads GH_TOKEN which is set at runtime, so git fetch in the
restore-config path failed with empty password.
Fixes#1139🏠 Remote-Dev: homespace
* 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.
* Auto-set CLAUDE_CODE_SUBPROCESS_ENV_SCRUB when allowed_non_write_users is configured
Sets the env var automatically whenever allowed_non_write_users is
non-empty, so downstream workflows don't need to add it manually.
Updates the input description and docs/security.md to note the behavior.
🏠 Remote-Dev: homespace
* Fall back to inherited env when allowed_non_write_users is unset
🏠 Remote-Dev: homespace
* Let workflow/job env override the auto-set scrub flag
Env var takes priority so users can opt in/out via CLAUDE_CODE_SUBPROCESS_ENV_SCRUB
at job or workflow level independently of allowed_non_write_users.
🏠 Remote-Dev: homespace
* 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).