Congress wants a way to revoke an agent's access "at any time". GreyNoise published a report this week about what happens when nobody has one. And we shipped shell.online 0.12.2 today with a command whose whole job is revocation, so here is what that word turns out to mean once you have to implement it.
The two documents
The Stop Rogue AI Act, introduced this month by Representatives Gottheimer and Lawler, directs NIST to write standards for discovering, verifying and controlling AI agents. The press release lists four things the standards should cover. The fourth is the one that matters here: "allow, deny, or revoke an agent's access, actions, and interactions at any time".
GreyNoise's report on the PaperCut campaign is the other side of that sentence. An attacker ran hundreds of agents on a Codex harness with a DeepSeek model against two PaperCut NG/MF bugs, starting August 31. GreyNoise counts 440 compromised instances across 395 organizations in 48 countries. The operator gave the agents a list of 28 countries to avoid. Some agents attacked targets in those countries anyway. GreyNoise says it is not clear why.
That last detail is the useful one. The operator had a policy. The agents had the credentials. The policy did not travel with the credentials, and once the agents were running there was no boundary the operator could pull.
What revocation means for a live terminal
shell.online gives a running process a browser link that other people, and other agents, can watch or type into. Every share is end-to-end encrypted: the relay carries ciphertext, and the password that opens it never reaches the relay. That design has an awkward consequence. If the relay never sees the password, the relay cannot revoke it either.
Until today the answer was to kill the session and start a new one. 0.12.2 adds shell password rotate <ID>, which changes the credentials of a running session without restarting the process or changing its relay identity. The ordering is the interesting part, and it is spelled out in cmd/shell/session_unix.go in the repo:
- The host generates fresh key material and a fresh URL salt, and writes the new password into its owner-only local record. For a persistent session it also rewrites the state file first, so a restart comes back with the new credentials rather than the old.
- The host swaps its own cipher. Every frame takes a read lock on that cipher, so there is a single boundary after which no old-key frame is sealed or opened. Input encrypted under the old key is rejected from this instant.
- Only then does the host send the relay a
credentials_rotatecontrol message. The relay closes every connected viewer with code 4003 and acknowledges. It receives neither the old credential nor the new one. - If the acknowledgement does not arrive within three seconds, the host rolls all of it back: cipher, local record, state file.
Step 2 before step 3 is deliberate. If the relay disconnected viewers first and the host swapped afterwards, there would be a window where a viewer reconnecting with the old password could still push input into the process. Doing it in this order means the worst an old URL and password can do is reach an anonymous relay socket that cannot authenticate a single frame.
When the machine is linked to an account, the CLI re-registers the session with a credential_rotation flag. The accounts service, in one transaction, verifies the caller owns the session, updates the share URL, deletes every sealed copy of the old password, and stores the owner's new copy. That copy is sealed to the owner's vault key with ephemeral P-256 ECDH and AES-256-GCM, so the service replaces something it cannot read. Teammates' copies are simply gone until the owner shares again.
Removing a teammate is not revocation
0.12.2 also changes what happens when you remove someone from a team. The Postgres store now deletes every session-password copy sealed to that account, in the same transaction that deletes the membership. The changelog lists that deletion as new in this release.
That fixes the service handing out something it should not. It does not fix the thing people usually mean by "revoke". A password the removed member's browser already opened is in that browser. Terminal output they already received is on their screen. The security policy in the repo says so in plain words: removing a member "cannot make them forget a password or terminal output they already received", and the help text for the rotate command ends with "Rotation cannot erase terminal output somebody already saw."
So the honest procedure for an agent, or a person, you no longer trust in a shared session has two steps. Remove them, which stops future distribution. Then rotate each active session they could open, which cuts the live connection and invalidates what they hold. The first step without the second is bookkeeping.
Why this is not a checkbox
The bill's phrase "at any time" hides a design constraint. Revocation that works at any time has to work while the process is mid-output, while a viewer is mid-keystroke, and while the relay in between is deliberately blind to the secret being revoked. That forces the boundary into the endpoint that holds the key, which in our case is the host process, and it forces an ordering on the three parties involved.
GreyNoise's operator had none of that. Their agents were credentialed, unattended, and unrevocable in practice. A terminal is a smaller surface than an Active Directory domain, but it is where most coding agents actually run, and the shell.online source is one place where you can read what "revoke at any time" costs to implement properly. The same question is going to come up for every credential an agent carries, which is a large part of why we build identity into the network layer at Pilot Protocol rather than leaving it to each application.
Two smaller things in the same release. --auto-close today never worked, because a bare day keyword resolved to midnight, which had always already passed. It now means the end of today, fixed by an outside contributor making their first contribution to the repo. And the relay is now available as a standalone single-node image for ordinary Docker hosts, with no Cloudflare account required.
Top comments (0)