How to Revoke an AI Agent in Production
Revoking a compromised or misbehaving AI agent should take seconds, not hours. Here's the architecture behind instant agent revocation — and why rotating API keys is not enough.
At 14:47 on a Tuesday, your monitoring system flags an anomaly: an agent has initiated 47 Stripe charges in the past 90 seconds, all for amounts just under your per-transaction limit. The pattern matches a known prompt injection attack. You need to stop it — now.
What happens next depends entirely on your revocation architecture.
The Rotation Trap#
The most common response to a compromised agent is credential rotation: change the API key, restart the service. This approach has three critical problems:
Blast radius. The Stripe API key used by the compromised agent is probably shared across multiple agents, services, and environments. Rotating it breaks everything using that key — not just the compromised agent. You've turned a targeted incident into a broad service disruption.
Time to effect. Generating new credentials, updating them in your secrets manager, redeploying services, and verifying the fix takes 15–60 minutes in a well-run organization. In the scenario above, the agent can process hundreds more transactions in that window.
No forensic value. After rotation, the audit trail for the compromised agent's actions may be partially or fully lost, depending on your logging setup. You know that it misbehaved, but reconstructing exactly what it did becomes difficult.
Credential rotation is a blunt instrument. Agent revocation requires surgical precision.
The Architecture of Instant Revocation#
Effective agent revocation has three layers, each addressing a different aspect of the problem.
Layer 1: Agent Registry Revocation
The foundation of revocation is a central agent registry — a persistent store of agent identities and their current status. Each agent has a record:
{
"agent_id": "agt_01JXMK9P2Q3R4S5T6U7V",
"name": "checkout-assistant-v2",
"status": "active",
"public_key": "ed25519:MCowBQYDK2Vw...",
"policy_id": "pol_03",
"created_at": "2026-01-15T09:00:00Z",
"revoked_at": null
}
Revocation sets status to "revoked" and populates revoked_at. This is a millisecond operation. From this point forward, the verify gate refuses all requests from this agent ID.
kya agent revoke agt_01JXMK9P2Q3R4S5T6U7V \
--reason "prompt_injection_suspected" \
--incident-id "INC-2026-0205-001"
# Revoked: agt_01JXMK9P2Q3R4S5T6U7V
# All verify calls will now return DENY
# Revocation logged at: 2026-02-05T14:47:23.891Z
Layer 2: Capability Token Blacklist
Registry revocation stops the agent from getting new capability tokens. But what about tokens already issued?
If the agent received a 30-minute capability token at 14:30 and you revoke the agent at 14:47, that token is still valid for another 13 minutes — and any service validating tokens locally won't know about the revocation.
The solution is a token blacklist: a Redis set containing the jti (JWT ID) of every capability token issued to the revoked agent, with TTL matching the token's original expiry.
# On revocation, blacklist all active capability tokens
active_tokens = registry.get_active_tokens(agent_id)
pipeline = redis.pipeline()
for token in active_tokens:
ttl = token.exp - current_timestamp()
pipeline.setex(f"blacklist:{token.jti}", ttl, "revoked")
pipeline.execute()
Token validation now includes a blacklist check:
def validate_capability_token(token: str) -> ValidationResult:
claims = jwt.decode(token, kya_public_key)
# Check blacklist first — O(1) Redis lookup
if redis.exists(f"blacklist:{claims['jti']}"):
return ValidationResult(valid=False, reason="token_revoked")
return ValidationResult(valid=True, claims=claims)
The blacklist lookup adds microseconds of latency. In-flight tokens are invalidated within the Redis propagation time — typically under 50ms across a Redis cluster.
Layer 3: Policy Cache Invalidation
If you're running local verification mode — where policy decisions are evaluated against a Redis-cached policy snapshot — there's a third layer to address: nodes that have already cached the agent's policy as "active."
Cache invalidation broadcasts a revocation event to all nodes:
# Publish revocation to all verification nodes
redis.publish("agent:revocations", json.dumps({
"agent_id": "agt_01JXMK9P2Q3R4S5T6U7V",
"revoked_at": "2026-02-05T14:47:23.891Z",
"reason": "prompt_injection_suspected"
}))
Each node subscribes to this channel and immediately updates its local cache:
# On each verification node
def on_revocation_event(message):
agent_id = message["agent_id"]
local_cache.set(f"agent:{agent_id}:status", "revoked")
local_cache.set(f"agent:{agent_id}:deny_all", True)
After this broadcast, every node in the cluster denies all requests from the revoked agent — regardless of local cache state — within the Redis pubsub propagation time.
Time to Full Revocation#
With this three-layer architecture, full revocation happens in under 10 seconds:
| Layer | What it does | Time to effect |
|---|---|---|
| Registry revocation | New verify calls return DENY | ~100ms |
| Token blacklist | In-flight JWTs invalidated | ~500ms |
| Cache invalidation | Local verify nodes updated | 1–10 seconds |
Compare this to credential rotation, which typically takes 15–60 minutes and breaks unrelated services.
Capability Revocation: Surgical Precision#
Sometimes you don't need to revoke an agent entirely. You need to revoke a specific capability.
If an agent is authorized for create_charge and issue_refund, but you've detected anomalous refund behavior, you can revoke only the issue_refund capability — leaving the agent operational for its primary function:
kya capability revoke \
--agent agt_01JXMK9P2Q3R4S5T6U7V \
--action issue_refund \
--reason "anomalous_refund_pattern"
# Capability revoked: issue_refund for agt_01JXMK9P2Q3R4S5T6U7V
# create_charge remains active
This produces a targeted policy update — the agent's policy now explicitly denies issue_refund — without terminating the agent or disrupting its other functions.
The Incident Response Playbook#
A structured incident response workflow for a compromised agent:
T+0: Detection. Monitoring flags anomalous behavior — spike in transaction volume, unusual action patterns, policy violations logged in the audit trail.
T+30s: Suspend. Suspend the agent (not revoke — suspension stops new actions but preserves in-flight tokens for graceful completion of legitimate in-flight work):
kya agent suspend agt_01JXMK9P2Q3R4S5T6U7V \
--reason "investigating_anomaly"
T+2min: Investigate. Pull the audit log for the agent. The hash-chain integrity check confirms the log is unmodified. Review the decision trail — every ALLOW and DENY, the payload hash, the policy version, the state at decision time.
kya audit export \
--agent agt_01JXMK9P2Q3R4S5T6U7V \
--since "2026-02-05T14:00:00Z" \
--verify-chain # confirms hash chain integrity
T+5min: Decide. Based on the audit evidence, decide whether to:
- Revoke fully (confirmed compromise)
- Revoke specific capabilities (anomaly in one function)
- Restore to active (false positive)
T+6min: Revoke (if warranted). Full revocation, token blacklist, cache invalidation as described above.
T+10min: Deploy replacement. A new agent instance with a fresh Ed25519 keypair and verified policy can be deployed. The old agent's audit trail remains intact as forensic evidence.
Why the Audit Trail Survives Revocation#
A critical property of this architecture: revoking an agent does not delete its audit log. The hash-chained audit trail remains intact, immutable, and verifiable — providing forensic evidence of exactly what the compromised agent did, in what order, with what policy state at each decision.
This is not a given. If your "audit log" is application-level logging that lives on the same server as the agent, compromising the agent means potentially compromising the logs. A separate, append-only, hash-chained audit store is tamper-resistant by design: the agent never has write access to the audit log, only the verify gate does.
Proactive Revocation: Scheduled and Policy-Triggered#
Not all revocations are incident responses. Well-designed agent infrastructure supports proactive revocation patterns:
Time-bounded agents. Agents registered for a specific task can have a built-in expiry:
kya agent register \
--name "data-migration-agent" \
--pubkey ./agent.pub \
--expires-at "2026-02-06T00:00:00Z"
# Automatically revoked at midnight regardless of state
Spend-triggered revocation. An agent that exceeds its daily spend limit is automatically suspended pending review — not just denied the over-limit transaction.
Anomaly-triggered revocation. A policy can specify that exceeding a threshold of DENY decisions in a rolling window triggers automatic suspension:
auto_suspend:
condition: deny_rate_exceeds
threshold: 0.30 # 30% of requests denied
window: 5min
action: suspend_pending_review
Revocation is not just an incident response tool. It's an operational control that keeps agent behavior within expected bounds.
Related: What Is AI Agent Identity? · Agent Authorization vs Human IAM · AI Agents Are the New Root Users