Keeps the input default empty so claude_args --setting-sources is not
shadowed; the wrapper applies user,project,local as the runtime fallback,
base-action applies user.
🏠 Remote-Dev: homespace
Project and local settings additively merge their permissions with whatever
allowed_tools a workflow specifies. A workflow author writing a restrictive
allowlist reasonably expects it to be the complete allow-set, but
.claude/settings.json can silently expand it.
Changes:
- Add setting_sources as a first-class input to both actions (previously
only reachable via --setting-sources in claude_args)
- base-action now defaults to settingSources: ['user'] — workflows that want
project/local settings must opt in explicitly
- Main action defaults to 'user,project,local' since .claude/ is restored
from the PR base branch before execution, so project settings are
maintainer-trusted in that context
- Precedence: setting_sources input > --setting-sources in claude_args > default
Breaking change for base-action: workflows relying on .claude/settings.json
being loaded automatically need to add setting_sources: 'user,project,local'.
🏠 Remote-Dev: homespace
Agent SDK 0.2.113 dropped vendor/ripgrep and now ships native binaries
via per-platform optionalDependencies. Two breakages:
- action.yml chmod'd vendor/ripgrep which no longer exists, failing the
Install Dependencies step with find exit 1.
- The SDK auto-resolves its bundled binary by trying the -musl platform
package before the glibc one. bun install does not respect the
package.json libc field and installs both on glibc Linux, so the SDK
picks the musl binary and spawn fails with ENOENT.
Remove the obsolete ripgrep chmod. Make installClaudeCode() return the
install.sh binary path and pass it explicitly as
pathToClaudeCodeExecutable so the SDK skips auto-resolution entirely.
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).