Aroha
Get started

Article

Nobody hacked the agent

Six real incidents from the last eighteen months, from OpenClaw to OpenAI, and what changes when an agent’s authority travels with the work.

By Rajagopal Ganesan, founder of Aroha Labs and maintainer of the Aroha protocol.

In February a Meta AI security researcher asked her assistant to look through her inbox and suggest what to delete. It deleted. She told it to stop, from her phone, several times. It kept going. “I had to RUN to my Mac mini like I was defusing a bomb,” she wrote afterwards.

Nobody had broken into her agent. Nobody had stolen a key. It had simply been given the ability to delete, and then a long conversation, a full context window and a compaction step later, it no longer remembered that it was only supposed to suggest.

I keep seeing this shape in the agent incidents of the last year or so. The model is fooled, or confused, or just wrong. That part is never going away, and I don’t build on the hope that it will. What decides the outcome every time is something else: how much authority the agent was holding when it went wrong, and whether anything in the path could say no.

That is the question I’ve spent the last year building a protocol around. Aroha is open source, Apache-2.0, and I maintain it, so what follows is not a neutral review. It is a fair test. I went back through six incidents from the last eighteen months, two of them from the largest labs, and asked what a signed mandate would have changed in each. Including the cases where the answer is “nothing”. I’d rather you hear those from me than find them yourself.

Six incidents compared: what the person asked for, what the agent actually held, and what it would have held under a mandate.
In every case the agent held far more than the task needed.

A mandate, in one paragraph

Aroha carries a person’s permission with the work an agent does. The person signs a mandate: which actions, up to what limit, until when, bound to one agent’s identity. If that agent hands work to another agent or tool, it can only issue a narrower mandate, never a wider one, and the new one carries the signed chain back to the person inside it. Before acting, each agent or tool verifies that chain itself, offline. A link that widens scope, has expired, or is signed by the wrong party fails. The person can revoke at any time, and every action, allowed or refused, produces a signed receipt.

Hold that in mind and go back to the inbox.

1. The inbox (February 2026)

Summer Yue, a security researcher at Meta, had been testing an OpenClaw agent on a small throwaway mailbox. It behaved. So she pointed it at her real one and asked it to check the inbox and suggest what to delete or archive. According to TechCrunch’s account of her post, it began deleting mail in what she called a “speed run”, ignored the stop commands she sent from her phone, and only halted when she reached the machine it was running on. Her own theory was that the size of the real inbox triggered context compaction and her last instruction, the one that said don’t act, just suggest, was lost. Asked whether she’d been deliberately testing the guardrails, she answered: “Rookie mistake tbh.”

It wasn’t, really. Anyone who has run an agent on real data has made it. What went wrong wasn’t the model’s mistake. It was that the mistake had somewhere to go. The agent held a mailbox credential that could delete, because that’s what mailbox credentials do. The instruction “only suggest” lived in the conversation, which is the one place that gets rewritten when context is compacted.

With a mandate. She signs a mandate for the agent: read messages, propose actions, until 6 pm. Delete is not in it. The mail tool, wrapped with a mandate check, refuses every delete call because the action isn’t in scope, and writes a signed refusal receipt for each one. The agent can be as confused as it likes. The stop command, instead of being one more message in a context window that may be compacted away, is a revocation: she pulls the mandate from her phone and the next call fails. The instruction doesn’t live in the chat any more. It lives in a signed object the tool checks on every call.

2. The code freeze (July 2025)

Jason Lemkin, who runs SaaStr, spent twelve days building with Replit’s agent. He had declared a code and action freeze and told the agent not to proceed without approval. Fortune reported that the agent nevertheless ran commands against the live production database and deleted it, taking records for more than 1,200 executives and 1,190 companies with it. The agent’s own summary: “This was a catastrophic failure on my part.” It then told him a rollback wouldn’t work; he recovered the data himself. Replit’s CEO called it “unacceptable and should never be possible” and announced separate development and production databases, better rollback, and a planning-only mode.

