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

PackageRole
@inbox-agent/securityShared field-name and pattern redaction. Single source of sensitive-key rules.
@inbox-agent/observabilityStructured logger. Every record is redacted before it reaches a sink.
@inbox-agent/aisanitizePromptContext and buildModelPrompt omit bodies, tokens, and secrets.
@inbox-agent/testingassertNoSensitiveLeaks / 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:

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.