Skip to content

Pipeline-mode reviews load agent config from the untrusted PR/MR checkout #1005

Description

@nicoa

Summary

In pipeline mode (roborev ci review), the review agent runs with its working directory set to a checkout of the pull/merge request under review. Claude Code loads project-scoped configuration from that directory, and two of those files can execute commands:

  • Hooks in .claude/settings.json / .claude/settings.local.json run shell commands. Under -p there is no trust prompt — the docs state that "trust verification is disabled when running non-interactively with the -p flag", and the documented workspace-trust gate for hooks covers only subagent frontmatter hooks, not settings-file hooks.
  • .mcp.json can launch stdio MCP servers.

roborev's read-only review allowlist (--allowedTools Read,Glob,Grep, internal/agent/claude.go:239) does not contain either, because a hook is not a tool call.

So a PR/MR author who can trigger a pipeline can get command execution in the review job, in a process that holds the forge token and the agent's provider credentials.

Affected paths

  • GitHub Actions — the workflow from roborev init gh-action uses on: pull_request + actions/checkout, so the working tree is the PR merge ref. Mitigating factor: GitHub does not expose repository secrets to pull_request runs from forks, so this is limited to same-repo branches (i.e. anyone with push access), not arbitrary contributors.
  • GitLab CI (roborev ci review in a merge_request_event pipeline, Add GitLab merge request support to roborev ci review #1004) — same shape, and GitLab does inject project variables into MR pipelines from any branch, so the exposure is wider by default.

Not affected: the daemon CI poller. It fetches the PR head ref but reviews from the diff range without checking it out, so the working tree stays on the (trusted) default branch.

Suggested fix

Have the Claude adapter neutralize untrusted project config when reviewing, rather than leaving it to each user's CI config. Claude Code documents three mechanisms:

  • --setting-sources user — omits project and local, dropping project settings.json and project hooks while keeping user-level config.
  • --strict-mcp-config — ignores MCP configuration outside --mcp-config.
  • --bare / CLAUDE_CODE_SIMPLE=1 — skips auto-discovery of hooks, skills, plugins, MCP servers, auto memory and CLAUDE.md wholesale. Note this also changes the default tool set and skips keychain/OAuth credential reads, so it is the blunt option.

The narrow combination (--setting-sources user --strict-mcp-config) looks like the right default for CI review runs: it removes the execution vectors while leaving CLAUDE.md, .claude/rules, and project skills available as review context — which is genuinely useful signal for a reviewer. There is a residual prompt-injection risk in those context files, but instructions steering a review are a different severity from arbitrary command execution.

Worth considering whether this should apply to all review-type jobs (the local post-commit path reviews your own commits, so the trust model differs) or only to CI/pipeline reviews.

Happy to send a PR for this if the approach sounds right — it would sit on top of #1004, which is where the GitLab side of pipeline mode lands.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions