Skip to content

Security & trust

trimwire is a local forwarding gateway: Claude Code talks to it over plain HTTP on 127.0.0.1, and it forwards your requests to Anthropic over HTTPS. This page explains what that means for your data and your credentials — what trimwire touches, what stays on your machine, and what it deliberately won’t do.

Found a vulnerability? Don’t open a public issue — see the reporting instructions in SECURITY.md on GitHub.

trimwire binds to 127.0.0.1 only — it never listens on an external interface. It forwards to Anthropic over HTTPS with standard certificate validation (rustls + webpki-roots). There is no CA certificate to install, no TLS interception, and no patched client: Claude Code stays the client, unchanged.

(This describes the default ANTHROPIC_BASE_URL wiring. The opt-in Remote-Control coexistence mode — [server] remote_control = true, see CONFIGURATION.md — instead injects a Bun preload script that wraps fetch inside the running Claude Code process to reroute one endpoint. That’s a more invasive posture than the default gateway wiring, and worth understanding before you enable it — though it still installs no CA cert, does no TLS interception, and does not patch any on-disk binary, and all traffic still goes only to Anthropic.)

The upstream URL routes your Authorization token, so it is deliberately not configurable from a project-local ./.trimwire.toml — only the global config or TRIMWIRE_* environment variables can set it. A cloned repo can’t silently redirect your token. Likewise, trimwire run refuses to reuse a listener on the configured port unless it answers /healthz (i.e. it really is trimwire), so it won’t hand your token to something else squatting on that port.

  • Your Claude Code requests go to Anthropic, exactly as Claude Code sent them, minus the conversation context trimwire pruned. Same auth header, same system prompt (on the default path), same model and sampling parameters.
  • Nothing else, by default. trimwire sends no telemetry off-box unless you opt in.

