Add a Redacted<T> newtype in nuncio-core whose Debug, Display, and serde render a fixed placeholder, so a wrapped credential cannot leak through a derived Debug or serde_json; the value is reachable only via an explicit expose_secret(). Migrate the CalDAV/CardDAV account auth tokens to it and drop their hand-written redacting Debug impls. Document the logging redaction policy (what must never be logged vs what is allowed) in docs/LOGGING.md and the redact module doc. Add a cross-crate no-secrets canary that installs a capturing subscriber, drives auth reject, account add, mock and JMAP-wiremock sync, mark-read, send, a filter rule-fire, a webhook dispatch, and CalDAV/CardDAV sync with sentinel secrets/PII, and asserts none appear in captured telemetry. Refs #363 Co-authored-by: James Maes <jmaes@dmdbrands.com>
2.8 KiB
2.8 KiB
Logging & redaction policy
Nuncio's telemetry (tracing spans and events, plus anything that reaches a log
sink through a Debug / Display / serde rendering) is a plaintext side
channel. This document is the authoritative statement of what may and may not
appear in it. It is enforced mechanically by two things:
nuncio_core::redact::Redacted<T>— a newtype whoseDebug,Display, andserdeall render the fixed placeholder<redacted>, so a secret field wrapped in it cannot leak even through a derivedDebugon the containing struct. The inner value is reachable only via an explicit, greppableexpose_secret()/into_inner()call. The module doc comment restates this policy next to the type.- the no-secrets canary —
crates/nunciod/tests/no_secrets_canary_test.rs, a workspace test that drives a representative slice of the instrumented code paths with known sentinel secrets and asserts none of them appear in captured telemetry. It is the regression net: if a future change starts logging a secret, this test fails.
MUST NEVER be logged (in any encoding)
- Message bodies, subjects, and snippets.
- Sender / recipient email addresses and any other message correspondents.
- Account credentials: passwords and app-specific passwords/tokens.
- gRPC bearer tokens.
- Keyring secret values (the material stored in the OS vault) and any derived key material (encryption keys, HMAC signing keys).
- vCard / iCal personal data: contact names, phone numbers, addresses; event summaries, locations, attendees, and descriptions.
MAY be logged (identifying but non-sensitive)
Operating the daemon is impossible without correlation identifiers, and none of the following reveals content or grants access:
- Opaque ids: account id, message id, rule id, folder id — and UIDs.
- Counts, sizes, and durations.
- Operation / rule / transport / action kinds (the discriminant only, never its payload).
- Folder names, hostnames, ports, and the per-request
request_id. - The keyring lookup key (e.g.
nuncio/<account-id>) — an identifier that names a vault entry but is not itself the stored secret.
Guidance for contributors
- Prefer logging an id or a kind over the value it points at. If you need to correlate a log line with a message/event/contact, log its id/UID, never its content.
- When a struct must hold a credential/token/key, make the field type
Redacted<T>rather than relying on a hand-writtenDebug. Reach the value only at the point of use, viaexpose_secret(). - Never interpolate a whole config/request struct into a log or error with
{:?}unless every sensitive field it holds isRedacted<T>(or otherwise self-redacting). - If you add an instrumented path that could plausibly touch any of the "MUST NEVER" values, extend the canary to drive it with a sentinel.