docs(agents): what the audit log cannot answer #22

Merged
jlxq0 merged 3 commits from docs/what-the-audit-log-cannot-answer into main 2026-08-27 04:32:45 +00:00
Owner

Three limits of this service's own log, each measured on the running pod, and each of which reads as an answer rather than as a limit.

token_hash is a credential identifier, not a client identifier

All four sessions that mount jmap-mcp present the same bearer. Mounts live at ~/Smithy/<name>_agent/wt/main/src/mcp.json:

honoka   channel, matrix-julian, jmap
lucy     channel, matrix-julian, jmap, caldav, carddav, m365
mantis   channel, matrix-julian, jmap, m365, caldav, carddav
penny    channel, matrix-penny, jmap, caldav, carddav
vryan    channel, matrix-vryan, hevy          <- negative control, no jmap

Ten audit lines, three separate occasions, at least two sessions: 1 distinct token_hash, user julian@kampong.social on all ten.

Not wrong data. Non-discriminating data in the shape of discriminating data, which is worse than an absent field: an incident reader pairs token_hash with user, sees identical hashes, and concludes they have separated or joined two callers. Per-session attribution would be a bearer-per-agent decision and is not this repository's to make.

A request count is not a call count

One mcp__jmap__whoami produced seven authenticated requests:

2026-08-27T04:12:08.290281Z   first
2026-08-27T04:12:08.347111Z   seventh, 57 ms later

The ingress chain length line fires once per authenticated HTTP request, and streamable-http turns one tool call into a whole exchange. Reading nine requests as nine actions is out by most of an order of magnitude, and any rate or volume conclusion from these lines inherits it.

The logs are JSON, so a key=value grep matches nothing

JMAP_MCP_LOG_FORMAT=json. Measured on a pod mid-flight:

grep 'trusted_proxy_hops=[0-9]'   0 matches
grep '"trusted_proxy_hops":'      9 matches

The line as it lands:

{"timestamp":"2026-08-27T04:06:09.579021Z","level":"INFO","fields":{"message":"ingress chain length","xff_entries":2,"trusted_proxy_hops":2},"target":"jmap_mcp::auth"}

A zero there reads as "the feature is not running" rather than "my pattern is wrong", which is how it cost a fleet sweep three pods that were logging throughout. My own earlier reports rendered these lines as entries=2 hops=2 through jq, which is a rendering rather than a quote, and pasting a rendering into a grep is how the wrong pattern gets built.

Docs only.

Three limits of this service's own log, each measured on the running pod, and each of which reads as an answer rather than as a limit. ## `token_hash` is a credential identifier, not a client identifier All four sessions that mount jmap-mcp present the same bearer. Mounts live at `~/Smithy/<name>_agent/wt/main/src/mcp.json`: ``` honoka channel, matrix-julian, jmap lucy channel, matrix-julian, jmap, caldav, carddav, m365 mantis channel, matrix-julian, jmap, m365, caldav, carddav penny channel, matrix-penny, jmap, caldav, carddav vryan channel, matrix-vryan, hevy <- negative control, no jmap ``` Ten audit lines, three separate occasions, at least two sessions: **1 distinct `token_hash`, `user` `julian@kampong.social` on all ten.** Not wrong data. Non-discriminating data in the shape of discriminating data, which is worse than an absent field: an incident reader pairs `token_hash` with `user`, sees identical hashes, and concludes they have separated or joined two callers. Per-session attribution would be a bearer-per-agent decision and is not this repository's to make. ## A request count is not a call count One `mcp__jmap__whoami` produced **seven** authenticated requests: ``` 2026-08-27T04:12:08.290281Z first 2026-08-27T04:12:08.347111Z seventh, 57 ms later ``` The `ingress chain length` line fires once per authenticated HTTP request, and streamable-http turns one tool call into a whole exchange. Reading nine requests as nine actions is out by most of an order of magnitude, and any rate or volume conclusion from these lines inherits it. ## The logs are JSON, so a `key=value` grep matches nothing `JMAP_MCP_LOG_FORMAT=json`. Measured on a pod mid-flight: ``` grep 'trusted_proxy_hops=[0-9]' 0 matches grep '"trusted_proxy_hops":' 9 matches ``` The line as it lands: ```json {"timestamp":"2026-08-27T04:06:09.579021Z","level":"INFO","fields":{"message":"ingress chain length","xff_entries":2,"trusted_proxy_hops":2},"target":"jmap_mcp::auth"} ``` A zero there reads as "the feature is not running" rather than "my pattern is wrong", which is how it cost a fleet sweep three pods that were logging throughout. My own earlier reports rendered these lines as `entries=2 hops=2` through `jq`, which is a rendering rather than a quote, and pasting a rendering into a grep is how the wrong pattern gets built. Docs only.
docs(agents): what the audit log cannot answer
All checks were successful
CI / tag-ancestry (pull_request) Successful in 3s
CI / cargo (pull_request) Successful in 58s
CI / docker (pull_request) Successful in 20s
ed9bf0c35d
Three limits measured on the running pod, each of which reads as an answer.

