Agentic Covenants

Protect (PR) · Identity

Identity at the client side layer

deterministic · Outside the model's reasoning

If the agent decides to violate this concern, what stops it at this layer?

What this cell does

Per-agent credentials, no shared keys, filesystem ACLs.

Artifacts (3)

claude-code-prod.serviceview on GitHub
# ABOUTME: systemd unit binding the Claude Code agent to a dedicated user with a per-agent credential.
# ABOUTME: The credential file in EnvironmentFile is read by systemd as root; the agent process never reads it at rest.

[Unit]
Description=Claude Code agent (production)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=agent-runner
Group=agent-runner

# Read the per-agent credentials. systemd reads this as root before the
# User= switch; the agent process inherits the env vars but never sees the
# file directly (the filesystem ACL from provision-credential.sh denies the
# agent's own user read access).
EnvironmentFile=/etc/agents/claude-code-prod/env

# Hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=true
PrivateDevices=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
ReadWritePaths=/var/lib/agents/claude-code-prod /workspace

# The agent gets a working directory it can write to; everything else is
# read-only or hidden via ProtectSystem and ProtectHome.
WorkingDirectory=/workspace

ExecStart=/usr/local/bin/claude --config /etc/agents/claude-code-prod/config.json

# Restart on crash, but rate-limit so a crash loop doesn't burn API quota.
Restart=on-failure
RestartSec=30s
StartLimitIntervalSec=300
StartLimitBurst=3

[Install]
WantedBy=multi-user.target
provision-credential.shview on GitHub
#!/usr/bin/env bash
# ABOUTME: Provisions a per-agent credential file with operator-owned ACLs.
# ABOUTME: The agent's own user cannot read the file at rest; the operator launches with it in env.

set -euo pipefail

AGENT_NAME="${1:-}"
AGENT_USER="${2:-agent-runner}"
OPERATOR_GROUP="${3:-operators}"

if [[ -z "$AGENT_NAME" ]]; then
  cat <<'USAGE' >&2
Usage: provision-credential.sh <agent-name> [agent-user] [operator-group]

Example:
  AGENT_TOKEN="$(vault read -field=token agents/claude-code-prod)" \
    provision-credential.sh claude-code-prod agent-runner operators
USAGE
  exit 64
fi

if [[ "$EUID" -ne 0 ]]; then
  echo "Must run as root (uses chown and setfacl)." >&2
  exit 1
fi

if [[ -z "${AGENT_TOKEN:-}" ]]; then
  echo "AGENT_TOKEN must be set in the environment." >&2
  exit 1
fi

CRED_DIR="/etc/agents/${AGENT_NAME}"
CRED_FILE="${CRED_DIR}/token"
ENV_FILE="${CRED_DIR}/env"

mkdir -p "$CRED_DIR"
chown "root:${OPERATOR_GROUP}" "$CRED_DIR"
chmod 0750 "$CRED_DIR"

umask 0177
printf '%s\n' "$AGENT_TOKEN" > "$CRED_FILE"
chown "root:${OPERATOR_GROUP}" "$CRED_FILE"
chmod 0640 "$CRED_FILE"

# Render env file consumed by the systemd unit. The systemd unit reads this
# at agent launch and exports ANTHROPIC_API_KEY into the agent's environment.
# The agent process itself cannot read /etc/agents/<name>/env at rest.
cat > "$ENV_FILE" <<EOF
ANTHROPIC_API_KEY=${AGENT_TOKEN}
AGENT_NAME=${AGENT_NAME}
EOF
chown "root:${OPERATOR_GROUP}" "$ENV_FILE"
chmod 0640 "$ENV_FILE"

# ACL: the agent's own user is denied read on the credential file. The
# operator (or systemd, running as root) reads it at launch. A compromised
# agent process cannot re-read the file from disk.
if command -v setfacl >/dev/null 2>&1; then
  setfacl -m "u:${AGENT_USER}:---" "$CRED_FILE"
  setfacl -m "u:${AGENT_USER}:---" "$ENV_FILE"
else
  echo "WARN: setfacl not available; relying on group-based denial only." >&2
fi

echo "Provisioned ${CRED_DIR}"
echo "Credential fingerprint: $(printf '%s' "$AGENT_TOKEN" | sha256sum | cut -d' ' -f1 | head -c 16)"
unset AGENT_TOKEN
verify.shview on GitHub
#!/usr/bin/env bash
# ABOUTME: Verifies per-agent credential hygiene: not in process listing, not readable by agent, unique per agent.
# ABOUTME: Run as root. Exits non-zero on any failed check.

