04Docs
Observe & PII
Observe watches your service’s egress and reports metadata, not payloads. Each call leaves as its shape: host, templated path, method, status, timing, and the actor who made it. Values never leave your process, and the PII denylist is compiled into the sensor so no policy can reach past it.
What the sensor captures
One event per outbound call, metadata only. Ids in the path are templated inside your process before the event is built: /v1/customers/cus_9f2 becomes /v1/customers/{id}. UUIDs, numeric ids, hashes, and vendor ids all collapse to {id}.
| Field | What it holds |
|---|---|
| host | Destination host. |
| path | Templated path. Ids collapse to {id} before the event is built. |
| method, status | HTTP method and response status. |
| duration | Time to response, in milliseconds. |
| sizes | Request and response byte counts. |
| actor | The attributed actor and kind. An opaque internal id, never an email. |
| query names | Query parameter names only, capped at 64. Values are never read. |
Never on the wire
These never leave your process, on any route:
- Request and response bodies. Unrecognized routes contribute top-level field names and types only, on the first sighting of a shape.
- Query-string values. Names only, on shape samples.
- Header values. Headers are scanned only to strip
X-Bounded-*from egress. - Prompts, completions, and messages of LLM calls. They are in no safe-field list.
Authorization is never read. The wire envelope has no body slot at all.
Shape learning
The first time the sensor sees a new route it emits one shape event: the top-level field names and their JSON types, nothing else. Fields with PII-shaped names are dropped before the shape is built, so even their names never appear. Shape events are deduped in memory and rate-capped.
{
"class": "shape",
"host": "api.internal",
"path": "/v1/customers/{id}",
"fields": {
"amount_cents": "number",
"currency": "string",
"created": "number"
}
}Stories and boundary suggestions are built from shapes and safe fields, not from payloads.
The layered posture
Capture is default-deny. Three layers decide what can be recorded, and two of them run again on the server, independent of the sensor.
| Layer | What it holds |
|---|---|
| Allowlist | Only manifest-listed safe fields are read. Primitives only, strings capped in length. |
| Denylist | About 45 compiled patterns (email, phone, ssn, tokens, cards, addresses, names) are refused in the sensor and re-checked server-side. A manifest that lists customer_email still cannot capture it. |
| Value scrubber | At ingest, values matching email, JWT, or Luhn-valid card patterns become [REDACTED:...] and bump a scrubbed:true counter you can see. |
Progressive disclosure
Capture widens by policy, not by code. Adding a named safe field to the manifest is an explicit, versioned change: every event carries the policyVersion it was captured under, so you can see when capture changed and what it covered.
The denylist is not one of those levers. It is compiled into the sensor and cannot be overridden by any policy or manifest.