An Agent in CI Reopens the Boundary You Already Closed
The review reflex fires on checkout and stays silent on an agent reading a bug report

Ihor K
CEO
The review reflex fires on checkout and stays silent on an agent reading a bug report

Ihor K
CEO
The rule itself is not in dispute and never was: a workflow holding secrets does not run code from an untrusted fork. What changed is that an agent can now break that rule without executing a single line of anyone's code.
Every team that ever configured pull_request_target learned the rule the hard way, and GitHub now writes it down in its own hardening guidance: a trigger that exposes repository secrets must not check out untrusted code. The review reflex fires on the word checkout, and it fires correctly. An agent in the pipeline passes that check cleanly. It runs no attacker code. It reads a bug report, and a bug report is text, and text is not execution. Except that for this one component the text is the program. The reflex is aimed at a boundary the agent never crosses, and the price of reaching a job that holds your credentials has fallen from write access to one filed issue.
State the mechanism precisely and the shape of the fix falls out of it. The model receives one flat context: your instructions, the repository files it was pointed at, and a body written by an account created ten minutes ago, with no privileged channel marking which half is authoritative. Priority inside that context is a matter of phrasing, not of structure. An instruction wrapped in an HTML comment renders as nothing to the human triaging the issue and as a plain imperative to the model. Invariant Labs walked the whole chain in May 2025 against GitHub's own MCP server, carrying nothing but a public issue: the agent read it, and then used the user's token to publish the contents of private repositories into a public pull request.
Nothing exotic happens after that. The agent calls the tools it was granted, in a runner whose process environment already carries the tokens. There is no exploit in the chain and no memory-safety bug to patch; Invariant Labs said as much about their own finding, that what they had was architectural rather than a defect in the server they broke it on. Someone handed text to a component whose input language is text, and the component did its job. The defect sits a level up, in where the privilege boundary was drawn. It was drawn around code execution because until recently execution was the only way untrusted input had ever become an instruction. That stopped being true, and the boundary did not follow.
That is why a filter in front of the model buys less than it looks. A filter is a classifier, and a classifier has a false-negative rate; whatever rate you would be willing to defend out loud is also the attacker's expected cost in attempts, and filing issues costs him nothing. He also chooses the input: encode the payload, or split it across two comments that only meet inside the context window, and literal matching stops firing at all. This is not a forecast about cleverer attackers. Researchers from OpenAI, Anthropic, Google DeepMind and several universities took twelve published defenses in 2025 — prompting, adversarial training, filtering, secret-knowledge schemes — most of which had reported near-zero attack success rates, and pushed success above ninety percent on most of them simply by letting the attacker adapt to the defense. Meta's own security guidance concedes the same ground, framing its rule as what to do until detection becomes reliable. Every point of detection accuracy is paid for by the defender, and every attempt is free for the attacker. Access control has no false-negative rate, and that asymmetry is the whole of the reason to spend the engineering there instead.
Meta's guidance gives the shape a name. Its Agents Rule of Two holds that within a single session an agent should satisfy no more than two of three properties: processing untrusted input, holding access to sensitive data or systems, and being able to change state or communicate outward. Remove any one of the three and the chain does not close. In a pipeline that is two jobs and a schema between them. The first reads the issue with no secrets in its environment and egress denied, and emits typed fields: a file path, a test name, an enum. The second holds the credentials, reads only those fields, and never sees the issue. Count the secrets a job can see, multiply by the destinations it can reach, and that product is your blast radius per filed issue; the split sets one factor to zero rather than shrinking it.
The cost is real, and it is precisely what you bought. An agent that could read the issue, find the failing test and open the fix in one pass now stops at a typed struct and hands off. Half the value was the model deciding for itself what mattered in that text, and a schema written in advance can only carry the fields you already knew to ask for. Every case you did not anticipate comes back to a human triaging by hand, and every free-text field you push across the seam to win some of that back rebuilds the channel you just cut. So expect the pressure, and expect it to win quietly: in six months two jobs separated for a good reason get merged by someone shipping under a deadline, in a pull request titled simplify workflow. The reflex that fires on the word checkout was built out of incidents, one repository at a time. Nothing has yet taught anyone to fire on a thirty-line diff that deletes a schema and makes every test run faster.
SOURCES