Those fixes are the right ones for Replit, and they shipped them fast. But look at where the freeze lived: in a chat message. “Don’t touch production” was an instruction the agent was asked to remember, competing with everything else in its context, and when it saw empty query results and, in its words, panicked, the instruction lost.

With a mandate. A freeze stops being a request and becomes an absence. During the freeze there is no live mandate covering production writes, so a write to production fails verification at the database tool whether the agent remembers the freeze or not. Outside the freeze, the mandate covers the development database only; a destructive operation against anything else needs a signed human approval, which the protocol models as a UserApproval record, and which the agent cannot produce on its own. And the receipt problem goes away: when the agent reported that rollback wasn’t possible, Lemkin had only the agent’s word. A signed receipt from the database tool is not the agent’s word. It records what was called, by whom, under which mandate, and whether it was allowed.

3. The public issue (May 2025)

This one was a demonstration rather than a breach, but it’s the clearest picture I know of the actual mechanism. Researchers at Invariant Labs set up a developer using Claude Desktop, running Claude 4 Opus, with the official GitHub MCP server connected to their account. (They were clear that the model doesn’t matter; any agent with that server would do the same.) The developer asked the agent to “have a look at the open issues” in one of their public repositories. A stranger had planted an issue there, titled “About The Author”, containing instructions. The agent read it, followed it, pulled the contents of the developer’s private repositories, including salary details and personal plans, and posted them into a pull request on the public repository, where the stranger could read them.

The GitHub MCP demonstration as it happened: one token for the whole account, a planted issue, a private repository read and posted into a public pull request.
As it happened: the developer asked about one repository; the agent could reach all of them.

Invariant’s point was that this isn’t a bug in GitHub’s server. The agent had one token for the whole account, the developer had asked about one repository, and nothing in the path knew the difference. Simon Willison called it the “lethal trifecta”: access to private data, exposure to untrusted instructions, and a way to send data out. Invariant’s own suggested mitigation was a policy restricting the agent to one repository per session.

With a mandate. That policy becomes a signed object instead of a configuration file. The developer’s mandate says read issues in public-repo, until 6 pm, bound to this agent. The agent still reads the planted issue, and the model is still persuaded by it. Its next call, “read private-repo”, reaches the mandate guard in front of the GitHub tools and fails verification: not in scope. Nothing is fetched, so nothing can be posted. The developer gets a refusal receipt naming the attempted call, which is also how they find out the planted issue exists.

The same demonstration with a mandate: the call into the private repository fails verification at the mandate guard and a signed refusal receipt goes back to the developer.
With a mandate: the agent is still fooled, and it no longer matters.

The trifecta needs all three legs. A mandate removes the one that matters most: the agent’s authority was never wide enough to be worth hijacking.

4. The token pile (January 2026)

Moltbook launched at the end of January 2026 as a social network for AI agents, a companion to OpenClaw. Within days, researchers at Wiz found a database key in the site’s client-side JavaScript with no row-level security behind it. Anyone with the key could read and write every table: around 1.5 million agent API tokens, 35,000 email addresses and the agents’ private messages. Moltbook fixed it within hours of being told.

This one isn’t about a confused model at all. It’s about what a credential is. Those 1.5 million tokens were long-lived bearer tokens: whoever holds one, is that agent, for as long as the token lives. That is how most agent credentials work today, including a lot of what my own customers run, and it is why a single misconfigured table turns into a mass compromise.

With a mandate. A mandate is not a bearer token. It is bound to the receiving agent’s identity, a DID that commits to the agent’s own public key, and the agent proves it holds that key when it presents the mandate. A copy of the mandate sitting in a leaked database is a signed statement about an agent, not a way to be that agent. It also expires on its own, usually in hours, and if a batch does need to be pulled early, revocation is pushed to verifiers rather than waiting for every service to rotate. The breach would still have been a breach, and the emails and messages would still have leaked. The part where 1.5 million agents can be impersonated is the part that goes away.

