Skip to content
Aitsam Ahad

Security

Someone opened a GitHub issue. 4,000 machines installed software nobody approved.

No stolen password. No zero-day in a parser.

That was the whole budget — of text.

In February this year a security researcher showed that anyone at all could open an issue on a particular public repository. No write access. Not a contributor. A sentence typed into the title field.

No access. No credentials. No code review.

Eight days after he published that, roughly four thousand developer machines installed a piece of software nobody had asked for.

Why it matters

That is the whole budget. No stolen password, no zero-day in a parser, no elaborate jailbreak of a language model. One line of text in a field that any stranger on the internet is invited to fill in.

The vulnerable component behaved exactly as designed.

And the uncomfortable part is that nothing in this chain malfunctioned. Every component did precisely what it was built to do. The failure is in how the authority was arranged, not in any one piece of code.

This is not a new class of bug. It was named in 1988.

By the end of this you will be able to look at any agent wired into any system and answer one question about it: can an instruction from a stranger reach something that holds your credentials. That test is thirty-eight years old, and it decides this case completely.

what the attacker brought to this
what the attacker brought to this

The mental model

Start with the original. A company called Tymshare sold access to a shared computer, and on it ran a Fortran compiler that customers paid to use.

He opens by calling it 'a nearly true story'.

That compiler had a privilege its users did not. It recorded statistics about language feature usage, so it was granted what the system called a home files license - permission to write to any file in its own directory, SYSX. No customer could write there directly.

The compiler also had an ordinary feature. You could tell it where to put its debugging output. You passed it a filename, and it wrote there on your behalf.

So a user passed it the name of the billing file. The compiler checked nothing, because from the operating system's point of view there was nothing to check. The compiler was allowed to write there. It wrote there. The billing records were overwritten.

The compiler runs with authority stemming from two sources.

Norm Hardy wrote this up in nineteen eighty-eight, in the ACM SIGOPS Operating Systems Review, under the title The Confused Deputy. And his diagnosis is sharper than the retellings usually are. The compiler, he says, runs with authority stemming from two sources. That is precisely why it is a confused deputy.

Authority one comes from you, the moment you invoke it - you yield your rights to the program. Authority two is the home files license, granted permanently by the administrators. The compiler carries both at once.

'The compiler had no way of expressing these intents.'

And here is the sentence that matters. When it writes statistics it means to use the license. When it writes your debugging output it means to use your authority. The compiler had no way of expressing those intents. The request went to the operating system with no record of which of its two hats it was wearing.

the original exploit, 1970s the compiler's two authorities

The mechanism

Now the modern version. In December last year, the Cline project added an automated issue triage workflow - a language model that reads incoming issues and helps sort them. Sensible housekeeping, and an increasingly normal thing to run.

The issue title was interpolated straight into the model's prompt. Look at line three. Everything above it is instruction written by the maintainers. The title is data written by a stranger. Once both are inside the same block of text, there is nothing left that distinguishes them.

Models process instructions and data in one channel.

That is the structural problem, and it is worth being precise about it. A language model receives one stream of tokens. It does not receive a marked instruction region and a separate marked data region. The separation exists only in the mind of whoever wrote the template.

So the title carried instructions, and the model followed them. It was told to install a package from an attacker-controlled commit, reached by forking the repository - a fork's objects stay reachable from the parent, so the code looked like it came from the project itself.

Installing a package runs its install scripts. That is not a vulnerability, it is how the package manager works. The attacker's package carried a preinstall script, and it executed inside the project's own continuous integration runner.

Now the interesting part, because the triage runner itself had nothing worth stealing. What it had was access to the shared build cache. The attacker filled that cache with more than ten gigabytes of junk, which forced the oldest entries out, and then wrote poisoned entries under the exact keys the release job would later look up.

A low-privilege job wrote what a high-privilege job would read.

The release workflow ran that night, asked the cache for its dependencies, and got the attacker's versions instead. The privilege boundary was crossed by a cache key.

Three publishing tokens came out of that job. Together they are the ability to ship an update to every user of the project, through the channels those users already trust.

The researcher who found this, Adnan Khan, reported it privately on the first of January, heard nothing for forty-seven days, and published on the ninth of February. The project removed the vulnerable workflows in under an hour. But the token rotation that followed was incomplete - the wrong token was revoked, and one of the stolen ones stayed valid. Eight days later it was used to publish a tampered release, which sat live for roughly eight hours and installed a second AI agent on about four thousand machines.

cline/cline · Issues credentials exfiltrated from the release job developer machines that installed it - ~4,000

Back to the anomaly

Put them side by side and it is the same bug. The triage workflow also ran with authority from two sources. It had the instruction, which came from a stranger's issue title. And it had the project's own standing rights inside its own repository. Like the compiler, it had no way to say which of those it was acting on - so the registry at the far end saw a legitimate release, exactly as the operating system saw a legitimate write.

The triage workflow held more capability than its function required.

So the honest summary of the headline is not that an AI was tricked. It is that a component was given authority out of all proportion to its job - reading issue titles - and then handed text from strangers. The model is the newest way to confuse a deputy. It is not the reason the deputy had the keys.

The model made it easier. The permissions made it possible.

That distinction matters, because it decides where you spend your effort. If you believe the problem is the model, you write better system prompts. If you understand it as a confused deputy, you take the keys away from the thing that reads strangers' text.

thirty-eight years apart
thirty-eight years apart

What to look at next

Where does the authority actually come from?

The project's own fix points at the next question. They moved publishing away from long-lived tokens sitting in a secrets store, to short-lived credentials issued per workflow run and bound to it.

That is the capability idea Hardy was arguing for in his subtitle - authority that travels with a specific request rather than sitting permanently attached to whoever is running. Worth asking, of any agent you operate: if someone typed the perfect sentence into the least important input it reads, what could it already do.

the year this was already understood - 1988
the year this was already understood - 1988

Sources

  • Hardy, 'The Confused Deputy (or why capabilities might have been invented)', ACM SIGOPS Operating Systems Review 22(4), 1988, pp. 36-38
  • The confused deputy pattern applied to autonomous AI agents and prompt injection
  • Prompt Injection
  • Confused Deputy
  • Supply Chain

Written by

Aitsam Ahad

Senior Full-Stack Engineer with 6+ years architecting scalable web applications in Node.js, TypeScript, Express and NestJS on the backend and React/Next.js on the front. Currently Principal Software Engineer at TEO International, Islamabad.

Explore my experience