Marking a contract field sensitive
A resource's invoke inputs and outputs ride the debug wire on every call under
--inspect — which is every watch session. Nothing scrubs them: the kernel's
substring scrubbing has one call site, the resource-Created event's properties, and
log attributes match on exact values.
That was survivable while a credential was a held instance whose material never
crossed a dispatch boundary. It stops being survivable the moment auth is a
dispatched Telo.Invocable, because the token becomes an invoke output.
The annotation
Mark the contract property that carries the value:
outputType:
kind: Telo.JsonSchema
schema:
type: object
properties:
headers:
type: object
additionalProperties: { type: string }
x-telo-sensitive: true
The kernel then carries that value as [redacted] in trace payloads. The key is
kept and only the value replaced — a payload that silently loses a key reads as a
value that was never produced.
Where it is read from
Only where a contract reaches it — a node inputType / outputType reaches
through properties, items, additionalProperties / patternProperties or a union
branch, following $ref into a $defs entry or a named shape. It is read from a
data schema rather than from a kind's own schema:, because a contract is what the
kernel binds and validates at dispatch.
Written where no contract reaches it — a kind's configuration, a $defs entry
nothing references, a named shape no contract uses — it is an unknown keyword in an open schema: it
validates, it ships, and it does nothing. For a security control that is the worst
available failure — an author marks a token, sees no error, and puts it on the wire
anyway — so a misplacement is an error (SENSITIVE_ANNOTATION_MISPLACED) rather than
a silence.
What the walk covers
- A property, by name.
- A map's values —
additionalProperties/patternProperties. A headers or query bag carries no property names to walk, so without this the whole bag is emitted verbatim. - Array elements, through
items. - The whole value: marking the contract's root is legal. A contract whose entire output is the secret is the simplest shape there is.
A marked node is redacted whole, so nothing below it is walked.
Declare it on the abstract
Mark it where the contract is owned, so every implementation inherits it rather than
each one remembering. Http.Credential marks its own headers and query — inbound
as well as outbound, since the request handed to a credential already carries the
client's default headers.
What it does not cover
A thrown error's structured data is not redacted. ERR_HTTP_STATUS carries up
to 8 KB of provider response body there by design, so do not put a credential in an
error's data and expect this to hide it.
A contract that cannot be resolved withholds the payload whole rather than guessing — the dispatch that follows raises that same failure with its own code, so nothing is swallowed.