The hook never reached the model
On August 31, Token Security walked through Anthropic's new Compliance API endpoints for local Claude Code sessions on The Hacker News. They are a real improvement, and the piece is precise about where the transcript stops: hooks that fire before a tool runs, files sitting on disk, processes started outside a session, anything served by Bedrock or Vertex. Last week's npm worm spent its whole life in that gap. Bromure Agentic Coding taps the wire between the VM and everything else, so the record covers what happened rather than what someone said to the model.
A transcript can only contain what someone said to the model. The interesting half of an agent session is the half nobody says out loud.
For most of this year, an engineering manager who wanted to know what Claude Code had been doing on a laptop had two options: read the developer's scrollback, or buy a third-party wrapper. Anthropic's compliance endpoints covered claude.ai and Claude Desktop, and Claude Code barely at all.
That changed on August 11. Anthropic shipped three Compliance API endpoints for
local sessions: one lists sessions, one fetches a session's metadata, and the
third pulls the full transcript from
GET /v1/compliance/apps/sessions/local/{session_id}/messages. On August 31,
Token Security published
a walkthrough of what they give you
on The Hacker News.
Token Security sponsored that piece, and it is still one of the more careful accounts I have read of where a log's horizon sits.
Three block types, and what they cover
The transcript arrives as three kinds of block. In the article's words:
"Whatever is communicated to the model is logged in three block types: text,
tool_use, and tool_result. Between them, they cover user prompts, bash
commands, reads and writes, and even MCP commands."
That is a lot: every shell command the model chose, every file it read, every MCP tool it called. If your question is "what did this agent decide to do on Tuesday", the transcript answers it, and it answers from Anthropic's servers rather than from a file on the machine you are investigating.
The clause doing the work is the first one: whatever is communicated to the model.
The horizon
Four things sit outside that boundary, and the article names all four.
Hooks. "Hooks are the clearest case: they run locally, between the model's decision and the tool actually running, and can block a tool from executing or a prompt from being sent." A hook is code on the developer's disk that fires in the gap between the model choosing an action and the action happening. The model never hears about it, so the transcript never carries it.
Disk. "Neither can see what sits on disk: config files, installed skills and plugins and their .md files (unless they were used in a session), or processes launched outside a session." A skill you install and never invoke leaves no trace, and neither does a background process the agent started an hour ago and forgot about.
Other model providers. "If you run Claude Code on a model that isn't Anthropic's, you get no Compliance API coverage at all, because it only logs interactions with Anthropic's models. Sessions running on Bedrock, Foundry, or Google Cloud won't be covered." Coverage stops at Anthropic's models, which leaves out Codex, Gemini CLI, Grok, and whatever your team installs next quarter.
Permission decisions. A developer's approval of a dangerous command, or a bypass flag that waved a whole prompt through, lands in OpenTelemetry instead of the compliance transcript.
All of this is correct behaviour. A log anchored to the model records what the model saw. The question for your next security review is whether the things you most need to see fall on the model's side of that line.
Last week's worm lived in that gap
Four days ago an npm code generator with more than 150,000 weekly downloads started shipping a credential stealer. We wrote up the publishing chain on Saturday: a stranger commented on a pull request, the project's release workflow read the comment without checking who wrote it, and ten signed versions went out.
Read that malware again with the compliance transcript in one hand. Trace where each step would show up.
The agent runs npm install. That is a tool_use block, and the transcript has
it. Everything after it is a child process.
The first wave declared no install script at all. Its payload sat in
binding.gyp, the build description node-gyp reads, inside a conditions
field that node-gyp evaluates with Python. The model never took a turn on it
and no tool call names it: a Python expression in a build file, running as a
grandchild of the one command the transcript recorded as ordinary.
The sweep that followed walked more than 150 glob patterns across the home
directory for SSH keys, .env files, cloud credentials and registry tokens.
Nobody asked the model about any of it.
Then the persistence. In every repository it could reach, the worm wrote a
.claude/settings.json carrying a SessionStart hook that runs setup.mjs
every time a developer opens the project in Claude Code. A hook, on disk, firing
before anyone consults the model: two of the four blind spots in one file. The
attacker who picked that spot had almost certainly never read a vendor's
compliance changelog.
The whole hostile window ran three hours and eleven minutes, and no part of it ever reached a model.
Where Bromure puts the tap
Bromure Agentic Coding runs each workspace as its own virtual machine on Apple Silicon, and everything leaving that VM passes through a proxy and a virtual switch that live on the Mac side of the hypervisor. The switch diverts the VM's port 80 and port 443 flows into the proxy with no environment variable to set and nothing the guest can unset, so the record does not depend on the agent cooperating, or on the agent knowing the proxy is there.
At the Activity only trace level and above, every request gets one metadata
record: timestamp, host, port, method, path, status code, latency, request
bytes measured before any credential swap, response bytes, which credentials the
proxy substituted on the way out, and a warning if the request carried a bearer
token Bromure did not mint. That record
exists whether the request came from the agent, from a postinstall script,
from an MCP server doing its own thing, or from a process someone started three
hours ago and forgot.
The Security Timeline window (Window → Security Timeline…) sits next to it
and answers a different question. The Trace Inspector tells you what the agent
sent; the timeline tells you what Bromure's engines decided. One chronological
table, newest first, colour-coded green for allowed, red for blocked, blue for
informational: Credential brokering, Firewall, Supply chain, Guardrails, Prompt
injection, Credential used, Upstream TLS. It holds 5,000 rows in memory. The
durable copy is the encrypted session traces under
~/Library/Application Support/BromureAC/traces/, AES-GCM sealed with the same
Keychain key as your workspace secrets.
The same install, in a Bromure workspace, reads like this.
Not one of those rows requires the payload to have passed through a model, and none of them can be edited by code inside the VM, because the engines that write them run on the Mac and the guest has no route to them.
The same rows, whichever agent you run
A team standardises on Claude Code. Then someone brings in Codex for one project, the platform group routes a workspace through Bedrock for data residency, and a researcher starts running Grok. By the article's own account, three of those four produce nothing in the Compliance API.
A proxy that reads TLS server names sees all four the same way, and the same
llm.request, tool.use, command.run, file.read and file.write events
come out of every one of them, pulled from the traffic rather than granted by a
vendor. On a Mac enrolled with a bromure.io workspace they stream to the
organisation over mutual TLS, alongside egress.firewall for every firewall
verdict, credential.exfiltration when a decoy credential leaves for a host it
was never minted for, and supply_chain.fetch for every package the workspace
pulled. That last one fires even with every supply-chain enforcement layer
switched off, because Bromure separates watching from blocking. The stream
carries no raw prompts. It answers what the agent did, not what the developer
asked.
The line the article ends on
The sharpest sentence in the piece leaves coverage behind: "activity logs alone cannot tell you whether an agent's access is legitimate."
That holds for any log, which is why Bromure's engines write the timeline themselves instead of handing the job to a separate observer. Every red row in that window records a decision that already happened. A supply-chain row means a package the agent never received. A firewall row means a connection that never opened. The Guardrails row is a push the host refused with a 403, which the agent read as an ordinary API failure, and the credential-brokering row is a decoy that never became a real secret, because the real one was never in the VM.
You still have to decide the policy. But you answer "was this access legitimate" while configuring the workspace, not weeks later while reading rows.
Where to put your tap
If you run coding agents at any scale, turn the Compliance API on. Four weeks ago that surface had almost no visibility at all, and a week of transcripts will tell you things about your own team you did not know.
Then ask the second question: what do you do with a machine whose transcript
looks clean? In a Bromure workspace the answer is already on screen. Open
Window → Trace Inspector for the hosts, Window → Security Timeline for
the verdicts, or run bromure-cli trace hostnames and read every domain that
workspace has contacted since it booted.
The agent does not have to mention a thing for it to be there. Install Bromure Agentic Coding, open the timeline, and watch an install.