Detect (DE) · Identity
Identity at the client side layer
deterministic · Outside the model's reasoning
If this concern is breached, how do we know?
What this cell does
PreToolUse hook emits structured identity events; auditd watches agent process startup; Vector/Fluent Bit ships to SIEM.
Artifacts (3)
auditd-agent.rulesview on GitHub# ABOUTME: auditd rules for agent process startup. Drop in /etc/audit/rules.d/ and reload with augenrules --load.
# ABOUTME: Watches by absolute path; if the agent binary is moved or symlinked elsewhere the rule misses, so re-audit periodically.
# Watch for execve of any agent binary. The -k tag is what audit search uses.
-a always,exit -F arch=b64 -S execve -F path=/usr/local/bin/claude -k agent_exec
-a always,exit -F arch=b64 -S execve -F path=/usr/local/bin/agent-bwrap -k agent_exec
-a always,exit -F arch=b64 -S execve -F path=/usr/local/bin/mcp-launch -k agent_exec
# Watch for credential file reads (ACL should already deny these to the agent
# user, but a rule on the file detects attempts).
-w /etc/agents/ -p ra -k agent_credential_access
# Watch for hook config edits.
-w /etc/agents/hooks/ -p wa -k hook_config_edit
-w /etc/agents/mcp-allowlist.json -p wa -k allowlist_edit
# Watch for --no-verify in any process arg (the agent attempting to bypass
# pre-commit). The short form -n is also caught by the second rule.
-a always,exit -F arch=b64 -S execve -F a1=*--no-verify* -k git_no_verify
-a always,exit -F arch=b64 -S execve -F a1=*-n* -k git_short_no_verify
identity-log-hook.shview on GitHub#!/usr/bin/env bash
# ABOUTME: PreToolUse hook that emits a structured JSON identity event to local syslog. Designed to chain ahead of pre_tool_use.sh.
# ABOUTME: Hashes the credential before logging; never logs the raw token.
set -euo pipefail
INPUT="$(cat)"
SESSION_ID="$(echo "$INPUT" | jq -r '.session_id // "unknown"')"
TOOL_NAME="$(echo "$INPUT" | jq -r '.tool_name // "unknown"')"
TOOL_INPUT="$(echo "$INPUT" | jq -r '.tool_input.command // ""')"
# Hash the credential. Truncate to 16 hex chars (64 bits) -- long enough that
# collisions across the agent population are negligible, short enough not to
# reveal the full hash in logs.
CRED_HASH="$(printf '%s' "${ANTHROPIC_API_KEY:-}" | sha256sum | cut -d' ' -f1 | head -c 16)"
EFFECTIVE_UID="$(id -u)"
HOSTNAME="$(hostname -f 2>/dev/null || hostname)"
TIMESTAMP="$(date -Iseconds)"
EVENT="$(jq -n \
--arg session "$SESSION_ID" \
--arg tool "$TOOL_NAME" \
--arg input "$TOOL_INPUT" \
--arg cred "$CRED_HASH" \
--argjson uid "$EFFECTIVE_UID" \
--arg host "$HOSTNAME" \
--arg ts "$TIMESTAMP" \
'{event: "identity", session: $session, tool: $tool, input: $input, cred_fingerprint: $cred, uid: $uid, host: $host, ts: $ts}')"
logger -t agent-sentinel -p user.info "$EVENT"
# Pass the original input through to the next hook in the chain. PreToolUse
# hooks are expected to echo the input for downstream consumers.
echo "$INPUT"
exit 0
vector.tomlview on GitHub# ABOUTME: Vector config that ingests local syslog and ships parsed agent-sentinel events to Elasticsearch.
# ABOUTME: Substitute siem.example.com and SIEM_TOKEN. Vector 0.40+ required for the parse_json! built-in used here.
[sources.syslog_local]
type = "syslog"
mode = "tcp"
address = "127.0.0.1:6514"
max_length = 102400
[transforms.parse_agent]
type = "remap"
inputs = ["syslog_local"]
source = '''
# Only process events tagged "agent-sentinel".
if .appname != "agent-sentinel" {
abort
}
parsed, err = parse_json(.message)
if err != null {
.parse_error = err
} else {
. = merge(., parsed)
}
.source = "agent-sentinel"
.ingested_at = now()
'''
# Drop events that failed to parse so we don't pollute the index. The parse
# error is logged at WARN by Vector's internal stats.
[transforms.drop_unparsed]
type = "filter"
inputs = ["parse_agent"]
condition = '''
exists(.event)
'''
[sinks.siem]
type = "elasticsearch"
inputs = ["drop_unparsed"]
endpoints = ["https://siem.example.com:9200"]
mode = "bulk"
bulk.index = "agent-sentinel-%Y.%m.%d"
[sinks.siem.auth]
strategy = "bearer"
token = "${SIEM_TOKEN}"
# Ship to a local file as a backstop in case the SIEM is unreachable.
# Vector buffers and replays automatically, but this gives us a last-resort
# audit trail on the operator host itself.
[sinks.local_archive]
type = "file"
inputs = ["drop_unparsed"]
path = "/var/log/agent-sentinel/%Y-%m-%d.jsonl"
encoding.codec = "json"
compression = "gzip"
Cell notes
Sentinels, Identity / Client-side
Control. PreToolUse hook emits structured identity events; auditd watches agent process startup; Vector or Fluent Bit ships to SIEM.
Strength. Deterministic when ships actually happen. Tamper-evident only if the SIEM endpoint is owned by the operator account, not the agent's.
Tooling
- -
auditd(Linux) oraudit_pipe(macOS). - - Vector 0.40+ or Fluent Bit 3.0+.
- - A SIEM (Splunk, Elastic, Loki, Datadog, Sumo Logic).
Files in this directory
- -
identity-log-hook.sh, PreToolUse hook that emits a structured JSON event with session ID, tool name, credential fingerprint (hash, never the token), effective UID, hostname, timestamp. - -
auditd-agent.rules, auditd watch rules for agent process startup. Drop in/etc/audit/rules.d/. - -
vector.toml, Vector config that parses syslog, enriches, and ships to Elasticsearch (substitute your SIEM endpoint).
Verification
# 1. Hook emits log on tool call
echo '{"session_id":"test","tool_name":"Read"}' | ./identity-log-hook.sh
journalctl -t agent-sentinel --since "1 minute ago"
# expected: structured JSON event with credential fingerprint, no raw token
# 2. Audit rule fires on agent start
sudo -u agent-runner /usr/local/bin/claude --version
ausearch -k agent_exec --start recent
# expected: execve event with UID and command line
# 3. Vector ships to SIEM
curl -sS https://siem.example.com:9200/agent-sentinel-*/_search?q=event:identity \
-H "Authorization: Bearer $SIEM_TOKEN" | jq '.hits.total.value'
# expected: non-zero count
Common mistakes
- - Logging the raw token instead of a hash. Anyone with SIEM access can replay the agent.
- - Hook ships logs to local file only. Agent with filesystem access edits the log.
- - Auditd rule on the binary path; if the agent moves the binary or runs from a different path, the rule misses. Use
auditctl -lto confirm rules are loaded. - - Credential fingerprint hash too short and collides across agents. Use at least 16 hex chars.
Citation
NIST CSF 2.0 DE.CM-01, DE.CM-09. NIST SP 800-92. NIST NCCoE Concept Paper on Software and AI Agent Identity and Authorization (Feb 5, 2026).
Primary failure modes
Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.
- logging the raw token instead of a hash
- hook ships only to local file
- auditd rule on binary path defeated by binary move
Crosswalk
| NIST CSF 2 0 | DE.CM-01, DE.CM-09 |
|---|---|
| OTHER | NIST SP 800-92, NIST NCCoE Concept Paper on AI Agent Identity (Feb 5, 2026) |
Cite this cell:
https://agenticcovenants.com/detect/identity/client-side/