A malicious GitHub issue shouldn't be able to trigger privileged workflows in your repository. But that's exactly what happened when Pillar Security discovered a vulnerability in Google's ADK Python repository. A public comment manipulated a triage agent, triggering a privileged code-fixing agent, potentially exposing credentials and enabling arbitrary code execution. Google deleted the three vulnerable workflows on June 9, 2026.
This isn't an isolated case. It's a pattern that repeats across organizations adopting automation without rethinking their access controls.
Why These Mistakes Keep Happening
Your CI/CD pipeline evolved from a simpler setup. You added a bot to auto-triage issues, another to run tests, and one to deploy staging environments. Each bot got a token with "just enough" permissions, but "just enough" was defined when you had fewer bots.
The problem grows when you treat automation tokens like outdated service accounts. You're running agents that parse untrusted input (pull requests, issues, comments) with the same identity that can push to production. The attack surface expands with each new workflow, but your access model remains flat.
Mistake 1: Using a Single Bot Identity Across Multiple Workflows
Why it happens: You created one GitHub App or service account when setting up CI/CD, and every workflow uses the same identity. It's simpler to manage one set of credentials than several.
Real consequence: When Pillar Security identified the vulnerable bot as a collaborator, that single identity had access to both the triage workflow (processing untrusted input) and the privileged code-fixing workflow (which can commit changes). An attacker who compromises the low-privilege workflow inherits all permissions of that shared identity.
The fix: Create separate bot identities for different trust boundaries. Your issue triage bot should use a different GitHub App than your deployment bot. If a workflow processes external input, it gets a restricted identity. If a workflow modifies code or infrastructure, it gets elevated permissions but never directly handles untrusted data.
Map your workflows by trust level:
- Public-facing (processes issues, PRs from external contributors): read-only access, can label and comment
- Internal automation (runs tests, updates dependencies): write access to specific branches
- Privileged operations (deploys, manages secrets): separate identity, triggered only by verified events
Mistake 2: Granting Repository-Wide Token Scopes
Why it happens: GitHub's default token scopes are broad. When you create a personal access token or GitHub App, selecting "repo" gives full control because that's the checkbox you need to enable basic operations.
Real consequence: A token with full repo scope can read secrets, modify workflows, and push to protected branches. In the Google ADK case, the compromised workflow could potentially access any credential stored in the repository because the bot's permissions weren't narrowed to its actual function.
The fix: Use fine-grained personal access tokens or GitHub Apps with minimal scopes. A bot that triages issues needs:
issues:writeto add labels and commentspull_requests:readto check PR status
It doesn't need contents:write, actions:write, or secrets:read.
For GitHub Actions specifically, use the permissions block in every workflow:
permissions:
issues: write
contents: read
pull-requests: read
Start with permissions: {} (no permissions) and add only what the workflow requires. If you can't articulate why a workflow needs a permission, it doesn't need it.
Mistake 3: Trusting Workflow Triggers Without Validation
Why it happens: You assume that because a workflow is triggered by a GitHub event, the event data is safe to process.
Real consequence: The Google ADK vulnerability worked through prompt injection in a public issue. The triage agent processed the issue content as trusted input, allowing an attacker to manipulate the agent's behavior and trigger the privileged workflow. Public issues, PR descriptions, and commit messages are all attacker-controlled input.
The fix: Treat all external input as untrusted, even if it arrives through your repository. Implement validation before any workflow acts on user-provided data:
- Sanitize before processing: Strip markdown, limit length, validate format
- Use allowlists: If a workflow should only run for organization members, check
github.actoragainst your team list - Separate parsing from action: One workflow validates and labels; a different workflow (with higher privileges) acts on the label, not the raw input
For AI agents specifically, you're dealing with prompt injection risks. Don't pass user input directly into agent prompts. Use structured data extraction first, then validate the extracted data before feeding it to decision-making workflows.
Mistake 4: Allowing Workflow Modification by Low-Trust Actors
Why it happens: Your repository allows external contributors to submit PRs, and you've configured workflows to run on pull_request events to test their changes.
Real consequence: An attacker submits a PR that modifies .github/workflows/deploy.yml to exfiltrate secrets or execute malicious code. Even if you require approval for the PR merge, the workflow runs before you review it.
The fix: Use pull_request_target instead of pull_request for workflows that need write access, but understand the tradeoff: pull_request_target runs in the context of the base branch, not the PR branch. This means you're running your existing workflow code (safe) but you need to explicitly check out the PR code (which requires validation).
Better approach: require workflows to run from protected branches only. In GitHub, use environment protection rules:
- Workflows that handle secrets or deploy code must target a protected environment
- Protected environments require approval from specific teams
- Workflow files in
.github/workflows/are protected by branch protection rules
This prevents an attacker from modifying the workflow itself, even if they can trigger it.
Mistake 5: Storing Secrets in Repository Variables Instead of Environment-Specific Vaults
Why it happens: GitHub repository secrets are convenient. You add PROD_API_KEY to repository settings, reference it in workflows, and it works.
Real consequence: Every workflow in the repository can access every secret. If an attacker compromises any workflow, they can exfiltrate all secrets. Repository-level secrets don't distinguish between your issue triage bot and your production deployment workflow.
The fix: Use environment-specific secrets with protection rules. Create separate environments in GitHub (staging, production) and assign secrets to those environments. Configure environment protection rules to require manual approval or restrict which branches can deploy.
For credentials that multiple workflows need, use a secrets manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) with short-lived tokens. Your workflow authenticates with OIDC, requests a time-limited credential, uses it, and the credential expires. Even if an attacker captures the token, it's worthless after 15 minutes.
Prevention Checklist
Before you add or modify a CI/CD workflow:
- Does this workflow process any external input (issues, PRs, comments)? If yes, it uses a restricted bot identity with read-only permissions
- Have I specified explicit
permissionsin the workflow file, starting from zero? - Does this workflow need to modify code or infrastructure? If yes, it requires approval from a protected environment
- Am I passing user input directly to any command or agent? If yes, add validation and sanitization first
- Can this workflow access secrets it doesn't need? If yes, move secrets to environment-specific vaults
- Is this workflow triggered by
pull_requestand does it have write permissions? If yes, switch topull_request_targetwith explicit checkout validation or require branch protection - Have I documented which bot identity this workflow uses and why it needs each permission?
Audit existing workflows quarterly:
- List all bot identities and their permissions
- Map which workflows use which identities
- Identify workflows that process external input and verify they use restricted identities
- Check for repository-level secrets that should be environment-specific
- Review workflows triggered by public events for input validation
The Google ADK incident shows what happens when you bolt automation onto access models designed for humans. Your bots aren't humans. They don't need the same permissions, and they definitely shouldn't share identities across trust boundaries. Fix that, and you close the attack path before someone finds it in your repository.



