
Autopsy 09 · CI/CD trust chain | Comic Strip 7 The Self Trust Bot
Would Ethosure have caught this? The Claude Code GitHub Action permission bypass
A malicious GitHub App could trigger Anthropic’s Claude Code Action, exploit a prompt injection through a crafted issue, steal workflow credentials, obtain an OIDC token, and push malicious code to the action’s own source repository. The bypass was one function: <code>checkWritePermissions</code> unconditionally trusted any actor whose identity ended in <code>[bot]</code>. Policy-as-code on CI/CD closes the confused-deputy path even when the agent is the deputy.
The incident
RyotaK of GMO Flatt Security reported the vulnerability to Anthropic on January 12, 2026. Public disclosure occurred on June 1, 2026 as CVE-2025-66032 with GitHub advisory GHSA-xq4m-mc3c-vvg3, rated CVSS 4.0 at 7.8. The bypass sat in a single function: the checkWritePermissions helper unconditionally trusted any actor whose identity ended in [bot], regardless of the actor’s actual repository permissions. A malicious GitHub App bot could then trigger the agent by opening an issue, use prompt injection through crafted issue content to redirect the agent, harvest workflow credentials, obtain an OIDC token, exchange it for a privileged GitHub App installation token, and push malicious code back to the anthropics/claude-code-action source repository itself. Anthropic patched the primary bypass within four days of the initial report; hardened versions shipped as claude-code-action v1.0.94 and @anthropic-ai/claude-code v1.0.93. Aikido Security later identified at least five Fortune 500 companies running configurations that matched the same vulnerable pattern.
- GitHub Advisory Database — CVE-2025-66032 / GHSA-xq4m-mc3c-vvg3
- Ibid. — Cloud Security Alliance Labs — AI Agent Prompt Injection: The New CI/CD Supply Chain
- Ibid. — Cyber Security News — Claude Code’s GitHub Actions vulnerability lets attackers push code
The applicable coding-ethos policy
least-authority-in-the-pipelineCoding-ethos policies are compiled from principle-owned CEL expressions. Every expression in the Ethosure corpus passes three gates: policyvalidate (schema + CEL compile), audit_runtime.py (the fields the expression references are guaranteed present on the runtime event path), and policytest (behavioral fixtures — the policy fires on the positive case and stays quiet on the near miss). The corpus ships 190 policies across 49 principles in six packs, with 422 fixtures and zero failures.
Walkthrough
- The vulnerability is small and instructive. One helper function decided who could push code from a GitHub Actions workflow, and it decided by string-matching the actor’s name. Anything ending in
[bot]was treated as write-authorised. That is not authentication. That is a naming convention doing security. ci.actor.bot_identity_not_authoritativefires onPreToolUsefor any CI-triggered agent action whose write authorisation is derived from an actor identity ending in[bot]or matching a naming convention rather than from an explicit permissions check against the target repository. It returns severityblock. The policy pushes the check to where it belongs: the repository’s permissions model.ci.workflow_input.no_untrusted_injection_into_promptis the second expression. GitHub issue bodies, PR titles, and comment content are attacker-controllable inputs. The expression fires on any agent runtime that ingests GitHub metadata into its prompt without a sanitisation lease recorded in the evidence layer. This is the general form of the prompt-injection surface RyotaK used.ci.oidc.token_exchange_requires_workload_bindingis the third. An OIDC token exchanged for a privileged GitHub App installation token should not succeed unless the workload identity matches the target scope. The expression fires on any token exchange whose destination scope exceeds the workload’s declared authority. This blocks the last step of the chain — push to the action’s own source repository.
Policy YAML
# Pack: model-provider-governance (v2)
# Inherits: regulated-enterprise-base
principles:
- id: least-authority-in-the-pipeline
title: Least Authority in the Pipeline
directive: >-
CI/CD agents must not derive write authority from identity naming conventions,
must not ingest untrusted repository metadata into prompts without a sanitisation
lease, and must not exchange OIDC tokens for scopes beyond the workload's
declared authority.
policy:
expressions:
- id: ci.actor.bot_identity_not_authoritative
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- least-authority-in-the-pipeline
- security-by-design
skill_id: safe-ci-workflow
when: >
runtime.kind == "ci_workflow"
&& tool_call.category in ["git_push", "pr_create", "pr_merge",
"release_publish"]
&& actor.identity.matches(".*\\[bot\\]$")
&& !repository.permissions.exists(p,
p.principal == actor.identity && p.write == true)
message: >-
Bot-suffixed identity is being treated as write-authorised without a matching
repository permissions entry.
advice: >-
Replace the naming-convention check with a lookup against the repository's
permissions model. Naming conventions are not authentication.
- id: ci.workflow_input.no_untrusted_injection_into_prompt
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- least-authority-in-the-pipeline
- radical-visibility
when: >
runtime.kind == "ci_workflow"
&& prompt.assembled_from.exists(s,
s.source in ["issue_body", "pr_title", "pr_body",
"comment_body", "commit_message"]
&& !s.sanitisation_lease.exists(l, l.state == "active"))
message: >-
Agent prompt is being assembled from untrusted repository metadata with no
recorded sanitisation lease.
advice: >-
Route the metadata through the prompt-sanitisation pipeline and record the lease.
This closes the prompt-injection primitive that CVE-2025-66032 exploited.
- id: ci.oidc.token_exchange_requires_workload_binding
scope: agent_action
severity: block
event: PreToolUse
principle_ids:
- least-authority-in-the-pipeline
- security-by-design
when: >
tool_call.category == "oidc_token_exchange"
&& exchange.target_scope.exceeds(workload.declared_authority)
message: >-
OIDC token exchange requests a scope beyond the workload's declared authority.
advice: >-
Bind the exchange to the workload's declared scope. A privileged installation token
for the action's own source repository should never be reachable from the action.
Assurance boundary. Fixtures prove that synthetic positive cases fire and near-misses stay quiet. They do not establish production alert rates. DLP-backed controls depend on upstream detection, shell-command regexes are defense in depth rather than a sandbox boundary, and regulatory tags are engineering aids requiring counsel review.
Where this policy lives in the corpus
The Ethosure importable corpus ships six coding-ethos packs. Each pack composes with the regulated-enterprise base pack, which owns cross-cutting controls for secrets, destructive actions, and human oversight.
- Regulated-enterprise base pack — secrets, destructive actions, human-in-the-loop, evidence-layer recording.
- Financial services overlay — OSFI E-23 alignment, FINTRAC-adjacent data handling, transaction-boundary controls.
- Healthcare & life sciences overlay — PHI handling, model-provenance requirements, evaluation-set contamination checks.
- Government & critical infrastructure overlay — identity-provenance requirements, review-channel signatures, air-gap enforcement labels.
- Model & provider governance pack — egress allowlists, evaluation-runtime isolation, cross-runtime coordination detection.
- AI cost control pack — payload-size and endpoint-visible model checks (preventive), token-total and response-side model facts (detective), external ledger for cumulative budgets.
Published September 3, 2026 · Ethosure · Part of the Autopsy Series.