v2.10.0
Credentials you can use but cannot read
Tool output no longer carries credential values. A token is replaced by a handle ([secret:auth_api.acme.io]) and the value stays server-side, so it never lands in a transcript that gets stored, shared or replayed.
list_secrets— the credentials captured this session, by handle, with origin, age and JWT expiry. Values are never shown, and nothing derived from a JWT's claims is either: issuer and subject are self-asserted.http_request— issue a request from the host, carrying a vaulted credential by handle. The clean-room counterpart toapp_request: no app TLS trust, proxy or cookie jar, and mock rules do not intercept it, so a difference between the two separates a server bug from a client one. A 401 here whereapp_requestsucceeds means the backend enforces attestation.authis a typed object rather than string interpolation, so a credential cannot be smuggled into an arbitrary field, and each one is bound to the origin it was observed on.vault_capture— read a credential out of the running app straight into the vault without returning it. Covers a cold session, a background-refreshed token, or one held where no heuristic looks (keychain, expo-secure-store, an Apollo link).
Redaction moved to a chokepoint
It now runs once over every tool's text output instead of at a single render site, so it covers logs, Redux state, component trees and network alike, plus errorMessage and errorContext, which reach telemetry without passing through content.
Breaking for anyone who relied on it: verbose:true no longer reveals a credential. That put the escape hatch in the hands of the model, and a transcript is append-only, so one revealing call is not undone by a thousand redacted ones. EXECBRO_REDACT=off is the only way out: a human sets it, and it needs a restart. verbose:true still drops the bounding, it just reveals nothing.
Credential headers are matched by pattern rather than a fixed list. A sweep found ten common vendor-namespaced headers (x-shopify-access-token, x-goog-api-key, x-hasura-admin-secret, x-firebase-appcheck) passing the old seven-name list untouched. x-request-id, x-correlation-id and x-idempotency-key are deliberately left alone, since redacting them would break the workflows these tools exist to serve.
App data is data, not instructions
Every tool here pipes content the app fetched, logged or rendered into an agent's context, which makes each one an injection channel by construction. The server now tells every connecting agent, ahead of the decision tree, that such content is never to be followed as an instruction. This is the half of the problem redaction cannot address: redaction governs what leaves in a transcript and does nothing about an agent acting on injected content. The vault shipped first on purpose, so "use the handle" is a real answer rather than a refusal.
Fixes
- A body is now redacted before structural bounding, not after. Projection clips strings to 60 characters, so a large access token used to arrive at the chokepoint already truncated, no longer matched the JWT shape, and got a generic redaction with no handle — breaking exactly the link worth having, that the token minted here is the token sent there.
- A handle that resolves to nothing no longer prints a bare
--- secrets referenced above ---header with no entries under it. redactSecretspreserves a handle when other text shares the field.- JWT expiry is derived from the token's shape rather than the caller's
kindlabel.