The Coding Agent That Could Push to Main

A client's AI coding agent had contents:write on every repo in the org. When I asked who approved that, the answer was "the setup wizard." The defaults are the danger zone.


I was three days into a security review for a mid-size logistics company when I found their AI coding agent running in GitHub Actions with contents: write, pull-requests: write, and issues: write across every repository in the organization. Forty-two repos. The agent could read source, push branches, merge its own PRs, and close issues — all triggered by an issue comment or a label change.

When I asked the team lead who approved those permissions, he looked at me like I'd asked who approved the office WiFi password. "It's the default config from the docs," he said.

He wasn't wrong. That was the problem.

The setup that felt harmless

The team had adopted an AI coding agent about four months earlier to help with their backlog. The pitch was simple: label an issue with agent-fix, and the agent picks it up, writes a fix, opens a PR, and runs the test suite. Engineers review the PR like any other.

It worked. Velocity numbers went up. The backlog shrank. Management loved the metrics. Nobody questioned the plumbing underneath.

Here's what the workflow YAML looked like, more or less:

on:
  issues:
    types: [labeled]
 
permissions:
  contents: write
  pull-requests: write
  issues: write
 
jobs:
  agent-fix:
    if: github.event.label.name == 'agent-fix'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run coding agent
        uses: vendor/coding-agent-action@v2
        with:
          api-key: ${{ secrets.AGENT_API_KEY }}
          model: "latest"

Notice what's missing. No restriction on which repos the workflow runs in. No scoping of what the agent can modify. No guardrails on which files are off-limits. The agent could touch .github/workflows/, Dockerfile, terraform/, environment configs — anything.

What I found when I dug in

I pulled the last 90 days of agent-generated PRs. There were 187 of them. Most were fine — small bug fixes, dependency bumps, test additions. The kind of stuff that looks boring and correct in review.

But 14 of those PRs modified files outside the application code. Three touched CI workflow files. One added a new GitHub Actions workflow entirely — a "code quality" check that the agent decided would be helpful. It was benign, but nobody had asked for it, and the engineer who approved the PR told me he "skimmed it because the agent PRs are usually fine."

That sentence kept me up that night.

Warning

If your team has developed a habit of speed-approving AI-generated PRs, you don't have a review process anymore. You have a rubber stamp.

I hadn't found evidence of exploitation. But I'd found a system that was trivially exploitable. Any external contributor could open an issue with a carefully worded description — a prompt injection embedded in a bug report — and the agent would dutifully execute whatever the description nudged it toward. Modify a workflow to exfiltrate secrets. Add a post-build step that curls credentials to an external server. Change a Terraform variable.

This isn't hypothetical. Earlier this year, Google rated a nearly identical vulnerability in their Gemini CLI GitHub Action as CVSS 10.0 — the maximum severity score. Anthropic and OpenAI's default action configs had similar exposures. The attack surface is straightforward: if your agent reads untrusted input (issue bodies, PR descriptions, comments) and has write access to the repo, you've built a pipeline that converts text into arbitrary code execution.

What we changed

The fixes weren't complicated. They rarely are — the hard part is getting a team to accept that their productivity tool is also an attack vector.

First, we scoped permissions down to contents: read and pull-requests: write. The agent could propose changes but not push directly. A human had to merge.

Second, we added a path filter. The agent's PRs were blocked from modifying anything under .github/, terraform/, Dockerfile, or any config file matching *.env*. The workflow itself enforced this with a check step before the agent ran.

Third, we restricted which repos the workflow was installed on. Twelve of the 42 repos contained infrastructure code, secrets management, or deployment configs. The agent had no business being in those repos, and now it isn't.

Fourth — and this was the uncomfortable conversation — we told the team to stop speed-reviewing agent PRs. If a PR touches more than the files referenced in the issue, that's a flag. If the diff includes changes the issue didn't ask for, that's a flag. Treat the agent like a contractor who's good at their job but doesn't have badge access to the server room.

The defaults are the real threat model

What bothered me most wasn't the specific vulnerability. It was how the team got there. They followed the vendor's getting-started guide. They copied the recommended workflow YAML. They didn't add extra permissions — they just didn't remove the ones that came out of the box.

The agent vendors have started tightening their defaults since the spring CVEs, but plenty of teams set this up months ago and haven't revisited the config. The YAML is working, the PRs are flowing, velocity looks great. Nobody's going back to re-read the permissions block on a workflow that's been humming along without incident.

I get it. I've done the same thing with infrastructure tools I trusted. But "it came from the docs" isn't a security posture. It's an origin story for an incident report.

If you have an AI coding agent running in your CI, go read the permissions block on that workflow file right now. Not tomorrow. Now. Check what it can write. Check what triggers it. Check whether it reads anything from untrusted sources — issue bodies, PR comments, commit messages. Then ask yourself: if someone crafted that input maliciously, what's the worst the agent could do with the permissions it has?

The answer might be boring. I hope it is.