- Docs
- Bureau — Blue Team (defensive)
- Tripwire
Bureau — Blue Team (defensive)
Tripwire
A local recorder that watches every outbound LLM request from your dev machine and writes a signed receipt for each one.
Posture: 🔵 Blue Team (defensive) · Status: alpha
What it does
Tripwire sits inside your Node process and records every outbound HTTP request your code makes to an AI vendor – what URL, what model, what messages, what response, how long it took. Captures every outbound HTTP/HTTPS call from agent processes. Operates as a transparent in-process recorder – no application changes needed; cassettes accumulate in the background.
Think of it as Wireshark for agent traffic. When some library you imported is silently calling a vendor you didn't authorize, when your CI pipeline picks up an unexpected gpt-4o-mini instead of the model you pinned, when an LLM response leaks a customer's data into a log – Tripwire has the cassette (a signed bundle of the full request and response) on disk, ready to inspect or hand to a reviewer.
Who would use it
- A platform engineer onboarding a junior developer who wants to know exactly which AI calls their first feature is making.
- A security team at a fintech startup running Tripwire across every dev laptop so a
curl https://evil.example.comfrom inside an LLM agent shows up immediately. - A solo developer building a multi-agent system who wants to debug "wait, why did this agent talk to OpenAI when I configured Anthropic?"
- A compliance lead at a healthcare company auditing whether any LLM in the codebase has ever sent a real patient name to a vendor (PII regression detection).
- An open-source maintainer evaluating a new agent framework who wants to see every request it makes before approving it for the project.
What you'll need
- Node.js 18 or newer (Tripwire patches the Node
fetch,node:http, andnode:httpsclients – anything using those is captured). - The Pluck CLI:
npm install -g @sizls/pluck-cli. - An operator key – one-time setup with
pluck bureau keys generate --out ./keys --name "alice". - A directory to hold the dossier and cassettes (e.g.,
./.tripwire). - Optional: an internet connection to publish red-flagged cassettes to Sigstore Rekor. Default is local-only.
Step-by-step
1. Start the daemon
In one terminal:
pluck bureau tripwire install --keys ./keys --out ./.tripwire
The daemon runs in the foreground. Leave it open. It prints status as captures arrive. Press Ctrl-C to drain cleanly.
2. Run your code as normal
In another terminal:
node my-agent.js
Every fetch, every https.request, every OpenAI/Anthropic/Ollama call your agent makes gets intercepted, classified against the built-in policy, and written as a cassette into ./.tripwire/cassettes/.
3. Check the dossier
pluck bureau tripwire status --out ./.tripwire
Output looks like:
tripwire/status: 417 dots (412 green, 4 red, 1 black)
dossier: ./.tripwire/me-dossier.json
earliest: 2026-04-23T10:14:00Z
latest: 2026-04-23T12:28:00Z
Green dots are routine LLM calls matching the policy. Red dots are anomalies – model swaps, unexpected endpoints, suspicious traffic. Black dots are errors.
4. Bundle for sharing
When you need to hand a cassette to a reviewer:
pluck bureau tripwire export ./bundle --out ./.tripwire
This copies the dossier and every cassette into ./bundle as a portable, verifiable archive.
5. (Optional) Publish red dots
Add --notarize to the install command and any non-green capture gets pushed to Sigstore Rekor. Use this when you want a third party to be able to independently verify what you saw.
Run it yourself
Drop this into a Node 18+ project (npm install @sizls/pluck-bureau-tripwire @sizls/pluck-bureau-core tsx):
// index.ts
import { createTripwireSystem } from "@sizls/pluck-bureau-tripwire";
import { generateOperatorKey } from "@sizls/pluck-bureau-core";
async function main() {
const operator = generateOperatorKey();
const system = createTripwireSystem({
signingKey: operator.privateKeyPem,
disablePausePoll: true,
disableLogging: true,
});
try {
// Synthetic outbound traffic: the kind of requests the in-process
// interceptor would feed in production.
system.observeRequest("https://api.openai.com/v1/chat/completions", {
method: "POST",
model: "gpt-4o-mini",
requestBytes: 312, responseBytes: 1024,
responseStatusCode: 200, durationMs: 480,
});
system.observeRequest("https://evil.example.com/exfil", {
method: "POST",
requestBytes: 64, responseBytes: 0,
responseStatusCode: 200, durationMs: 12,
});
for (const c of system.facts.classifications()) {
console.log(`${c.event.vendor.padEnd(8)} ${c.match.tone.padEnd(5)} ${c.match.reason} (llm=${c.match.isLlmTraffic})`);
}
} finally {
await system.shutdown();
}
}
main().catch((err) => { console.error(err); process.exit(1); });
Run with tsx index.ts. Expected output:
openai green default:green (llm=true)
other green non-llm-traffic (llm=false)
(In production, runTripwire() patches fetch + node:http(s) so every real outbound request flows through matchPolicy and lands in the dossier.)
▶ Open in StackBlitz – runs in your browser, no install required.
What you get
- A dossier –
me-dossier.jsonlisting every captured request as a tone-tagged dot (green / red / black) with timestamps and policy verdicts. - Cassettes on disk – full signed bundles of each (request, response) pair in
./.tripwire/cassettes/. These are what you hand to a reviewer or attach to a security-incident ticket. - Built-in secret redaction –
Authorizationheaders,Bearertokens,sk-…API keys, and signed-URL credentials are scrubbed from every cassette before it hits disk. Even with--notarizeoff.
The whole point: when something weird happens at 3am, you have the receipt.
What it can't do
- Tripwire is in-process. It only sees outbound HTTP from the Node process where the daemon runs. Subprocesses, separate workers, or non-Node tools (Python scripts, Docker containers) need their own recorder.
- It does not blanket-redact LLM message content. Tripwire scrubs known credential shapes inside
messages[].content, but does NOT redact every word of conversation text – that would destroy the audit trail. Do not run Tripwire on systems where your prompts contain regulated data (PHI, PCI) without configuring application-level redaction first. - No SSL termination, no MITM. Tripwire watches the request before encryption. It can't be tricked into trusting a fake CA, but it also can't see traffic that bypasses Node's HTTP layer.
- POSIX-only key permissions. Linux and macOS enforce
0600on operator keys; Windows accepts the keys but skips the permission check. Use Tripwire on Windows carefully.
A real-world example
A startup's lead engineer in April 2026 ships a new agent framework into production. Tests pass, alpha customers are happy. Two weeks later, finance flags a $4,000 OpenAI bill – three times the projection. Nobody on the team can explain it.
The engineer runs Tripwire in dev for an afternoon. The dossier shows that on every user request, the framework's "summarizer" sub-agent is calling gpt-4o (expensive) instead of gpt-4o-mini (the configured cheap model). A library buried four levels deep in the dependency tree had a default override.
The signed cassettes attached to the post-mortem ticket are unambiguous: the request body literally contains "model": "gpt-4o", signed and timestamped. Fix is one line, blast radius is documented, and the next dependency review catches the same pattern in two other libraries.
For developers
JS-layer interceptor
This alpha ships the in-process JavaScript-layer interceptor:
- A patched
globalThis.fetch(undici-backed in Node 18+). - A patched
node:httpandnode:httpsrequest creator.
That covers every Node-process LLM client that uses standard HTTP – which is essentially all of them. Native macOS Network Extension and Linux eBPF paths are deferred because they require entitlements and libbpf bindings that aren't trivial to ship inside a published npm package.
Predicate URIs
Tripwire rides on top of existing predicate types – it does not mint a new URI of its own. Every cassette emits as https://pluck.run/AgentRun/v1 (the standard cassette predicate). The dossier per machine is sha256-anchored via computeDossierHash.
Programs composed
attest, notarize. The interceptor → cassette → attestation chain is the same one Concepts: Act → Notarisation describes; Tripwire only adds the in-process capture layer above it.
Privacy and secrecy posture
- Bodies are local-only by default. The intercepted request and response bodies become a Pluck cassette and stay on disk in
<outputDir>/cassettes/<envelopeHash>.json. They are NEVER embedded in aTimelineEvent,TimelineDot.summary, orTimelineDot.reason. - Secret-pattern redaction runs unconditionally. Before the cassette is built, every request and response body is walked for known credential shapes –
Authorization/X-API-Key/CookieJSON fields,sk-…API keys,Bearer …tokens, andapi_key=URL fragments – and replaced with[REDACTED]. This applies even when--notarizeis OFF, because cassettes can also leak viatripwire exportorcosign verify-attestationshared with reviewers. - LLM message content is your data. Tripwire redacts the known credential shapes above inside
messages[].content, but does NOT blanket-scrub conversation text – that would destroy the audit trail. Do not run Tripwire on systems where your LLM messages contain regulated data (PHI, PCI, etc.) without configuring application-level redaction first. - No SSL termination. Tripwire is in-process – it sees the request before the http layer encrypts. We don't MITM.
- Notarization is opt-in. Pass
--notarize(CLI) ornotarize: true(library) to publish the DSSE envelope to Rekor. Default is local-only; the dossier hash is the only thing that ever touches the bureau. - Path-segment secrets are redacted in the dot's
endpointfield. Per-account/tenant URLs like/projects/<long-id>/...and credentials embedded in the path (AWS Bedrock signed URLs, legacy?apikey=migrations) are replaced with[REDACTED]before the URL hashes into the dossier.
Threat model and limits
- 64-hex signing-key fingerprints. No truncation.
- Strict ISO 8601 UTC validation.
- Bounds caps. Events per session, dossier size, watched-model list, rule list – all bounded.
- Canonical JSON for every signed body.
- Fail-closed on missing trust anchors.
- AbortSignal threaded through daemon and interceptors.
- Strict identifier validation on every CLI flag.
index.tstype-pure /register.tsside-effect /./pluginsubpath /sideEffectsallowlist – Tripwire follows the standard Bureau plugin shape.
Studio routes
studio.pluck.run/bureau/tripwire/me– per-machine timeline (operator-only by default).studio.pluck.run/bureau/tripwire/<machine-fingerprint>– opt-in shared timeline.studio.pluck.run/bureau/tripwire/policy– community policy registry.
Library surface
import { runTripwire, defaultPolicy } from "@sizls/pluck-bureau-tripwire";
const summary = await runTripwire({
outputDir: "./.tripwire",
signingKey,
rekorPublicKey,
policy: defaultPolicy(),
notarize: false,
abortSignal,
});
// summary.eventsCaptured, summary.dotsByTone, summary.errors[]
See also
- Dragnet – server-side hunt loop; Tripwire is the dev-machine equivalent.
- Whistle – borrows Tripwire's secret-pattern redactor; layers k-anonymity and stylometric guards.
- Bureau Foundations → redactBureauPayload – the cross-program pre-notarize scrub.
- Concepts: Act → Notarisation –
attest/notarize.