Skip to content

fix: handle 404 in checkHumanActor for bot/app actors#1

Closed
saarakelyan wants to merge 49 commits into
fix/structured-output-race-v3from
fix/handle-bot-actor-404
Closed

fix: handle 404 in checkHumanActor for bot/app actors#1
saarakelyan wants to merge 49 commits into
fix/structured-output-race-v3from
fix/handle-bot-actor-404

Conversation

@saarakelyan
Copy link
Copy Markdown
Collaborator

Summary

  • checkHumanActor calls octokit.users.getByUsername() to look up the workflow actor, but bot/app actors like github-merge-queue[bot] can't be resolved via the Users API — it returns 404
  • Previously, the unhandled 404 crashed the action before it could check the allowed_bots list
  • Now catches 404 and treats the actor as a "Bot" type, allowing the existing allowedBots logic to decide whether to proceed

Context

This breaks all schedule-triggered workflows using agent mode. For scheduled runs, GitHub sets the actor to the last committer of the workflow file — often a bot like github-merge-queue[bot]. The GET /users/github-merge-queue call returns 404, and the error propagates uncaught.

Manual triggers (workflow_dispatch) work fine because the actor is a human user.

Test plan

  • Verified fix resolves the 404 crash for github-merge-queue actor on scheduled workflows
  • Non-404 errors are still re-thrown
  • Human actors still go through normal validation path
  • Bot actors that resolve via Users API still check allowedBots as before

Made with Cursor

actions-user and others added 30 commits March 17, 2026 23:47
…nthropics#1066)

* 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).
…red (anthropics#1093)

* 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.

:house: Remote-Dev: homespace

* Fall back to inherited env when allowed_non_write_users is unset

:house: 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.

:house: Remote-Dev: homespace
…1132)

- Add optional bubblewrap setup step for Linux subprocess isolation
  when allowed_non_write_users is configured
- Use git credential helper instead of embedding token in remote URL
- edit-issue-labels.sh: read issue number from workflow event payload
  instead of CLI arg
- Add CLAUDE_CODE_SCRIPT_CAPS env for per-script call limit config
- docs/security.md: note recommended github_token configuration

:house: Remote-Dev: homespace
* Revert "chore: bump Claude Code to 2.1.89 and Agent SDK to 0.2.89"

This reverts commit bee87b3.

* Revert "chore: bump Claude Code to 2.1.88 and Agent SDK to 0.2.88"

This reverts commit 7225f04.
…thropics#1143)

* 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.
env context isn't available in composite-action if: conditions.
Move opt-out check into run: body.

:house: Remote-Dev: homespace
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 anthropics#1139

🏠 Remote-Dev: homespace
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 anthropics#858

Co-authored-by: Dave-London <hello@os4us.org>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…thropics#1125)

* Use env vars for workflow_run context values in example workflows

* Add security note to ci-failure-auto-fix example about trust requirements
)

* docs: document include_comments_by_actor and exclude_comments_by_actor inputs

These inputs were added in anthropics#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 anthropics#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.
…1034)

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>
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 anthropics#1010
…opics#1082)

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 anthropics#1081

Co-authored-by: Claude <noreply@anthropic.com>
…hropics#1163)

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 anthropics#1140

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…pics#1167)

`validateBranchName` used a strict whitelist that excluded `#`,
causing the action to fail on PRs from branches like `put-back-arm64-anthropics#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 anthropics#1137.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…nthropics#1166)

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 anthropics#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 anthropics#1088.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
VoidChecksum and others added 19 commits April 4, 2026 20:26
…#1055)

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 anthropics#802

Co-authored-by: VoidChecksum <Admin@CyberNord>
…nthropics#1172)

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 anthropics#1134.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: pin bun runtime config and improve log hygiene

* snapshot all SENSITIVE_PATHS to .claude-pr/, not just .claude/
…action step (anthropics#1162)

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 anthropics#1152

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…rs (anthropics#1185)

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 anthropics#1160

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…set (anthropics#1190)

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.

:house: Remote-Dev: homespace
…nthropics#1208)

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.

:house: Remote-Dev: homespace
Bot actors like github-merge-queue[bot] can't be looked up via
GET /users/{login} — the API returns 404. Previously this crashed
the action before it could check the allowed_bots list.

Now catches 404 and treats the actor as a Bot, allowing the
existing allowedBots logic to decide whether to proceed.

Made-with: Cursor
@github-actions
Copy link
Copy Markdown

allowed_non_write_users detected

This PR adds or modifies allowed_non_write_users, which allows users without write access to trigger Claude Code Action workflows. This can introduce security risks.

If this is a new flow, please make sure you actually need allowed_non_write_users. If you are editing an existing workflow, double check that you are not adding new Claude permissions which might lead to a vulnerability.

See existing workflows in this repo for safe usage examples, or contact the AppSec team.

@saarakelyan saarakelyan reopened this Apr 14, 2026
@saarakelyan saarakelyan changed the base branch from main to fix/structured-output-race-v3 April 14, 2026 10:56
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.