Redaction
Telescope serves its buffers to AI agents and, with a file sink, writes them to disk. Two layers keep secrets out of both: the credential lists for HTTP records, and a host-supplied text redactor for every record that carries free text.
Credential lists (HTTP)
TelescopeStore.recordHttp masks credential values before it buffers a record. The request headers
authorization, proxy-authorization, cookie, set-cookie and x-api-key, and credential keys in a
JSON or form-encoded body (password, token, access_token, ...), read ********. Names match
case-insensitively and at any depth. An empty value (null, false, '', [], {}) stays visible.
Extend the lists with TelescopeRedaction.hideRequestHeaders, hideRequestParameters and
hideResponseParameters. Additions merge with the defaults and never replace them. An adapter that holds a
body as a Dart Map runs it through TelescopeRedaction.redactParameters(data, keys) or
redactBody(body, keys) before it stringifies or truncates it, because the store cannot mask a body it
cannot parse.
The text redactor
TelescopeRedaction.redactor is a host hook, String Function(String)?, that is null until you set it:
TelescopeRedaction.redactor = (String text) =>
text.replaceAll(RegExp(r'\b\d{13,19}\b'), '[card]');
When it is set, the store runs it at insert over every text field of a log, event, exception and HTTP record, before the record enters its queue or its stream:
| Record | Text the redactor sees |
|---|---|
LogRecordEntry |
level, message, logger name, error, stack trace |
ExceptionRecord |
exception type, message, stack trace, isolate |
EventRecord |
event type, interaction fields, and every String value and every map key inside the payload's nested Maps and Lists |
HttpRequestRecord |
URL, method, header values, request and response body, request id, interaction fields (after the credential key masking above) |
A subtree nested past 64 levels is masked whole.
Contract
- The redactor must be pure and total: it takes one String and returns the masked String.
- A redactor that throws drops the record. The record is neither buffered nor emitted on its stream, because a record that could not be masked is never let through unmasked.
- A record buffered while no redactor is registered is stored as is. Setting a redactor later does not rewrite what is already in the buffers.
TelescopeRedaction.resetForTesting()unregisters the redactor and restores the credential lists.
The redacted flag
The log, event, exception and HTTP records carry a read-only redacted getter, serialized in toJson. It
is true only for a record the store's redaction pass produced with a redactor registered. The
constructors take no redacted argument, and HttpRequestRecord.copyWith returns an unredacted copy, so
no caller can build a record that a persisting consumer would take for masked. TelescopeFileSink relies
on this: it writes only records whose redacted is true (see File sink).
Per-record entry points
redactLogRecord, redactExceptionRecord, redactEventRecord and redactHttpRecord apply the redactor to
one record. The store calls them; a caller outside the store rarely needs to. Each returns null for a
dropped record, so redactHttpRecord returns HttpRequestRecord? and a caller adds a null check.
JSON-safe event payloads
recordEvent stores any payload value that jsonEncode cannot write (a DateTime, a Duration, any
object) as its toString(), and a non-String map key likewise. One bad payload can no longer fail a whole
ext.telescope.events response. A payload that is already safe keeps its identity. This runs with or
without a redactor.