diff --git a/.claude/settings.json b/.claude/settings.json index 187232f0..3bbb2db4 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -5,7 +5,7 @@ "hooks": [ { "type": "command", - "command": "bun run format" + "command": "bunx prettier@3.5.3 --no-config --write ." } ], "matcher": "Edit|Write|MultiEdit" diff --git a/.prettierignore b/.prettierignore index d62057c2..32807f57 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,4 @@ # Test fixtures should not be formatted to preserve exact output matching -test/fixtures/ \ No newline at end of file +test/fixtures/ +# Snapshot of PR-authored config kept for review; do not reformat +.claude-pr/ diff --git a/docs/security.md b/docs/security.md index 47327fa0..efd7c38a 100644 --- a/docs/security.md +++ b/docs/security.md @@ -51,6 +51,14 @@ For `workflow_run` events, the action checks the repository access of the actor This is general guidance for these event types — see [GitHub's documentation](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/). +### Which files come from the base branch on pull requests + +When the action runs against a pull request, it restores a fixed list of Claude configuration paths from the PR base branch before starting Claude: `.claude/`, `.mcp.json`, `.claude.json`, `.gitmodules`, `.ripgreprc`, `CLAUDE.md`, `CLAUDE.local.md`, and `.husky/`. Paths in that list that do not exist on the base branch are removed, and the PR-authored versions are kept under `.claude-pr/` for reference only. + +Everything else in the working tree — including `package.json`, lockfiles, `Makefile`, `node_modules/`, and formatter/linter config files — stays at the PR head. If a hook, `apiKeyHelper`, or `statusLine` command in your base-branch `.claude/settings.json` runs a package-manager script (`bun run …`, `npm run …`, `yarn …`, `pnpm run …`), a `make` target, a repo-relative script, or a tool that loads executable project config, that command resolves through files the pull request supplies. Keep such commands self-contained: invoke the tool directly with a pinned version and pass its configuration on the command line (for example `bunx prettier@3.5.3 --no-config --write .` rather than `bun run format`). + +Note that the runtime executing the tool also reads project config. `bunx ` runs the tool's script under `node` when `node` is on `PATH` (as it is on GitHub-hosted runners); when only Bun is available, Bun executes the script itself and reads `bunfig.toml` from the checkout — including `preload` entries — which comes from the PR head. On such runners, make sure `node` is on `PATH` for the hook, and treat `bunfig.toml` and `.npmrc` in the checkout as PR-controlled runtime config. + ### `claude-code-action` vs `claude-code-base-action` `claude-code-base-action` is a lower-level building block that installs and runs Claude Code with the inputs you provide. It does not perform actor permission checks or restore project configuration from the base ref. If you need those behaviors, use this action (`claude-code-action`). See the [base-action README](../base-action/README.md#trust-model) for details. diff --git a/src/github/operations/restore-config.ts b/src/github/operations/restore-config.ts index 92ab2be8..bcacc5bb 100644 --- a/src/github/operations/restore-config.ts +++ b/src/github/operations/restore-config.ts @@ -86,6 +86,19 @@ function ensureClaudePrExcludedFromGit(): void { * commits with `git add -A`, the revert will be included in that commit. This * is a narrow UX tradeoff for closing the RCE surface. * + * Only the paths listed in SENSITIVE_PATHS come from the base branch; the rest + * of the working tree stays at the PR head. A base-branch hook or setting that + * calls out through files a PR can change — package-manager scripts + * (`bun run`, `npm run`, `yarn`, `pnpm run`), Makefile or task-runner targets, + * repo-relative script paths, or tools that load executable project config — + * therefore runs whatever the PR head provides. Keep restored hooks + * self-contained: invoke the tool binary directly, pin its version, and pass + * config on the command line rather than reading it from the checkout. This + * extends to the runtime itself: `bunx ` runs the tool under `node` when + * `node` is on PATH, but on a Bun-only runner Bun executes the script and reads + * `bunfig.toml` (e.g. `preload`) from the checkout, so `bunfig.toml` and + * `.npmrc` there are PR-controlled runtime config too. + * * @param baseBranch - PR base branch name. Must be pre-validated (branch.ts * calls validateBranchName on it before returning). */