Agentic Covenants

Respond (RS) · Authorization

Authorization at the client side layer

deterministic · Outside the model's reasoning

How do I stop the bleeding now?

What this cell does

Force-replace local hook config with deny-all, lock with chattr +i, kill running agent.

Artifacts (3)

agent-deny-all-localview on GitHub
#!/usr/bin/env bash
# ABOUTME: Local authorization-shrink runbook. Replaces hook + settings with deny-all, sets chattr +i, kills agent.
# ABOUTME: Pre-stage deny-all-hook.sh and settings-deny-all.json under /etc/agents/emergency/ before depending on this runbook.

set -euo pipefail

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

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

# Sanity-check pre-staged artifacts. Refuse if missing rather than silently
# leaving a half-applied state.
for src in "$EMERGENCY_DIR/deny-all-hook.sh" "$EMERGENCY_DIR/settings-deny-all.json"; do
  if [[ ! -r "$src" ]]; then
    echo "REFUSING: pre-staged artifact missing: $src" >&2
    echo "Pre-stage emergency artifacts before relying on this runbook." >&2
    exit 1
  fi
done

# 1. If hook file currently has the immutable bit set (from a prior incident),
# clear it before overwriting.
chattr -i /etc/agents/hooks/pre_tool_use.sh 2>/dev/null || true

# 2. Replace hook config with deny-all template.
cp "$EMERGENCY_DIR/deny-all-hook.sh" /etc/agents/hooks/pre_tool_use.sh
chmod 0755 /etc/agents/hooks/pre_tool_use.sh
chown root:root /etc/agents/hooks/pre_tool_use.sh

# 3. Make the hook config immutable so the agent (and any compromised
# process running as the agent's user) cannot edit it back.
if ! chattr +i /etc/agents/hooks/pre_tool_use.sh 2>/dev/null; then
  echo "WARN: chattr not supported on this filesystem; immutability skipped" >&2
fi

# 4. Replace settings.json with the deny-all version.
cp "$EMERGENCY_DIR/settings-deny-all.json" "/etc/agents/$AGENT_NAME/settings.json"
chmod 0644 "/etc/agents/$AGENT_NAME/settings.json"

# 5. Kill any in-flight agent so the new state applies on next launch.
pkill -KILL -f "claude.*$AGENT_NAME" 2>/dev/null || true

logger -t agent-incident -p user.warning \
  "$(jq -n \
      --arg event "authorization_locked_local" \
      --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 "Local authorization locked down for $AGENT_NAME (incident $INCIDENT_ID)"
deny-all-hook.shview on GitHub
#!/usr/bin/env bash
# ABOUTME: Pre-staged emergency PreToolUse hook. Always denies. Applied during incident response.
# ABOUTME: Pre-stage at /etc/agents/emergency/deny-all-hook.sh; agent-deny-all-local copies it into place.

# Read stdin to drain the hook input even though we are not using it. Some
# agent runtimes block on stdin not being consumed.
cat >/dev/null

echo "BLOCKED: agent in emergency lockdown" >&2
exit 2
settings-deny-all.jsonview on GitHub
{
  "_comment": "Emergency lockdown. dontAsk auto-denies everything that would prompt; the empty allow list leaves nothing pre-approved; deny [*] blocks every tool outright in every mode. Deny rules and the PreToolUse hook below both still apply even if someone re-enters a looser mode, which is the point.",
  "permissions": {
    "defaultMode": "dontAsk",
    "allow": [],
    "ask": [],
    "deny": ["*"]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "/etc/agents/hooks/pre_tool_use.sh"
          }
        ]
      }
    ]
  }
}

Cell notes

Interventions, Authorization / Client-side

Trigger. Sentinels alert: hook decision spike (multiple denies in short window suggesting probing); hook config tampering detected; auditd watch fires on /etc/agents/hooks/.

Authority. On-call, no second approval.

Speed target. Under 5 seconds.

Tooling

Files in this directory

  • - agent-deny-all-local, runbook script. Replaces the hook with the deny-all template, sets chattr +i, replaces settings.json with the deny-all version, kills any in-flight agent so the new state applies.
  • - deny-all-hook.sh, pre-staged hook template. Always returns exit code 2 with a "BLOCKED: agent in emergency lockdown" message. Pre-stage at /etc/agents/emergency/deny-all-hook.sh.
  • - settings-deny-all.json, pre-staged Claude Code settings with defaultMode: deny, empty allow/ask, deny *. Pre-stage at /etc/agents/emergency/settings-deny-all.json.

Verification


# 1. Hook is the deny-all version (md5 must match)
md5sum /etc/agents/hooks/pre_tool_use.sh /etc/agents/emergency/deny-all-hook.sh

# 2. Immutable bit set
lsattr /etc/agents/hooks/pre_tool_use.sh
# expected: 'i' attribute present

# 3. New agent launch is blocked
sudo -u agent-runner /usr/local/bin/claude --print "test" 2>&1 | grep "emergency lockdown"

Common mistakes

  • - chattr +i does not work on every filesystem (tmpfs, NFS, FAT). Verify your /etc/agents/ mount.
  • - Pre-staged emergency template not in source control means you discover the typo during the incident.
  • - Forgetting to kill the running agent, the new hook applies only to new sessions.

Citation

NIST CSF 2.0 RS.MI-01, RS.MI-02. NIST AI RMF MANAGE 4.1. OWASP ASI02, ASI05.

Primary failure modes

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

  • chattr +i not supported on tmpfs/NFS/FAT
  • in-flight agent uses old hook until killed

Crosswalk

NIST CSF 2 0RS.MI-01, RS.MI-02
NIST AI RMFMANAGE 4.1
OWASP AGENTICASI02, ASI05