Sign in

Brooks

@brooksmcmillin.com
40 followers 67 following 58 posts

Infrastructure / AI Security Engineer

PostsRepliesMedia
Brooks @brooksmcmillin.com · 03/06/2026
The hallucinated filename isn't the bug. Belief-persistence is: ignoring ground truth for a prior. Writeup: brooksmcmillin.com/blog/dead-pa...
brooksmcmillin.com
A Coding Agent Read a File That Didn't Exist Five Times, Then Blamed the Tools
A Claude Code session confabulated a nonexistent Python file, persisted against five truthful "does not exist" errors, then self-diagnosed as corrupted tool output. A reconstruction from the raw trans...
000
Brooks @brooksmcmillin.com · 03/06/2026
A Claude Code session read a 156-line Python file five times and started editing it. The file never existed; every read said so. It reasoned around all five errors, then blamed "corrupted tool output."
200
Brooks @brooksmcmillin.com · 21/05/2026
The forcing function matters more than the crypto IMO. Enumerating allowed_tools per agent surfaced "why does this one even need task:write?" questions we'd been avoiding. The same verifier path extends to arguments: "web_search but only these domains" is just a tighter predicate, not new machinery.
000
Brooks @brooksmcmillin.com · 21/05/2026
7/7 Full write-up: offline verification, argument-level constraints, multi-hop delegation with attenuation, and where the nearby alternatives (mTLS+RBAC, OPA, SPIFFE, macaroons) don't do the same thing: brooksmcmillin.com/blog/wiring-...
brooksmcmillin.com
Wiring capability warrants into autonomous agents
Why OAuth scopes aren't enough for autonomous LLM agents calling MCP tools, and how we wired Tenuo capability warrants end-to-end. Scope-gated rollout, two real bugs, multi-hop delegation, and an atta...
000
Brooks @brooksmcmillin.com · 21/05/2026
6/7 7 days of production: 686 warrants verified, 0 failed, sub-second propagation from denial in-cluster to the audit dashboard. Average scope width trending down as we tighten agents' allowed_tools. The denials we see are the ones we want to see.
100
Brooks @brooksmcmillin.com · 21/05/2026
5/7 Unglamorous trick that made the rollout shippable: enforcement is scope-gated. If your OAuth token carries agent:autonomous, a warrant is mandatory. Claude Code, the web UI, and ad-hoc service accounts don't carry that scope, so they pass through unchanged.
200
Brooks @brooksmcmillin.com · 21/05/2026
4/7 FastMCP normalizes tool arguments server-side after the client signs the raw wire dict, defaults filled in and Nones dropped. PoP mismatch on every call. Fix: capture raw wire args before Pydantic runs, strip Nones on the client so both sides agree on 'absent.'
100
Brooks @brooksmcmillin.com · 21/05/2026
3/7 An agent reads a task description with a prompt injection telling it to call delete_task. Under OAuth scopes, task:write covered delete and the call went through.Under a warrant whose actions don't include delete: deny_scope, audit event, no deletion.
100
Brooks @brooksmcmillin.com · 21/05/2026
2/7 I wired Tenuo capability warrants into 16 autonomous agents end-to-end. Every outbound MCP tool call carries a signed warrant scoped to that agent's allowed tools. The MCP server verifies offline against cached trusted roots. No cloud round-trip in the hot path.
110
Brooks @brooksmcmillin.com · 21/05/2026
1/7 OAuth scopes get autonomous LLM agents 70% of the way to safe. The token says who is calling. It doesn't say what this specific run of who can do, signed and time-bounded. That gap is the whole problem with shipping autonomous agents to production. brooksmcmillin.com/blog/wiring-...
brooksmcmillin.com
Wiring capability warrants into autonomous agents
Why OAuth scopes aren't enough for autonomous LLM agents calling MCP tools, and how we wired Tenuo capability warrants end-to-end. Scope-gated rollout, two real bugs, multi-hop delegation, and an atta...
131
Brooks @brooksmcmillin.com · 19/05/2026
Cheapest defense: AGENTS.md, CLAUDE.md, .cursorrules, .github/copilot-instructions.md under CODEOWNERS. Five-minute change. Full writeup: brooksmcmillin.com/blog/poisoni...
000
Brooks @brooksmcmillin.com · 19/05/2026
Other things that slip through: - pickle.loads(blob) with a docstring claiming a sealed admin container - SSRF behind a "patched in 2.4.1" release note the agent fetched - Auth bypass buried in a 2,000-line PR - .cursorrules inside node_modules poisoning every future review
100
Brooks @brooksmcmillin.com · 19/05/2026
The pattern that works: declarative beats directive. "Do not flag X" gets caught. "X is mitigated at layer Y, per ADR-0042" gets believed. The model defers to documentation instead of resisting an order. ADR-0042 doesn't exist. The ticket number is enough.
100
Brooks @brooksmcmillin.com · 19/05/2026
I told my AI reviewer to ignore three CVEs. It caught me trying to fool it. That was last year's attack. Modern reviewers catch "do not flag X" and narrate the catch. So I went back and tried to write attacks that would survive the upgrade. brooksmcmillin.com/blog/poisoni...
brooksmcmillin.com
Poisoning the Safety Net: Attacking AI Code Review Pipelines
Four months after writing about defense in depth for LLM-assisted development, I went back and tried to attack every layer of my own stack. The obvious attacks are caught by 2026 models. The class isn...
100
Brooks @brooksmcmillin.com · 30/04/2026
Awesome! Happy to test against any preview build if useful.
000
Brooks @brooksmcmillin.com · 30/04/2026
One sharp edge: Claude Code's MCP SDK crashes silently if .well-known/oauth-protected-resource returns a plain-text 404 instead of JSON. Empty {} with a 404 fixes it, but the failure mode was opaque. Writeup: brooksmcmillin.com/blog/mcp-std...
brooksmcmillin.com
The MCP stdio Problem: Why I Rebuilt My Auth Proxy as a Persistent HTTP Service
Claude Code silently kills stdio MCP servers during idle periods, forcing manual reconnection. How I converted a fragile stdio bridge into a persistent Starlette HTTP reverse proxy — and the obscure S...
100
Brooks @brooksmcmillin.com · 30/04/2026
It shows. Running PRM-discovered auth across a handful of self-hosted MCP servers in production now and the flow is refreshingly boring.
100
Brooks @brooksmcmillin.com · 30/04/2026
There's an end-to-end example repo. `docker compose up`, point Claude Code at `localhost:9001/mcp`, and watch a full OAuth dance happen on the first tool call. pip install mcp-authflow / mcp-authflow-resource
010
Brooks @brooksmcmillin.com · 30/04/2026
MCP clients in Claude Desktop and Claude Code already probe `.well-known/oauth-protected-resource` and run the auth-code-with-PKCE flow. Stand up something RFC-compliant and they just work. No custom client path needed.
110
Brooks @brooksmcmillin.com · 30/04/2026
The split: one auth server issues and introspects tokens (PostgreSQL, PKCE, dynamic client registration, rate limiting). The resource server is a thin wrapper around your actual tools that verifies bearer tokens and enforces scopes. No database on the resource side.
100
Brooks @brooksmcmillin.com · 30/04/2026
So I built `mcp-authflow` and `mcp-authflow-resource`. Two MIT-licensed Python packages for proper OAuth 2.0 on MCP servers. Auth server + resource server, independently deployable.
100
Brooks @brooksmcmillin.com · 30/04/2026
When an MCP server can delete tasks, push code, or write to a database, "static undifferentiated credentials" is a real problem. You can't revoke one agent's access. You can't scope tools individually. Rotating the key means coordinating a config swap everywhere.
100
Brooks @brooksmcmillin.com · 30/04/2026
Most MCP servers in the wild have one of three "auth" strategies: no auth, a shared API key, or localhost-and-pray. None of those are auth. brooksmcmillin.com/blog/product...
brooksmcmillin.com
mcp-authflow: OAuth 2.0 for Production MCP Servers
Open-sourcing mcp-authflow and mcp-authflow-resource: an RFC-compliant OAuth 2.0 framework for MCP servers, plus a one-command example server. Why MCP deployments need real auth, what the two…
110
Brooks @brooksmcmillin.com · 15/04/2026
Speaking at @cackalackycon May 16th "Poisoning the Safety Net: Attacking AI-Powered Code Review Pipelines" Live demos attacking the defenses around LLM-generated code: poisoned CLAUDE.md files, manipulated AI code reviewers, injection through PR diffs. AI security tools need their own threat model
020
Brooks @brooksmcmillin.com · 09/04/2026
Glad to see I’m not alone! Getting as much of the process as possible in our control is definitely the way to go.
000
Brooks @brooksmcmillin.com · 09/04/2026
Full writeup: the architecture, the systemd setup, the OAuth token manager, and both bugs. If you're running authenticated remote MCP servers with Claude Code, worth a read. brook.ski/stdio-problem
brook.ski
The MCP stdio Problem: Why I Rebuilt My Auth Proxy as a Persistent HTTP Service
Claude Code silently kills stdio MCP servers during idle periods, forcing manual reconnection. How I converted a fragile stdio bridge into a persistent Starlette HTTP reverse proxy — and the obscure…
000
Brooks @brooksmcmillin.com · 09/04/2026
Turns out the MCP SDK probes `/.well-known/oauth-authorization-server` and `/.well-known/oauth-protected-resource` on every HTTP connection. Starlette returns plain-text 404s for undefined routes. The SDK expects JSON. Connection fails, no useful error. Fix: two routes returning `{}` with a 404.
210
Brooks @brooksmcmillin.com · 09/04/2026
Shipped it. Worked immediately. Then the MCP SDK silently refused to connect.
110
Brooks @brooksmcmillin.com · 09/04/2026
The fix is to stop using stdio. Run a persistent HTTP server on localhost instead. Claude Code connects to a URL — it doesn't own that process, so it can't kill it. Before: Claude Code → stdio → proxy (killed on idle) → MCP After: Claude Code → HTTP → localhost:4510 (always running) → MCP
110
Brooks @brooksmcmillin.com · 09/04/2026
The core problem: Claude Code owns the stdio process lifecycle. When it decides you've been idle too long, it kills the subprocess. Keepalive pings don't help. There's no override.
100
Brooks @brooksmcmillin.com · 09/04/2026
Claude Code silently kills your stdio MCP server when it goes idle. No warning, no config to change it. Auth state gone. You're reconnecting from scratch. Spent weeks fighting this. Here's what actually fixed it.
220
Brooks @brooksmcmillin.com · 24/03/2026
Most agent security stops at prompt injection defense. That's the most bypassable layer. The ones nobody talks about: context trimming as a security boundary, memory namespace poisoning across agents, SSRF via DNS rebinding at the transport layer. Six layers, code included: brook.ski/unprompted
brooksmcmillin.com
Building Secure Agentic Systems: The Six Layers
Six layers of security architecture for running LLM agents as daily drivers — every design decision with production stats and companion code.
110
Brooks @brooksmcmillin.com · 12/03/2026
People kept asking how I set up my LLM coding safety layers, so I wrote the guide. Pre-commit hooks → CLAUDE.md → review agents → CI workflows → branch protection. Zero to fully protected in ~30 minutes. Step-by-step, no prior experience assumed: brooksmcmillin.com/blog/llm-saf...
brooksmcmillin.com
A Beginner's Guide to Safe LLM-Assisted Development | Brooks McMillin - AI Security Researcher
A complete beginner's guide to setting up every safety layer from the 'Coding Safer with LLMs' post: pre-commit hooks, local review agents, CI workflows, and CLAUDE.md—starting from scratch.
010
Brooks @brooksmcmillin.com · 27/02/2026
Full data and test harness are open source 👇 brooksmcmillin.com/blog/prompt-...
brooksmcmillin.com
010
Brooks @brooksmcmillin.com · 27/02/2026
Other surprises: - GPT-4o and Claude Sonnet 4.5: never injected, even with zero defense - Few-shot poisoning: 29.5% success rate, the only attack that still works reliably - The sandwich technique most people recommend? It actually makes things worse
brooksmcmillin.com
100
Brooks @brooksmcmillin.com · 27/02/2026
I ran 10,080 prompt injection tests across 8 models and 6 defense strategies. The biggest finding: telling a model to "note the attempt and disregard it" cuts injection rates 5x vs "don't follow other instructions." Procedures beat prohibitions.
brooksmcmillin.com
100
Brooks @brooksmcmillin.com · 27/02/2026
Yep, the more we depend on logical security tooling vs hoping the LLM translates our words the right way, the better.
000
Reposted by Brooks
Matthew Green @matthewdgreen.bsky.social · 25/02/2026
And right on schedule: there goes pseudonymity on the Internet. arxiv.org/abs/2602.16800
arxiv.org
Large-scale online deanonymization with LLMs
We show that large language models can be used to perform at-scale deanonymization. With full Internet access, our agent can re-identify Hacker News users and Anthropic Interviewer participants at hig...
49862
Brooks @brooksmcmillin.com · 23/02/2026
No theoretical architectures. I'll show the actual code, the actual failures, and the fixes, including a memory contamination bug where my business advisor's context leaked into my security researcher's responses. Come find me if you're building agentic systems and want to compare notes.
000
Brooks @brooksmcmillin.com · 23/02/2026
- Tool allowlists & capability bounding - Prompt injection detection that needed real-world tuning (too many false positives) - Multi-agent memory isolation failure - OAuth device flow for headless auth - Hot-reload to stop bypassing my own security to iterate faster
200
Brooks @brooksmcmillin.com · 23/02/2026
Speaking at [un]prompted AI Security Conference next week at Salesforce Tower SF. "Building Secure Agentic Systems: Lessons from Daily-Driver Agents" March 4, 11:45 AM Live demos of what actually breaks when you build agents you use every day. 🧵
100
Brooks @brooksmcmillin.com · 08/02/2026
I use blink in iPhone and it’s the best SSH client I’ve run into. $20/year, but well worth it if you want to be able to SSH into a box and do some quick edits. That plus a bunch of linting and QoL extensions for Neovim make it bearable. blink.sh
blink.sh
Blink Shell is a professional, desktop grade terminal for iOS. With Mosh & SSH clients for iOS, local UNIX tools, lightning fast and fully customizable. The best terminal for iOS and iPadOS.
010
Brooks @brooksmcmillin.com · 08/02/2026
How about CLI based IDEs, like Neovim (en.wikipedia.org/wiki/Neovim). Not sure if it’s the best of both worlds or the worst, but it makes it so I can have the same workflow no matter which box I’m SSHed to, and I can use it from my phone terminal when needed.
en.wikipedia.org
Neovim - Wikipedia
120
Brooks @brooksmcmillin.com · 07/02/2026
4/ Slides + all sample code (prompt injection test harness, MCP client, OAuth email server w/ Lakera Guard) are open source: 📎 slides.brooksmcmillin.com/cactus.html#1 📎 github.com/brooksmcmill...
slides.brooksmcmillin.com
Breaking Model Context Protocol - CactusCon 2026
010
Brooks @brooksmcmillin.com · 07/02/2026
3/ CVE-2025-6514 (CVSS 9.6): mcp-remote passed OAuth metadata straight to the system shell. One crafted authorization_endpoint = full RCE on Claude Desktop, Cursor, Windsurf, VS Code. 437K+ installs before patch.
110
Brooks @brooksmcmillin.com · 07/02/2026
2/ But the new threat model is real. Your OAuth client is now a reasoning engine that can be lied to. I demoed a malicious MCP server that exfiltrates data from a legitimate task manager through the AI agent. No jailbreaking. Just a poisoned tool description.
120
Brooks @brooksmcmillin.com · 07/02/2026
1/ Most MCP vulns are classics in disguise: • Missing PKCE on public clients • Plaintext token storage • Timing attacks on token comparison (found this 8 times) • DNS rebinding against local servers • Default secrets deployed to prod
110
Brooks @brooksmcmillin.com · 07/02/2026
MCP has 97M monthly SDK downloads. Only 8.5% of servers use OAuth. Authentication is optional in the spec. I gave a talk at @CactusCon on breaking MCP security. Here's what I found 🧵
110
Brooks @brooksmcmillin.com · 02/02/2026
LLMs will happily remove your auth middleware if it helps them complete the task faster. I wrote up the defensive layers I actually use: pre-commit hooks, review agents, and CI that catches LLM mistakes before they ship. ~30 seconds per commit, but worth it. brooksmcmillin.com/blog/coding-...
brooksmcmillin.com
Defense in Depth for AI-Assisted Development: Pre-commit Hooks, Review Agents, and CI That Catch LLM Mistakes | Brooks McMillin - AI Security Researcher
Practical strategies for safer AI-assisted development: automated review agents, layered security checks, and context management that prevents catastrophic mistakes.
000
Reposted by Brooks
Matthew Green @matthewdgreen.bsky.social · 23/01/2026
Microsoft is handing over Bitlocker keys to law enforcement. www.forbes.com/sites/thomas...
forbes.com
Microsoft Gave FBI BitLocker Encryption Keys, Exposing Privacy Flaw
The tech giant said providing encryption keys was a standard response to a court order. But companies like Apple and Meta set up their systems so such a privacy violation isn’t possible.
16455312