Agentic Covenants

Respond (RS) · Identity

Identity at the server side layer

external · Outside the agent entirely

How do I stop the bleeding now?

What this cell does

Revoke OIDC token at IdP, disable ServiceAccount, rotate IAM keys, attach deny-all IAM policy, invalidate all active sessions.

Artifacts (2)

agent-revoke-serverview on GitHub
#!/usr/bin/env bash
# ABOUTME: Server-side identity-revocation runbook. Disables SA, force-deletes pods, attaches IAM deny-all, revokes OIDC sessions.
# ABOUTME: Pre-authorized at on-call level. Reversible. Pre-stage iam-deny-all.json under /etc/agents/emergency/ before relying on this script.

set -euo pipefail

if [[ $# -lt 1 ]]; then
  echo "Usage: agent-revoke-server <AGENT_NAME>" >&2
  exit 64
fi

AGENT_NAME="$1"
NAMESPACE="agent-${AGENT_NAME}"
INCIDENT_ID="$(uuidgen 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())')"
EMERGENCY_DIR="${EMERGENCY_DIR:-/etc/agents/emergency}"

# 1. Disable ServiceAccount automount (existing pods keep their tokens until restart).
kubectl patch sa -n "$NAMESPACE" claude-code \
  --type='merge' \
  -p '{"automountServiceAccountToken":false}' 2>/dev/null || \
  echo "WARN: kubectl patch failed; SA may not exist in $NAMESPACE" >&2

# 2. Delete all pods in the namespace to force token re-mount (which will fail).
# Background so we can continue with IAM revocation in parallel.
kubectl delete pods -n "$NAMESPACE" --all --force --grace-period=0 2>/dev/null &
KUBE_PID=$!

# 3. Attach IAM deny-all policy.
if [[ -r "$EMERGENCY_DIR/iam-deny-all.json" ]]; then
  aws iam put-role-policy \
    --role-name "$AGENT_NAME" \
    --policy-name "EmergencyDenyAll-${INCIDENT_ID}" \
    --policy-document "file://$EMERGENCY_DIR/iam-deny-all.json" 2>/dev/null || \
    echo "WARN: aws iam put-role-policy failed" >&2
else
  echo "WARN: $EMERGENCY_DIR/iam-deny-all.json not found; pre-stage this file before depending on this runbook." >&2
fi

# 4. Revoke OIDC tokens at the IdP. Provider-specific; Okta example below.
# Set OKTA_API_TOKEN, OKTA_DOMAIN in the operator's break-glass env before
# invoking. For other IdPs (Auth0, Keycloak, Dex), substitute the equivalent
# session-clear API call.
if [[ -n "${OKTA_DOMAIN:-}" && -n "${OKTA_API_TOKEN:-}" ]]; then
  USER_ID=$(curl -sS "https://${OKTA_DOMAIN}/api/v1/users?q=${AGENT_NAME}" \
    -H "Authorization: SSWS $OKTA_API_TOKEN" \
    -H "Accept: application/json" | jq -r '.[0].id // empty')
  if [[ -n "$USER_ID" ]]; then
    curl -sS -X DELETE "https://${OKTA_DOMAIN}/api/v1/users/${USER_ID}/sessions" \
      -H "Authorization: SSWS $OKTA_API_TOKEN" \
      -H "Accept: application/json" >/dev/null
  fi
fi

# 5. Wait for pod deletion to complete before declaring done.
wait "$KUBE_PID" 2>/dev/null || true

# 6. Log to cluster events and to syslog.
kubectl create event --namespace "$NAMESPACE" \
  --type=Warning \
  --reason=IdentityRevoked \
  --message="Emergency identity revocation for $AGENT_NAME, incident $INCIDENT_ID" 2>/dev/null || true

logger -t agent-incident -p user.warning \
  "$(jq -n \
      --arg event "identity_revoked_server" \
      --arg agent "$AGENT_NAME" \
      --arg incident "$INCIDENT_ID" \
      --arg actor "$(whoami)" \
      --arg ts "$(date -Iseconds)" \
      '{event:$event, agent:$agent, incident:$incident, actor:$actor, ts:$ts}')"

echo "Server-side identity revoked for $AGENT_NAME (incident $INCIDENT_ID)"
iam-deny-all.jsonview on GitHub
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "EmergencyDenyAll",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*"
    }
  ]
}

Cell notes

Interventions, Identity / Server-side

Trigger. Same as client-side, plus indicators that the agent has cluster or cloud reach.

Authority. On-call, no second approval. Reversible.

Speed target. Under 30 seconds plus token TTL window.

Tooling

  • - kubectl with permission to patch ServiceAccounts and delete Pods in agent namespaces.
  • - AWS CLI (or GCP/Azure equivalent) with permission to attach IAM policies.
  • - IdP API access for OIDC session revocation (Okta/Auth0/Keycloak/Dex).

Files in this directory

  • - agent-revoke-server, runbook script. Disables ServiceAccount automount, force-deletes pods to break token mounts, attaches an IAM Deny * policy to the agent role, terminates active OIDC sessions.
  • - iam-deny-all.json, pre-staged IAM policy document used by the runbook. Deny on . Pre-stage this file at /etc/agents/emergency/iam-deny-all.json.

Verification


# 1. SA automount disabled
kubectl get sa -n agent-claude-code-prod claude-code -o jsonpath='{.automountServiceAccountToken}'
# expected: false

# 2. Pods recreated and failing to mount token
kubectl get pods -n agent-claude-code-prod
# expected: error/CrashLoopBackOff state, unable to start

# 3. IAM deny-all attached
aws iam list-role-policies --role-name claude-code-prod | grep EmergencyDenyAll

# 4. OIDC sessions terminated (provider-specific verification)

Common mistakes

  • - Forgetting that existing pods retain their mounted tokens until restart. Step 2 (pod deletion) is mandatory.
  • - Token TTL of 1 hour means deny-policy is the only fast cutoff; revocation alone leaves a window.
  • - Deleting the IAM role instead of attaching a deny policy. Deletion can fail if other resources reference the role; deny policy is faster and reversible.
  • - Not testing the IdP API path before the incident. The emergency is the wrong time to learn that your Okta API token expired.

Citation

NIST CSF 2.0 RS.MI-01, RS.MI-02; PR.AA-01 (response dimension). NIST SP 800-63B Rev. 4 (credential lifecycle). NIST SP 800-207 (Zero Trust). OWASP ASI03, ASI10. NIST AI RMF MANAGE 4.1.

Primary failure modes

Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.

  • JWT TTL window remains valid until expiry
  • existing pods retain mounted tokens until restart

Crosswalk

NIST CSF 2 0RS.MI-01, RS.MI-02, PR.AA-01
NIST AI RMFMANAGE 4.1
OWASP AGENTICASI03, ASI10
OTHERNIST SP 800-63B Rev. 4, NIST SP 800-207