Two opt-in features can send data, and only when you turn them on:

  • Community telemetry (trimwire share enable, then trimwire share stats) sends a bucketed, content-free aggregate to the collector at https://api.trimwire.dev/ingest (and …/ingest-benchmark for benchmarks) — never prompt, file, or command content. Off until you enable it. The exact payload is documented in TELEMETRY.md.
  • The optional summarizer ([summarizer] engine, off by default). With engine = "local", an old slice of messages[] goes to a local model endpoint (e.g. ollama at http://localhost:11434) — nothing leaves the machine. With a cloud provider id, that slice goes to the provider you configured, authenticated with your own key — never your Claude subscription credential. With the default engine = "model-free", trimwire makes no model calls of its own at all. See SUMMARIZER.md.
  • No request or response bodies, ever. The optional SQLite ledger records byte counts and cache-prefix hashes only — not message content. Set [ledger] enabled = false to record nothing.
  • The optional wire audit (--audit / TRIMWIRE_AUDIT, off by default) writes one JSONL line per request with shape metadata only — counts, sizes, flags, the model, the anthropic-beta header, and a session id — and never message content, tool input, or result text. It’s a local debugging aid. The one structural identifier worth noting: it records the request’s tool-definition names, which for MCP servers look like mcp__<server>__<tool> and can reveal configured MCP server names — so treat the audit file like your MCP config. It is created owner-only (0600 on Unix), as are the ledger DB and the telemetry install-id.
  • Your transcripts are untouched. trimwire shapes the request on the wire; it never writes to your ~/.claude session files. (The separate, explicit trimwire sweep command is the only thing that edits on-disk transcripts, and only when you run it.)

To prune a request, trimwire holds it in memory for the duration of that one request. While a request is in flight it sees:

  • the full messages[] array, including conversation content;
  • the Authorization Bearer token (forwarded unchanged, never logged);
  • the anthropic-* and x-stainless-* headers (forwarded unchanged).

It does not modify the system prompt or any header that would change the client’s identity from Anthropic’s perspective (the opt-in system_shape_normalize strategy is the only exception, and only repairs a malformed leading system message). It never reuses your credentials to originate its own traffic.

Release binaries (built by .github/workflows/release.yml) ship three independent verification mechanisms, which answer different questions:

  • .sha256 checksum — integrity. Proves the archive wasn’t corrupted in transit. It’s served from the same origin as the binary, so it does not defend against a compromised release/account (an attacker who can swap the asset can swap its checksum too). scripts/install.sh checks it automatically (falling back to a warning if no sha256sum/shasum tool is on PATH).

  • Build provenance attestation — authenticity/provenance (verify with gh). Each archive is attested with GitHub artifact attestations (actions/attest-build-provenance), cryptographically binding the exact archive bytes to the workflow + commit that produced them. This is the public bar anyone with gh can check.

  • .minisig signature — provenance the updater checks (no gh needed). The release sign job signs each archive with minisign (Ed25519); the matching public key is pinned in the trimwire binary. trimwire upgrade --dry-run / trimwire upgrade verify this signature offline against the pinned key — defending against a swapped/forged asset without requiring gh or a round-trip to GitHub’s attestation store.

    Why minisign/Ed25519 and not native Sigstore: a pinned-key minisign check is a tiny, zero-network, dependency-light verifier the client runs offline. The Rust sigstore crate, by contrast, can’t yet verify the GitHub artifact attestations this pipeline emits, requires a newer toolchain than our MSRV, and pulls a heavy C-based crypto dependency — so it isn’t viable for a credential-path CLI today. The GitHub attestation (below) stays as the public, gh-checkable provenance bar; minisign is the client-side gate the updater enforces.

Verify a download’s provenance with the GitHub CLI — no key to manage, but it requires gh ≥ 2.49 and gh auth login (verification queries GitHub’s attestation store):

Terminal window
# After downloading e.g. trimwire-x86_64-unknown-linux-gnu.tar.gz from the release.
# Recommended — also pins the signer to this repo's release workflow (not just the
# repo), so it proves the asset was built by release.yml specifically:
gh attestation verify trimwire-x86_64-unknown-linux-gnu.tar.gz \
--repo AZagatti/trimwire \
--signer-workflow AZagatti/trimwire/.github/workflows/release.yml
# Quick form — proves only that some workflow in this repo attested the asset:
gh attestation verify trimwire-x86_64-unknown-linux-gnu.tar.gz --repo AZagatti/trimwire

For other platforms, substitute the asset name (e.g. trimwire-aarch64-apple-darwin.tar.gz, trimwire-x86_64-pc-windows-msvc.zip).

A successful run prints the verified provenance (the build workflow + source commit). The release pipeline’s own verify job runs this check on every release event (currently against the Linux x86_64 asset; every platform’s attestation is independently verifiable with the command above), so a release whose provenance can’t be verified fails loudly. Separately, trimwire upgrade (--dry-run to verify only) checks the minisign signature against the pinned key before trusting or installing a download — fail-closed if it can’t.

Scope of the guarantee (SLSA build L2): provenance proves an asset was built by this repo’s release workflow at a given commit, and roots in GitHub’s signing infrastructure (Sigstore/Fulcio) and the build runner. It does not prove the build’s inputs were pristine (a compromised dependency or runner would still produce an “authentic” attestation), and verification is online (it queries GitHub’s attestation store). It defends the realistic case — a swapped/forged release asset — not every conceivable build-time compromise.

trimwire’s security model assumes:

  • you trust the binary you’re running (download a release, audit the source, or build from the repo); and
  • your localhost is not compromised.

Given those, the trust boundaries are simple: Claude Code is the local client whose requests are trusted as your own; the Anthropic API is the trusted HTTPS destination; and you, the local user, fully control the gateway process and the on-disk session files (which are not encrypted at rest — but contain no trimwire-added secrets).

  • DoS resistance — trimwire listens on localhost only; if a malicious local process can reach it, your machine already has bigger problems.
  • Encryption at rest of the ledger — it stores no request bodies, only byte counts and hashes.
  • Localhost-only bind; HTTPS to Anthropic with cert validation.
  • No telemetry, no summarizer model calls, no system-prompt changes — unless you opt in.
  • Ledger stores counts/hashes only; audit is off by default and content-free when on.
  • On any error or a request it can’t safely prune, trimwire forwards your original bytes unchanged — worst case is “no pruning this turn,” never a broken request.

For privacy specifics (what the telemetry payload contains, retention), see the Privacy policy and TELEMETRY.md. For trust/ToS questions, see the FAQ.