Detect (DE) · Approval gating
Approval gating at the client side layer
deterministic · Outside the model's reasoning
If this concern is breached, how do we know?
What this cell does
Approval-timing analysis surfaces alert-fatigue (response under 2s across more than 50 approvals); typed-confirmation mismatch events; out-of-band channel decisions joined to session.
Artifacts (3)
approval-timing-emit.shview on GitHub#!/usr/bin/env bash
# ABOUTME: Snippet that wraps the typed-confirmation read with timing measurement and emits a structured timing event.
# ABOUTME: Source from pre_tool_use_tiered.sh in place of the bare `read -r confirmation < /dev/tty`.
emit_approval_timing() {
# Args:
# $1 = tier (numeric: 2, 3, 4)
# $2 = expected confirmation (the literal command, for typed-verbatim tiers)
# Reads response from /dev/tty into the global confirmation variable.
local tier="$1"
local expected="$2"
local start_ns end_ns response_ms matched
start_ns="$(date +%s%N)"
IFS= read -r confirmation < /dev/tty
end_ns="$(date +%s%N)"
response_ms=$(( (end_ns - start_ns) / 1000000 ))
if [[ "$confirmation" == "$expected" ]]; then
matched=true
else
matched=false
fi
local event
event="$(jq -n \
--arg session "${SESSION_ID:-default}" \
--argjson tier "$tier" \
--argjson response_ms "$response_ms" \
--argjson matched "$matched" \
--arg ts "$(date -Iseconds)" \
'{event: "approval_timing", session: $session, tier: $tier, response_ms: $response_ms, matched: $matched, ts: $ts}')"
logger -t agent-sentinel -p user.info "$event"
}
# Usage in pre_tool_use_tiered.sh:
#
# . /etc/agents/hooks/approval-timing-emit.sh
#
# echo "TIER-3 ACTION: $TOOL_INPUT" >&2
# echo "Type the command verbatim to confirm:" >&2
# emit_approval_timing 3 "$TOOL_INPUT"
# if [[ "$confirmation" != "$TOOL_INPUT" ]]; then
# ...
# fi
oob-decision-log.shview on GitHub#!/usr/bin/env bash
# ABOUTME: Logs out-of-band approval decisions with the original request ID and session, so SIEM can reconstruct the chain.
# ABOUTME: Called by the OOB approval workflow (Slack approver action, FIDO2 prompt result, etc.) when a decision is made.
set -euo pipefail
REQ_ID="${1:?Missing request ID}"
DECISION="${2:?Missing decision (approved|denied|timeout)}"
APPROVER="${3:-unknown}"
SESSION_ID="${4:-unknown}"
case "$DECISION" in
approved|denied|timeout) ;;
*)
echo "oob-decision-log: invalid decision '$DECISION' (must be approved|denied|timeout)" >&2
exit 64
;;
esac
event="$(jq -n \
--arg req "$REQ_ID" \
--arg decision "$DECISION" \
--arg approver "$APPROVER" \
--arg session "$SESSION_ID" \
--arg ts "$(date -Iseconds)" \
'{event: "oob_approval", req_id: $req, decision: $decision, approver: $approver, session: $session, ts: $ts}')"
logger -t agent-sentinel -p user.info "$event"
sigma-approval-fatigue.yamlview on GitHub# ABOUTME: Sigma rule firing when a user's mean tier-≥2 response time is <2s across >50 approvals.
# ABOUTME: Calibrated against AHRQ PSNet alarm-fatigue research and Anthropic Auto Mode telemetry. Tune for your team.
title: Operator approval fatigue pattern
id: 5a7b8c9d-0e1f-2a3b-4c5d-6e7f8a9b0c1d
status: experimental
description: >
Detects an operator whose typed-confirmation response time has dropped to
reflexive levels: mean response under 2 seconds across 50 or more
tier-≥2 approvals. Reflexive approval is the failure mode tiered approval
exists to fight; when this rule fires, the tiering is no longer adding
meaningful friction to the approval flow.
references:
- https://github.com/peopleforrester/agentic-covenants/blob/main/SENTINELS_MATRIX.md
- https://psnet.ahrq.gov/perspective/alert-fatigue
- Anthropic Auto Mode (March 2026 paper)
author: agentic-covenants
date: 2026/05/08
logsource:
category: agent_hook
detection:
selection:
event: approval_timing
tier|gte: 2
timeframe: 1d
condition: |
selection
| aggregate avg(response_ms) as avg_ms, count() as n by user
| where avg_ms < 2000 and n > 50
falsepositives:
- Operator running a batch script that pre-types confirmations (legitimate but indicates a scope mismatch — those operations probably don't need tiered approval at all).
- Single very-long approval inflating one user's average without other indicators (re-check; the rule uses mean, not median).
level: medium
tags:
- agent
- approval_gating
- sentinels
- human_factors
Cell notes
Sentinels, Approval gating / Client-side
Control. Approval-timing logger captures local think-time on every confirmation. OOB approval channel logger captures decisions joined to session. SIEM rule fires on response under 2s across more than 50 approvals (the alert-fatigue pattern, calibrated against AHRQ PSNet research and Anthropic Auto Mode telemetry).
Strength. Deterministic when timings are measured locally and shipped reliably. Failure modes: timing measurement that includes network latency to a remote approval service (measure local think-time only); threshold too aggressive (anything under 5 seconds is "fatigue") yielding false positives; threshold too lenient (anything over 100ms is "real consideration") so the rule never fires; OOB channel log not joined to the original request session, cannot reconstruct the chain.
Tooling
- - The tiered hook from
../../../controls/approval-gating/client-side/pre_tool_use_tiered.sh, extended to emit timing events. - - A SIEM with windowed-aggregate query support.
Files in this directory
- -
approval-timing-emit.sh, appendable snippet that wraps the typed-confirmation read with timing measurement (start/end nanoseconds, response_ms, matched). - -
oob-decision-log.sh, script that the out-of-band approval workflow calls when an approver decides; emits a structured event keyed to the original request ID and session. - -
sigma-approval-fatigue.yaml, SIEM rule firing when a user's mean tier-≥2 response time is under 2s across more than 50 approvals.
Verification
# 1. Approval timing captured
# Run a tier-3 command, type the confirmation, check log
journalctl -t agent-sentinel --since "5 minutes ago" | grep approval_timing
# expected: response_ms field present
# 2. Fatigue pattern alert fires (test environment)
# Generate 60 approvals at <1s each
for i in {1..60}; do
echo '{"session_id":"fatigue-test","tool_name":"Bash","tool_input":{"command":"echo test"}}' \
| /etc/agents/hooks/pre_tool_use_tiered.sh
done
# Query SIEM for the fatigue rule output
Common mistakes
- - Timing measurement that includes network latency to a remote approval service. Measure local think-time only.
- - Threshold too aggressive (anything under 5s): false positives on routine approvals.
- - Threshold too lenient (anything over 100ms is "real consideration"): never fires.
- - OOB channel log not joined to the original session. Cannot reconstruct full chain.
Citation
NIST CSF 2.0 DE.CM-03, DE.AE-02. AHRQ PSNet alarm-fatigue research. Anthropic Auto Mode (March 2026).
Primary failure modes
Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.
- timing measurement includes network latency
- threshold too aggressive (5s false-positives) or too lenient (100ms never fires)
Crosswalk
| NIST CSF 2 0 | DE.CM-03, DE.AE-02 |
|---|---|
| OTHER | AHRQ PSNet alarm fatigue, Anthropic Auto Mode (March 2026) |
Cite this cell:
https://agenticcovenants.com/detect/approval-gating/client-side/