Agentic Covenants

Respond (RS) · Supply chain

Supply chain at the client side layer

deterministic · Outside the model's reasoning

How do I stop the bleeding now?

What this cell does

Remove suspect MCP server from allowlist, quarantine downloaded packages to restricted location, lock lockfile, pin runtime to last-known-good, kill agent.

Artifacts (1)

agent-quarantine-supply-chain-localview on GitHub
#!/usr/bin/env bash
# ABOUTME: Local supply-chain quarantine runbook. Allowlist removal, package quarantine, lockfile lock, runtime pin, kill.
# ABOUTME: Pre-stage last-known-good-runtime-version.txt under /etc/agents/emergency/ before relying on this runbook.

set -euo pipefail

if [[ $# -lt 2 ]]; then
  echo "Usage: agent-quarantine-supply-chain-local <AGENT_NAME> <SUSPECT_PACKAGE>" >&2
  echo "  SUSPECT_PACKAGE is the name as it appears in mcp-allowlist.json or installed module dirs." >&2
  exit 64
fi

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

# 1. Remove suspect MCP server from allowlist.
if [[ -r "$ALLOWLIST" ]]; then
  TMPFILE="$(mktemp)"
  jq --arg name "$SUSPECT" 'del(.servers[$name])' "$ALLOWLIST" > "$TMPFILE"
  mv "$TMPFILE" "$ALLOWLIST"
  chmod 0644 "$ALLOWLIST"
fi

# 2. Quarantine downloaded packages by moving them to a restricted location.
QUARANTINE="/var/quarantine/${INCIDENT_ID}"
mkdir -p "$QUARANTINE"
chmod 0700 "$QUARANTINE"

for path in \
    "/usr/local/lib/node_modules/$SUSPECT" \
    "/usr/lib/python3/dist-packages/$SUSPECT" \
    "/usr/local/lib/python3/dist-packages/$SUSPECT" \
    "/home/agent-runner/.local/share/mcp/$SUSPECT" \
    "/home/agent-runner/.npm/_cacache/$SUSPECT"; do
  if [[ -e "$path" ]]; then
    mv "$path" "$QUARANTINE/" 2>/dev/null || \
      echo "WARN: could not move $path to quarantine" >&2
  fi
done

# 3. Lock lockfiles against further changes.
for lockfile in \
    "/etc/agents/$AGENT_NAME/package-lock.json" \
    "/etc/agents/$AGENT_NAME/requirements.txt" \
    "/etc/agents/$AGENT_NAME/Pipfile.lock" \
    "/etc/agents/$AGENT_NAME/poetry.lock" \
    "/etc/agents/$AGENT_NAME/go.sum" \
    "/etc/agents/$AGENT_NAME/Cargo.lock"; do
  [[ -f "$lockfile" ]] && chattr +i "$lockfile" 2>/dev/null || true
done

# 4. Pin agent runtime to last-known-good.
if [[ -r "$EMERGENCY_DIR/last-known-good-runtime-version.txt" ]]; then
  cp "$EMERGENCY_DIR/last-known-good-runtime-version.txt" \
     "/etc/agents/$AGENT_NAME/runtime-version.txt"
else
  echo "WARN: $EMERGENCY_DIR/last-known-good-runtime-version.txt not found; runtime pin skipped" >&2
fi

# 5. Kill the agent so it picks up the new state on restart.
pkill -KILL -f "claude.*$AGENT_NAME" 2>/dev/null || true

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

echo "Local supply chain quarantined for $AGENT_NAME (suspect: $SUSPECT, quarantine: $QUARANTINE, incident: $INCIDENT_ID)"

Cell notes

Interventions, Supply chain / Client-side

Trigger. MCP allowlist violation, tool-description hash mismatch, lockfile diff with unsigned package, pre-commit dependency scan finding.

Authority. On-call.

Speed target. Under 60 seconds.

Tooling

Files in this directory

  • - agent-quarantine-supply-chain-local, runbook script. Removes the suspect server from mcp-allowlist.json, moves suspect package files to /var/quarantine/<incident>/, sets chattr +i on lockfiles, pins runtime to last-known-good, kills the agent so the new state applies.

Verification


# 1. Suspect removed from allowlist
jq '.servers | has("filesystem-bad")' /etc/agents/mcp-allowlist.json
# expected: false

# 2. Quarantine populated
ls -la /var/quarantine/

# 3. Lockfile immutable
lsattr /etc/agents/claude-code-prod/package-lock.json
# expected: 'i' attribute present

Common mistakes

  • - Quarantining files but missing in-memory state, Python with already-imported modules keeps the malicious code loaded. The kill step is mandatory.
  • - chattr +i on a file in tmpfs, does not stick across reboot. Mitigate by ensuring lockfile lives on persistent FS.
  • - Forgetting that the agent may have credentials cached in OS keychain that the quarantined package planted.

Citation

NIST CSF 2.0 RS.MI-01, RS.MI-02. NIST AI RMF MANAGE 3.1. OWASP ASI04, ASI06. OWASP MCP04, MCP09.

Primary failure modes

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

  • in-memory state retains malicious imports until restart
  • chattr +i not durable on tmpfs
  • keychain credentials planted by package survive

Crosswalk

NIST CSF 2 0RS.MI-01, RS.MI-02
NIST AI RMFMANAGE 3.1
OWASP AGENTICASI04, ASI06
OWASP MCPMCP04, MCP09