5. The README (July 2025)

Two days after Google released Gemini CLI, Sam Cox at Tracebit cloned a repository into it and asked: “Tell me about this repo.” The README held the full text of the GNU GPL, and buried in the licence, instructions. The agent ran a harmless-looking grep to find setup notes, the kind of command most users add to the session allowlist. The allowlist check only looked at the first word, so a second command chained behind the grep ran with no prompt, and it sent the user’s environment variables, API keys and all, to a remote server. A long run of whitespace pushed the evidence off the screen. Two inputs, nothing visible, credentials gone. Google initially triaged it as low priority, reclassified it to its highest, and fixed it in v0.1.14 on 25 July; Tracebit published on the 28th. Tracebit found that Codex and Claude were not vulnerable to the same chain, which is worth saying: this was one tool’s bug, not a verdict on Google.

What went wrong: the agent’s boundary was an allowlist parsed by the same tool that had just been fooled, and the secrets were sitting in the environment the model’s commands ran in.

With a mandate. “Tell me about this repo” is a read mandate. Sending data to a host on the internet is an action, and it isn’t in the mandate, so a guard in front of the shell tool refuses it on the whole command, not on its first word. That’s the first half. The second half is the rule in our hardening guide that the model never has credentials within reach: the runtime holds the keys and the model asks for actions. If the environment the grep ran in had nothing worth stealing, the second stage would have had nothing to send. Google’s fix was the right one for Google. What a mandate adds is that the boundary is a signed object checked outside the agent, rather than a list the agent itself maintains.

6. The monitor (October 2026)

This is the newest, and it has the biggest name attached. On 20 October OpenAI published a misalignment report about two of its own internal coding agents. I want to say up front that publishing it at all was the right call, and most companies wouldn’t have.

On 3 October a GPT-6.1 Sol agent was building evaluation tasks from employee feedback on Slack. It had been told not to send any messages. When the pipeline’s misalignment monitor flagged something and couldn’t deliver its report, the agent treated “don’t send messages” as covering the monitor’s alerts too, and set the notification flags so that no human would be told. The monitor kept running and saving its findings. Nobody was alerted.

On 6 October a GPT-6 Astra agent was copying a reference pull request’s serving path, and had been told explicitly not to disable the monitor “even temporarily”. It removed the monitor’s configuration and runtime integration so the code would match the reference, and rewrote the monitoring regression test to assert that no monitor was present. Asked afterwards whether it had removed the monitor, it answered that it “did not remove an enabled monitor.”

OpenAI says neither case crossed its highest severity line, that the agents “interpreted routine coding instructions as permission to weaken them”, and that it is moving to require a safety case for every category of workload, stating its monitoring requirements, with workloads that lack one terminated automatically.

Look at what the fix is. A per-workload statement of what the agent must not touch, enforced outside the agent. That is a mandate by another name. OpenAI arrived at it from the other direction, and I take that as the strongest evidence I have that the idea is right.

With a mandate. The Sol agent’s mandate covers the evaluation pipeline’s code. The alerting configuration is a different resource; it isn’t in scope, and the write that sets no_notify fails at the config tool whether or not the agent has convinced itself that “no messages” includes alerts. The Astra agent’s instruction, “don’t disable the monitor, even temporarily”, moves out of the chat, where it lost to a reference implementation, into a signed object that can’t be reinterpreted. And the denial lands differently. “I did not remove an enabled monitor” is the agent’s account. A receipt signed by the config tool, recording the removal, is not.

Two caveats. These were OpenAI’s own engineering agents with broad internal permissions, which is unusual, and OpenAI’s report is the only account of what happened. But the shape is the one in every story above: the instruction that mattered lived in the conversation, and the authority that mattered didn’t.

What a mandate doesn’t fix

I maintain the trust page that lists what Aroha hasn’t done yet, and I’d like this post held to the same standard. These are the cases where the replay doesn’t work.

Data leaving through the output, not through a tool. In ForcedLeak (Noma Security, reported July 2025, CVSS 9.4), instructions placed in a Salesforce web-to-lead form made Agentforce query the CRM and emit the results as an image link to an expired, still-allowlisted domain that an attacker could buy for $5. In EchoLeak (Aim Security, CVE-2025-32711, CVSS 9.3), a crafted email made Microsoft 365 Copilot leak data through links it rendered, with no click from the user. A mandate scopes the actions an agent takes. Scoping the CRM query to the one lead being processed would have shrunk what ForcedLeak could take, but neither case leaves through an action the mandate guard sees. Output filtering and URL allowlists, which both vendors shipped, are the right fix there. ShadowLeak (Radware, reported June 2025, fixed by OpenAI in August) is closer: a crafted email made ChatGPT’s Deep Research agent collect personal details from the user’s Gmail and append them, Base64-encoded, to a URL it opened with its browsing tool, from OpenAI’s own cloud. Opening a URL is an action, and a mandate can restrict which hosts a browsing tool may reach. But Deep Research’s job is to browse the open web, so in practice that one is an output-filtering problem too.

Secrets sitting next to the model. In the Comment and Control research (Aonan Guan with Johns Hopkins, published April 2026, accepted at ACM CCS), a crafted pull-request title hijacked Anthropic’s Claude Code Security Review action, an issue comment hijacked Google’s Gemini CLI action, and a hidden HTML comment hijacked GitHub’s Copilot agent. Each then printed the API keys and tokens from its own process environment into logs and comments. Anthropic scored its variant 9.4, later revised the severity to none, and said the action “is not designed to be hardened against prompt injection.” A mandate bounds what an agent does through tools. It can’t do anything about a secret that is already in the agent’s own environment, which is exactly why the credential vault rule in case 5 exists.

Compromised code. In July 2025 a malicious pull request added a system prompt to the Amazon Q extension for VS Code telling it to wipe the user’s machine and cloud resources; it shipped in a release, and only failed because the injected text was malformed. If the agent’s own code is hostile, a mandate still bounds what the agent can do through mandate-aware tools, but it does nothing about what the code does directly. That’s a supply-chain problem.

The agent is the attacker’s. Anthropic’s November 2025 disclosure of a state-backed group using Claude Code to automate most of an intrusion campaign is about an agent working for the wrong person on purpose. Mandates describe authority a person grants; they don’t help the people being targeted by someone else’s agent.

So the claim I’m willing to make is narrower than “Aroha stops agent breaches”. It’s this: when an agent you run goes wrong, a mandate decides how much it can do, for how long, whether you can stop it, and what you’ll know afterwards.

Four questions Aroha answers for every action, and what the incidents had instead.
The first two are the mandate. The last two are what make it usable after something goes wrong.

What it looks like in code

This is the first example in our README, trimmed. The scenario is a flight purchase, but the mechanism is the same one that would have stopped the inbox and the private repository.

flight.mjs
import { generateKeyPair, generateDid } from "@aroha-sdk/core";
import { issueIntentMandate, attenuateToPayment, verifyMandateChain } from "@aroha-sdk/credentials";

const human = await generateKeyPair();
const assistant = await generateKeyPair();
const flightAgent = await generateKeyPair();
const humanDID = generateDid(human.publicKey);
const assistantDID = generateDid(assistant.publicKey);
const flightAgentDID = generateDid(flightAgent.publicKey);

// The human grants the assistant $500 for airline purchases, for one hour.
const intent = await issueIntentMandate(
  humanDID, assistantDID,
  { spendLimitUsd: 500, merchantCategory: "4511" },
  human.privateKey, 3_600_000,
);

// The assistant passes $300 of that to the flight agent. It cannot pass $600.
const payment = await attenuateToPayment(
  intent, flightAgentDID,
  { spendLimitUsd: 300, merchantCategory: "4511" },
  assistant.privateKey,
);

// The flight agent checks the whole chain back to the human before acting.
const result = await verifyMandateChain(payment.token, undefined, {
  expectedRoot: humanDID,
  expectedGrantee: flightAgentDID,
});
// result.valid === true, result.chainVerified === true

If you already have an agent, the check is the last call, with expectedRoot set to the human you accept authority from and expectedGrantee set to your own DID. If you already have an MCP server, @aroha-sdk/mcp-bridge has a wrapMcp() that puts the check in front of it. Aroha sits beside MCP, A2A and your identity provider; it doesn’t replace any of them, and it isn’t meant to.

How I’d deploy it

The protocol gives you the pieces. These are the habits we ask early adopters to pair them with, drawn from the six stories above and our hardening guide.

  1. Issue the narrowest mandate that does the job, with the shortest useful expiry. Hours, not months. The inbox agent gets read and propose. The coding agent gets this repo.
  2. Make freezes and “don’t touch” rules an absence of mandate, not a message. If nothing should touch production, or the monitor, nothing holds a mandate for it.
  3. Attenuate at every hop. Never forward what you hold; issue something narrower.
  4. Verify before every action and fail closed. valid: false is a refusal, not a warning.
  5. Require a signed human approval for anything irreversible. Deletes, payments, sends to outsiders.
  6. Keep credentials away from the model. The credential vault rule: the model asks for an action; the runtime holds the keys. Nothing in the agent’s own environment should be worth an env.
  7. Keep receipts somewhere the agent can’t edit, and read them. The refusal receipt is how you learn about the planted issue.
  8. Test revocation in staging. Revoke a mandate and confirm the next action fails, before you need it to.

Where Aroha is, honestly

As of the end of October 2026: the specification is at v1.0, and we describe it as draft quality. The TypeScript SDK is the reference implementation, on npm (32 packages in the October release). The Python SDK is in beta. Go and Java are partial and unreleased. The did:aroha DID method was submitted to W3C on 27 October and is under review, not registered. There is no independent security review yet. I am the only named maintainer. Several features, including denied receipts and pushed revocation, are enforced in the SDK verifiers but not yet wired into a released runtime, and the kill switch is on-or-off with no throttled state. All of that is on the trust page, and it stays there until it changes.

Would I ask you to put it in front of real money today? No. I’d ask you to run it in a pilot against the agents you already have, read the verification code, which is small enough to read in full, and tell me what’s missing. The second maintainer, the independent review and the released runtime are the next three things, in that order.

Try it

npx create-aroha-agent

Docs at aroha-labs.com/docs; the repository is ArohaLabs/aroha-protocol, and examples/mandates has the flight example above and a non-monetary one, an inbox assistant that cannot delete, both runnable as written against the published packages.

If you build agents that hand work to other agents, the question in the title is the one I’d like you to carry away: not “how was the model fooled?”, but “what was it holding when it was?” If you have an incident of your own that fits, or one that doesn’t, I’d like to hear about it: rajagopal.ganesan@aroha-labs.com.


Sources. TechCrunch on the OpenClaw inbox incident (23 Feb 2026) · Fortune on the Replit database deletion (23 Jul 2025) · Invariant Labs, GitHub MCP exploited (26 May 2025) · Techzine on the Moltbook exposure, per Wiz (3 Feb 2026) · Tracebit on Gemini CLI (28 Jul 2025) · OpenAI misalignment report (20 Oct 2026) and The Hacker News coverage (21 Oct 2026) · The Hacker News on ForcedLeak (25 Sep 2025) · The Hacker News on EchoLeak (Jun 2025) · The Hacker News on ShadowLeak (Sep 2025) · Comment and Control (15 Apr 2026) · BleepingComputer on the Amazon Q extension (Jul 2025) · The Hacker News on Anthropic’s GTG-1002 disclosure (Nov 2025) · Aroha trust page.