Skip to content

Harden claude-pr-action.yml#584

Open
Micky774 wants to merge 2 commits into
devfrom
zain/claude-security
Open

Harden claude-pr-action.yml#584
Micky774 wants to merge 2 commits into
devfrom
zain/claude-security

Conversation

@Micky774
Copy link
Copy Markdown
Contributor

Description

Triaged from a third-party security review of the workflow. This commit addresses the must-fix issues presented in the review.

Fixes # (issue)

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

Please list the changes introduced in this PR:

  • Block fork PRs from non-collaborators before any PR code is staged. pull_request_target gives this job write-scoped GITHUB_TOKEN and the CLAUDE_CODE_OAUTH_TOKEN secret. Without this gate, a fork PR can hijack both via e.g. a .gitattributes textconv driver invoked by Claude's git diff calls. The new step runs first and exits 1 unless the head repo is the same as the base repo or the PR author has write/maintain/admin permission on the base.

  • Run the Claude warmup in mktemp -d instead of $GITHUB_WORKSPACE. Claude Code auto-discovers CLAUDE.md and .claude/ from CWD; with the PR tree checked out and CLAUDE_CODE_OAUTH_TOKEN in env, an attacker workspace CLAUDE.md could prompt-inject the warmup into echoing the token into workflow logs.

  • Pin third-party actions to commit SHAs:

    • actions/checkout@v4 -> @34e114876b0b... (v4.3.1)
    • actions/upload-artifact@v4 -> @ea165f8d65b6... (v4.6.2)
    • anthropics/claude-code-action@v1 -> @dde2242db6af... (v1.0.120)
  • Replace blocklist allowedTools with a strict allowlist for both the review and summary steps. The previous Bash(gh *) + denylist allowed e.g. gh api --method DELETE repos/... because the denylist only covered specific subcommands. New allowlist only matches read-shaped invocations (gh api repos/*, git diff *, etc.); summary additionally permits gh api --method PATCH repos/* so it can update prior walkthrough comments.

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Micky774 and others added 2 commits May 12, 2026 14:51
Triaged from a third-party security review of the workflow. This commit
addresses the must-fix bundle (M1+M2+M3 from that triage); deferred
items (show_full_output, install-script pinning, base-ref validation,
job timeouts) will land in a follow-up PR.

- Block fork PRs from non-collaborators before any PR code is staged.
  pull_request_target gives this job write-scoped GITHUB_TOKEN and the
  CLAUDE_CODE_OAUTH_TOKEN secret. Without this gate, a fork PR can
  hijack both via e.g. a .gitattributes textconv driver invoked by
  Claude's `git diff` calls. The new step runs first and exits 1 unless
  the head repo is the same as the base repo or the PR author has
  write/maintain/admin permission on the base.

- Run the Claude warmup in mktemp -d instead of $GITHUB_WORKSPACE.
  Claude Code auto-discovers CLAUDE.md and .claude/ from CWD; with the
  PR tree checked out and CLAUDE_CODE_OAUTH_TOKEN in env, an attacker
  workspace CLAUDE.md could prompt-inject the warmup into echoing the
  token into workflow logs.

- Pin third-party actions to commit SHAs:
  * actions/checkout@v4              -> @34e114876b0b... (v4.3.1)
  * actions/upload-artifact@v4       -> @ea165f8d65b6... (v4.6.2)
  * anthropics/claude-code-action@v1 -> @dde2242db6af... (v1.0.120)

- Replace blocklist allowedTools with a strict allowlist for both the
  review and summary steps. The previous `Bash(gh *)` + denylist allowed
  e.g. `gh api --method DELETE repos/...` because the denylist only
  covered specific subcommands. New allowlist only matches read-shaped
  invocations (`gh api repos/*`, `git diff *`, etc.); summary
  additionally permits `gh api --method PATCH repos/*` so it can update
  prior walkthrough comments.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After landing the strict allowlist in the previous commit, I downloaded
the claude-execution-output.json artifacts from the 10 most recent
successful runs of this workflow and extracted every tool_use event.
Several Bash patterns that the model actually invokes were missing
from the new allowlist and would have caused regressions.

Verified-missing patterns added to the review job:
  Bash(git fetch *)         (shallow-clone deepening)
  Bash(git status), Bash(git status *)
  Bash(git ls-remote *)     (used to verify remote refs)
  Bash(git rev-list *)
  Bash(git merge-base *)
  Bash(echo *)              (chained as `git log; echo ---; git diff`)
  Bash(ls), Bash(ls *)      (workspace inspection / skill discovery)

Verified-missing patterns added to the summary job (superset of above):
  Bash(cat *)               (cat > /tmp/walkthrough.md <<EOF heredoc)
  Bash(gh api -X POST repos/*) and --method POST equivalents
                            (creates the top-level walkthrough comment;
                             past runs use this OR `gh pr comment`)
  Bash(gh api -X PATCH repos/*) (alias for --method PATCH already allowed)

Comment block is also updated to record two pieces of mechanism
discovered while validating: (a) --allowedTools enforces only Bash(...)
and MCP tool patterns; built-in tools like Read/TodoWrite/Write bypass
it entirely, and (b) the matcher splits chained commands on `;`/`&&`/
`|` and matches each segment, which is why `Bash(git diff *)` covers
`BASE=…; git diff …` but `Bash(echo *)` is still needed for the
`echo "---"` separator the model uses between git steps.

Patterns were derived from runs 25510721849, 25537041205, 25537041220,
25696530024 (review and summary across PRs 543 and 547). Tools
referenced but never invoked in the data — `gh repo *`, `gh release *`,
`git push *`, `git reset *`, etc. — remain off the allowlist by
omission.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# if it's missing/stale, the workflow will fail fast on checkout, which
# is the right behavior for an unreviewable PR.
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps mention here why the pinning is done.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinning is pretty standard practice for CI security just to avoid accidentally using a new and compromised version -- this isn't pinned to that exact version for a particular version, other than it is "known to work". I think we can probably avoid an extra comment on it, but I'm open to it if you still think it would be useful to folks. What do you think?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants