The coding part is mostly solved. An agent can branch, patch, write the tests, open the PR. Then the change has to land on an actual server — a ser...
For further actions, you may consider blocking this person and/or reporting abuse
For the prior-art question, the cleanest pattern I have seen is CI workload identity rather than something agent-specific: an OIDC identity for one run is exchanged at a broker (for example Vault's SSH secrets engine) for an OpenSSH certificate with a minutes-long TTL. Claims such as repository, environment, run ID, and actor map to allowed principals and hosts; certificate critical options can further constrain source address or force a command.
The extra agent-specific edge is plan drift. I would bind the capability to an immutable plan hash plus the artifact digest and command class, then re-evaluate each tool call instead of authorizing the whole conversation. If the model changes the plan, it needs a new capability. Single-use nonces and replay checks matter too. That makes human approval mean “approve this exact deployment plan,” not the much broader “allow SSH.”
That's the reference I was reaching for, thanks - the OIDC->broker->short-TTL cert flow (Vault's SSH secrets engine being the clean example) is exactly the human-CI prior art, and mapping claims to principals is what makes it scope cleanly. The gap I keep hitting: a CI job has a fixed plan (the pipeline), so "one identity per run" maps neatly. An agent's "run" is open-ended - which is exactly your plan-drift point.
Binding to an immutable plan hash + artifact digest + command class, re-evaluated per tool call, is the sharpest framing I've seen. The part I'd stress-test is what counts as "the plan" when the agent legitimately needs to adapt (the log shows something unexpected, so the fix changes): too tight and you re-prompt the human on every deviation; too loose and the hash stops meaning anything. Feels like the plan wants to be a bounded envelope (these hosts, these command classes, this artifact) with re-approval only on envelope escape, not a literal command list.
Single-use nonces + replay-after-revocation testing - 100%, and it's the check everyone skips. One thing I'm still chewing on: do you bind at the certificate layer (critical options / force-command) or above it in the broker's policy? Curious where you'd draw that line.
I'd split the responsibility. Put the independently enforceable baseline at the certificate layer: principal, host/user scope, short expiry, and source constraints. Keep the adaptive envelope — artifact digest, command classes, approval state, and nonce consumption — in the broker policy.
force-commandis useful when there is one stable entry point, but it becomes too blunt for an agent that legitimately adapts.Then treat an agent run as a chain of small authorizations: validate live preconditions, issue a fresh short-TTL cert for the next bounded step, and require re-approval only when the step leaves the envelope. That preserves a fail-closed SSH boundary without forcing every valid adaptation into certificate semantics.
The edge I'd test hardest is TOCTOU: a cert can be issued for state A and used after the target changes. A server-side shim may still need to consume the nonce and re-check the digest immediately before exec. So my line would be: coarse, independently verifiable constraints in the cert; stateful and adaptive constraints in the broker, with a final check at execution.
This is the cleanest articulation of that split I've seen, and it's where we landed too: the cert carries only what's independently verifiable without external state (principal, host, short expiry, source), and everything stateful (digest, command class, approval, nonce) lives in the broker. Push adaptive state into the cert and you're re-minting on every micro-decision, which defeats the bounded-step idea.
TOCTOU is the right thing to hammer on, and "a final check at execution" is exactly where it gets uncomfortable. If a server-side shim has to re-check the digest and consume the nonce immediately before exec, the target has to reach the broker (or carry a broker-signed, exec-time token) in the hot path - which puts the broker on the critical path for every command and hands you a new availability/latency dependency right where you least want one.
The two ways I keep circling: (a) a ForceCommand/PAM shim that calls the broker synchronously and fails closed, vs (b) a short-enough cert TTL that the window is "small enough" plus an idempotent/append-only target so a stale re-exec is harmless - trading a hard guarantee for a probabilistic one. Feels like it comes down to whether the protected action is idempotent: if it is, squeeze the window; if it isn't, you're stuck paying for the synchronous check. Which way would you lean for the common deploy case?
For the common deploy case I'd take a third path: keep the broker off the hot path, but don't rely on TTL alone. Have it mint a single-use, signed exec token bound to the host, principal, artifact digest, operation, expected current release, nonce, and a very short expiry. The target shim verifies that locally, records the nonce durably, stages the content-addressed artifact, then performs an atomic compare-and-swap of the release pointer.
That makes the usual deploy idempotent and turns stale state into a failed precondition rather than a probabilistic window. The broker is needed to authorize the step, not to execute it. For irreversible operations that can't be reduced to an atomic/idempotent primitive — schema changes are the obvious one — I'd pay for the synchronous fail-closed broker check, and probably explicit approval.
So my split would be: local signed token + replay protection + CAS for normal deploys; synchronous broker for the small non-idempotent tail.
For the common deploy case, I'd use both, but not symmetrically. Make staging content-addressed and idempotent: upload to an immutable release path, verify the digest there, then prepare everything short of activation. Retries in that phase are harmless.
Keep the synchronous, fail-closed broker check only at the non-idempotent commit point: switching the current symlink, changing traffic, restarting against the new artifact, or running a migration. That keeps the broker out of every shell command while preserving a hard guarantee where stale authorization actually matters. The cert TTL then limits ambient access; it is not carrying the correctness argument by itself.
If the broker is unavailable, staging can continue but activation waits. I would treat schema migrations as a separate capability with an explicit precondition (expected schema version) and a one-time token, rather than bundling them into the general deploy envelope.
One gap in the broker model as usually implemented: it secures the deploy step but assumes the artifact being deployed is already trustworthy. In practice the agent wrote that artifact too, so the credential-custody problem and the 'did the agent's own code introduce a vuln' problem are separate and both need solving — a perfectly scoped short-TTL cert still happily deploys a broken auth check the same agent just wrote five minutes earlier. Feels like the broker belongs right after a gate that actually reads the diff, not just signs it.
Completely fair, and it's a distinction we're careful not to blur: custody secures the channel (can this agent reach prod, as whom, for how long) - it says nothing about whether the payload is correct. Two orthogonal trust problems, "can it act" vs "is what it's shipping any good," and solving the first doesn't touch the second.
Where they connect is the artifact digest. Bind the capability to a specific digest and the broker can enforce "only deploy the exact artifact that cleared the gate" - but it cannot BE the gate. The gate is the boring stuff that has to hold regardless of who wrote the code: tests, a human or a second model reading the diff, SAST, a canary that watches error rates before full rollout. The agent authoring the artifact just means you cannot skip any of it on the theory that "a person wrote this, so it is probably fine."
So I would put it exactly where you did: broker right after a gate that reads the diff, cert bound to the digest that gate approved. Custody stops the agent deploying to the wrong place or as the wrong identity; the review gate stops it deploying wrong code. You need both - conflating them is how you end up with a beautifully scoped cert shipping a broken auth check.
Binding the cert to the digest that cleared the gate is the right enforcement point, and it also means the gate doesn't need to be a full CI run to be useful. For agent-authored diffs specifically, the highest-signal checks are narrow: did this diff touch an authorization path, does a new query take a caller-supplied ID without a corresponding session check, did a service-role-equivalent credential move into a client-reachable file. Those are checkable in seconds and catch the exact failure mode the broker structurally can't see. A slower general SAST pass can run after, but the fast narrow gate is what keeps the broker from becoming a fig leaf.
That framing - the gate as a delta check on the trust boundary, not a correctness proof - is what makes it cheap enough to sit in the hot path. You're diffing the authz surface (did a caller-supplied ID reach a query without a session check, did a service-role cred cross into client-reachable code), not re-verifying the app. Semgrep-rule territory: seconds, per-diff, and it catches exactly the class the broker is blind to.
The pairing I like: the gate emits a verdict keyed to the artifact digest, and the broker only mints a cert for a digest that passed - so "reviewed" and "deployed" become the same object, not two hopeful steps.
For agent-authored diffs there's a nastier variant worth the narrow gate: make the agent declare which trust-boundary files it touched, and fail closed if the declared set doesn't match the actual diff. Catches the agent quietly editing an auth check it never mentioned.
The one place it stays a fig leaf: if the ruleset lags your own authz conventions - custom middleware, a new framework's session model - it green-lights the exact diff it should catch. So the fast gate has to encode your boundary patterns, not generic ones. Which loops back to your point: narrow and specific beats broad and slow, as long as "narrow" tracks the real boundary.
Yes - this is the synthesis I was fishing for, and "the broker authorizes the step, not executes it" is the line that makes it click. A single-use signed token carrying the expected current release turns the TOCTOU window into a failed precondition instead of a race, and CAS on the release pointer is what makes the "harmless retry" property real rather than aspirational. Reserving the synchronous fail-closed broker call for the small non-idempotent tail (activation, migrations) is exactly the right place to spend the availability cost.
The part I'd still poke at is "records the nonce durably" on the target. Per-target, crash-safe nonce storage is a small consistency problem in its own right - it has to survive the shim dying between "record nonce" and the CAS, or you either replay or wedge. Feels solvable by making the nonce burn and the swap commit-or-fail together: idempotency key = (nonce, digest) written in the same atomic step as the pointer CAS, or lean on the pointer store's own CAS to double as the replay guard.
Treating schema migrations as a separate capability with an expected-schema-version precondition + a one-time token is the cleanest framing I've seen for the irreversible tail - it stops the general deploy envelope from silently growing teeth. Genuinely good thread; you've basically speced the hard 20%.
The custodian model is the right direction. One boundary worth making explicit: in hosted mode the agent still holds a bearer API key, and 30–365 days is a long-lived capability even if it cannot exfiltrate SSH keys. A compromised context can still issue authorized actions through the broker. I’d treat that key as bootstrap only: exchange it for a per-run, minutes-lived capability bound to host(s), allowed tool or command class, deployment artifact digest/change ticket, max duration, and requester identity. Reauthorize at execution time, log denials, and test replay after revocation. That preserves key custody while also containing delegated authority—the remaining thing an attacker actually wants.
Sharp cut — thanks. You're right that the API key is the one standing bearer left in the hosted path. The SSH auth underneath is already a short-lived cert minted per run, but the key that authorizes the minting isn't, and "revocable within 30–365 days" is doing a lot of work in that sentence.
What's shipped today is partial containment, not the thing you're describing: keys are scoped to specific enrolled hosts, carry a per-key command policy, and hosted runs are rate-limited and time-boxed — so a leaked key can't reach an unlisted host or run outside its allowed command class. What it can still do is replay authorized actions inside that envelope until someone revokes it. Exactly your point.
The bootstrap→per-run exchange is where I want this to go: treat the API key as enrollment only, have it mint a minutes-lived capability bound to host + command class + a change/artifact reference + requester identity, reauthorized at execution time with denials logged. Custody stays the same; the delegated-authority blast radius drops to a single run. And +1 on testing replay after revocation — easy to skip, embarrassing to skip.
Have you seen this done cleanly for agents specifically? Most of the prior art I know is human-facing CI, and the agent case has a couple of extra sharp edges.