Privacy in logs and model prompts
Inbox Agent is private-by-default. Mail bodies, attachment content, OAuth tokens, token ciphertext, and secrets must never appear in routine logs or model-prompt assembly. This implements NFR §§7–10 and §16, AI §12, and Sync §16 (MAIL-69).
Redaction is always on. It is not gated by ENABLE_SEND, ENABLE_DELETE, or any other feature flag.
Package responsibilities
| Package | Role |
|---|---|
@inbox-agent/security | Shared field-name and pattern redaction. Single source of sensitive-key rules. |
@inbox-agent/observability | Structured logger. Every record is redacted before it reaches a sink. |
@inbox-agent/ai | sanitizePromptContext and buildModelPrompt omit bodies, tokens, and secrets. |
@inbox-agent/testing | assertNoSensitiveLeaks / findSensitiveLeaks for CI privacy assertions. |
Do not implement redaction in apps/web. UI hosts must log through @inbox-agent/observability.
What is redacted
Field names (case and punctuation insensitive) include mail bodies (body, htmlBody, textBody, snippet, attachment content), tokens (accessToken, refreshToken, authorization), token ciphertext (tokenCiphertext, ciphertext), and secrets (clientSecret, apiKey, password, privateKey).
In-string patterns include Bearer tokens, JWTs, PEM private keys, and access_token= / client_secret= assignments. Binary payloads (Uint8Array / Buffer) are replaced with a length marker so raw mail bytes cannot dump into logs.
Safe metadata stays visible: messageId, accountId, folder names, subject, timestamps, bodyHash, and bodyTextRef.
How to log
import { createLogger } from "@inbox-agent/observability";
const logger = createLogger({ service: "sync" });
logger.info("sync.upserted", { messageId, folderId, subject });
logger.error("oauth.refresh_failed", { accountId, error });
If a caller accidentally passes body, accessToken, or tokenCiphertext, the sink receives [REDACTED]. Sync, OAuth, and worker code should still avoid passing those fields.
How to build model prompts
Never spread a provider payload, token bag, or raw message entity into a prompt.
import { buildModelPrompt, sanitizePromptContext } from "@inbox-agent/ai";
const prompt = buildModelPrompt(providerPayload);
buildModelPrompt keeps subject/sender/received-at metadata and replaces bodies, tokens, and secrets with [OMITTED]. Classification that later needs mail text must take an explicit, audited path — not a leftover OAuth field.
Tests
tests/privacy/redaction.test.ts (MAIL-162) and tests/privacy/token-secret-redaction.test.ts (MAIL-145) assert:
- a synthetic fixture body is absent from a sync-style log line;
- OAuth tokens, token ciphertext, and
tokenEncryptionKeyare absent from error logs; - the model prompt helper strips bodies, tokens, and secrets;
assertNoSensitiveLeaksfails when a needle is present.
Use only synthetic fixtures under fixtures/mail. Production mailbox content is forbidden.
Operations
Alerting and dashboards must use identifiers and health metadata, not message content. If a log line is suspected to contain mail or credentials, treat it as an incident: rotate the exposed credential, drop or redact the retained log window, and add a regression needle to the privacy tests.