set -euo pipefail

AGENT_USER="${AGENT_USER:-agent-runner}"
AGENTS_DIR="${AGENTS_DIR:-/etc/agents}"
PASS=0
FAIL=0

check() {
  local name="$1"
  local result="$2"
  if [[ "$result" == "ok" ]]; then
    echo "PASS: $name"
    PASS=$((PASS + 1))
  else
    echo "FAIL: $name -- $result"
    FAIL=$((FAIL + 1))
  fi
}

# 1. Credential not in process listing
if ps -eo args | grep -E 'ANTHROPIC_API_KEY=[^ ]+' | grep -v grep >/dev/null; then
  check "credential not in process listing" "found ANTHROPIC_API_KEY=... in args"
else
  check "credential not in process listing" "ok"
fi

# 2. Agent cannot read its own credential file at rest
shopt -s nullglob
for cred in "$AGENTS_DIR"/*/token; do
  if sudo -u "$AGENT_USER" cat "$cred" >/dev/null 2>&1; then
    check "agent cannot read $cred" "agent user $AGENT_USER read it successfully"
  else
    check "agent cannot read $cred" "ok"
  fi
done

# 3. Unique credential per agent
TOKENS=$(find "$AGENTS_DIR" -maxdepth 2 -name token -type f 2>/dev/null)
if [[ -z "$TOKENS" ]]; then
  check "per-agent credentials provisioned" "no tokens found in $AGENTS_DIR"
else
  HASHES=$(echo "$TOKENS" | xargs -r md5sum | awk '{print $1}' | sort)
  UNIQUE=$(echo "$HASHES" | uniq | wc -l)
  TOTAL=$(echo "$HASHES" | wc -l)
  if [[ "$UNIQUE" -eq "$TOTAL" ]]; then
    check "per-agent credentials are unique ($TOTAL agents)" "ok"
  else
    check "per-agent credentials are unique" "$((TOTAL - UNIQUE)) duplicate(s) detected"
  fi
fi

echo ""
echo "Summary: $PASS passed, $FAIL failed"
exit "$FAIL"

Cell notes

Identity / Client-side

Control. Per-agent credentials in operator-owned config; no shared keys; filesystem ACLs preventing cross-agent credential access.

Strength. Deterministic when the operator-owned config is uncompromised. Bypassable through token theft from logs, process listings, or world-readable env files; through credential reuse if the operator copies the config; through agent processes that run as a privileged user.

Tooling

  • - A secrets manager (HashiCorp Vault, AWS Secrets Manager, GCP Secret Manager, 1Password CLI, or pass for solo work).
  • - setfacl (Linux) or chmod plus dedicated user accounts (macOS).
  • - An OIDC-issuing IdP (Okta, Auth0, Azure AD, Keycloak) for token-based credentials.
  • - systemd (Linux) or launchd (macOS) to bind the credential to the agent process.

Files in this directory

  • - provision-credential.sh, creates a per-agent credential directory, locks down ownership, applies an ACL so the agent's own user cannot read the file at rest. Run as root.
  • - claude-code-prod.service, systemd unit that loads the per-agent env file and runs the agent as a dedicated user. Drop in /etc/systemd/system/.
  • - verify.sh, three checks: credential not in process listing, agent cannot read its own credential file at rest, every agent has a unique credential hash.

Verification

Run verify.sh. All three checks must pass.


sudo ./verify.sh

Common mistakes

  • - Using a single ~/.anthropic/credentials for every agent on the box.
  • - Putting the token in a .env file checked into the repo.
  • - Setting the env var in /etc/profile, where every process inherits it.
  • - Logging the credential into the agent's own log file at startup.
  • - Granting the agent's own user write permission on /etc/agents/ so the agent can rotate its own key, that ability lets the agent change its identity at will.

Citation

NIST CSF 2.0 PR.AA-01, PR.AA-03. NIST SP 800-207 (Zero Trust Architecture). NIST SP 800-63 Rev. 4 (Digital Identity Guidelines).

Primary bypasses

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

  • token theft
  • credential leakage in logs

Crosswalk

NIST CSF 2 0PR.AA-01, PR.AA-03
NIST AI RMFMANAGE 2.4
OWASP LLMLLM02
OWASP AGENTICASI03
OTHERNIST SP 800-207, NIST SP 800-63