token_hash identifies a credential, not a client. All four sessions that mount
jmap-mcp present the same bearer, so ten audit lines from three occasions and
at least two sessions carry 1 distinct hash and one user. Non-discriminating
data in the shape of discriminating data.

A request count is not a call count: one whoami produced seven authenticated
requests in 57 ms, because the line fires per HTTP request and streamable-http
turns one tool call into an exchange.

The logs are JSON, so grep 'trusted_proxy_hops=[0-9]' returns 0 while
grep '"trusted_proxy_hops":' returns 9 on the same pod. A zero there reads as
the feature not running rather than the pattern being wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs(config): name the bypass residual the hop count carries
All checks were successful
CI / tag-ancestry (pull_request) Successful in 3s
CI / cargo (pull_request) Successful in 1m11s
CI / docker (pull_request) Successful in 55s
2740732d86
Codex, reviewing carddav-mcp's equivalent change, found a case in nobody's
brief: a caller reaching the Cilium gateway directly has real depth 1, sends
its own X-Forwarded-For, Envoy appends, len becomes 2, the len < hops guard
never fires, and hops=2 selects the caller's own string. Stolen bearer plus a
path to the gateway yields a forged address in an audit record rather than
access.

The mitigation is that the gateway is not reachable off the cluster LAN, and
nothing in this process can assert that. Measured from outside with a
same-moment control: the edge at 203.24.209.8:443 is OPEN while
203.24.209.5:443 and :80 both time out at 6s, so the gateway is unreachable
from the public internet rather than this host's network being at fault. That
covers one external vantage and says nothing about a LAN or in-cluster caller.

Comment only, no behaviour change, so no release of its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs: correct the residual, and foreclose the token_hash repair
All checks were successful
CI / tag-ancestry (pull_request) Successful in 18s
CI / cargo (pull_request) Successful in 1m27s
CI / docker (pull_request) Successful in 1m3s
6de889df4a
Clark measured the gateway's reachability: not the LAN, only code running
inside the cluster. fondue holds 203.24.209.5/32 as a MetalLB BGPAdvertisement
peered across sgp, lax and zrh, and the L2 pool is home-lan on 10.0.10.240, so
nothing on the wifi has a route. My earlier wording said 'off the cluster LAN'
and was wider than the measurement.

Adds the asymmetry, which is why 'use the edge-inclusive depth' is not a
complete instruction: on the bypass path 2 is worse than 1, because 1 selects
an infrastructure address and 2 selects whatever the caller typed.

Names the one line that would open it. This service's HTTPRoute has exactly
one parentRef, gateway/web sectionName http, as do all 89 routes on the
cluster; adding gateway/home as a second makes it LAN-reachable with no alert,
no failing test and no visible difference in the pod. So the assertion belongs
in oddie-apps/platform rather than here.

And forecloses the repair somebody will reach for after reading the token_hash
entry: pairing the ingress line to the audit line on the hash cannot work,
because every mounting session presents the same bearer, so two concurrent
requests carry identical hashes. That is exactly as ambiguous as adjacency
with the appearance of attribution added, and it would pass review. A real fix
needs a per-request correlation id or the count folded into the audit event.

Comment and docs only. No behaviour change, no release.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jlxq0 merged commit 49a10de296 into main 2026-08-27 04:32:45 +00:00
jlxq0 deleted branch docs/what-the-audit-log-cannot-answer 2026-08-27 04:32:45 +00:00
Sign in to join this conversation.
No reviewers
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/jmap-mcp!22
No description provided.