The data plane never fails closed
The proxy serves first and observes afterwards. Observation runs asynchronously behind a bounded queue, every capture stage is panic-isolated and counted, and the proxy never retries a request. An automatic retry in front of a payments API is a double-charge window. If pikopod’s own machinery breaks, your traffic still flows. That is the contract, and it is why observation failures increment a counter instead of surfacing as an error. See Data plane safety for the measurement.Redaction happens before the disk, not after
Values are classified and rewritten on the way in, not cleaned up later:- Credentials become typed placeholders.
- Identifiers become deterministic, format-preserving tokens, so
tok_8f3a91stays token-shaped and path templates still work. - Names and similar PII are dropped.
- Free text and unknown strings are dropped. The classifier fails closed on any string it cannot place.
5000 could be an amount, an account number or a timestamp, so key names are the only evidence, and an unrecognised key passes the value through. Keys that look like secrets, identifiers, card data, phone numbers or expiry dates are still substituted or tokenized whatever the JSON type, because a card number sent as a number must not ride through on its type. But "amount": 245000 under a key pikopod does not recognise is written verbatim.
This is a deliberate trade. Dropping every unrecognised number would discard most of what makes a baseline useful, and it is the one place redaction is not fail-closed. If that matters for a field, check it with pikopod inspect before you trust the recording. volatile_fields does not keep a field out of learning; it only stops its values from being tracked.
Detection is generic: key names, value shape, entropy. Never a list of provider-specific prefixes, so it does not silently stop working when you add a provider nobody anticipated.
One thing an imported contract changes, precisely. When an upstream has an imported spec, a response field whose schema declares an enum keeps a value on disk only if that value is one of the declared members. currency: NGN survives because the provider published [NGN, USD]; the same field carrying GHS or a free-text error is classified exactly as it would be without the spec. Only EXPLICIT and DERIVED enums count. An enum the model extracted from prose unlocks nothing, because relaxing redaction on a guess is not recoverable. Request bodies, headers, paths and every other field are untouched. pikopod up prints how many fields this applies to.
Verify it yourself rather than trusting this page:
The salt
Tokenization uses an HMAC keyed by a per-install salt stored at<data_dir>/.salt, generated on first run. The salt is what makes tokens correlatable within your install and meaningless outside it.
The file must be mode 0600. pikopod refuses to start if it is group- or world-readable, because another local user who can read it can correlate every token you have stored:
data_dir, and exclude it from anything you share.
Listener safety
Both servers bind127.0.0.1 by default.
Binding any non-loopback address requires a token, supplied through PIKOPOD_TOKEN or a token_file, never through a command-line argument, which is visible to every user on the box via ps. Tokenless listeners also reject requests carrying a foreign Host header, which blocks DNS rebinding from a browser on the same machine. The token authenticates the whole control surface and is stripped from every request before it can travel further.
The sandbox control plane under /_pikopod/ is unauthenticated on loopback, like the rest of the sandbox. It controls a fake, never a provider.
Serve over HTTPS with tls. Both certificate and key, or neither.
What leaves the machine
pikopod initiates no network traffic on its own. Everything outbound is something you configured, using your own credentials:
Every model row requires your own key. With no key configured, none of those three paths runs at all:
fix refuses, scenario create refuses, and a docs-URL import that needs rung 4 returns an error telling you so. pikopod never proxies a request through infrastructure anyone else operates. Your relationship is with the model provider you configured.
There is no telemetry, there are no accounts, and there is no license check.
Importing from a documentation URL fetches more than one page
If--spec points at an HTML page rather than a spec, pikopod climbs a ladder. You should know what that does before pointing it at an internal documentation host:
- Linked documents. It follows
hrefandsrclinks from the page to anything that looks machine-readable. There is deliberately no same-host restriction, because documentation sites legitimately host their OpenAPI document on a different domain. That means a page you do not control can direct the fetcher at a host of its choosing. The risk is bounded by size and content-type gates and by the fact that anything fetched must parse as a spec to be used, but if your threat model includes SSRF from an untrusted docs page, do not point--specat one. Fetch the spec yourself and pass a local path. - Embedded documents. The page itself, plus at most one hop into the site’s own reference index. Same host.
- Well-known spec paths on the same host.
- Model extraction. Only with your key. Assembles up to 240 KB of the site’s prose, preferring its
llms.txtindex, and asks your model to write an OpenAPI document from it. The result is markedLLM_EXTRACTEDand imported as a draft.
Third-party specs are untrusted input
A fetched specification is bytes from someone else’s server. Parsing is bounded on size (32 MiB), depth and reference expansion, runs panic-isolated, and a document that fails to normalize is reported as an error. It never silently retires the watcher. Remote and absolute$refs are refused outright; see Multi-file specs.