# Mailbox security model Mailbox reduces exposure; it cannot make mailbox access risk-free. Email bodies, addresses, subjects, credentials, and confirmation links are sensitive data. ## Protected by the module - IMAP is read-only and requires TLS. Certificate validation can be disabled only for a loopback bridge. - Username, password, and OAuth token payloads are encrypted with authenticated libsodium secretbox in account-scoped `mailbox_credentials` rows; `v3` envelopes bind ciphertext to the account ID, and plaintext legacy config is verified, migrated, then removed. - The account registry permits at most three profiles, prevents disabling/deleting the default, and scopes admin folder/message/attachment/test operations to the explicitly selected enabled account. Account and OAuth administration is POST+CSRF and superuser-only. - OAuth authorization uses HTTPS-only exact callbacks, Authorization Code + PKCE S256, expiring one-time state, verified token-endpoint TLS, bounded responses, and no redirects. Client secrets are accepted only from configuration or a secret-provider callback. - The encryption derivation secret stays in `config.php`, and `env:NAME` can keep the actual password outside the database. - Frontend access requires a logged-in ProcessWire user plus `mailbox-api`; confirmation approval additionally requires `mailbox-confirm-links`. - REST is same-origin/session-only, disabled by default, no-CORS, no-store, bounded and rate-limited. Every POST requires a valid ProcessWire CSRF token; `isLogin` alone grants nothing. - Dependency diagnostics are informational only. Mailbox never runs package managers, downloads PHP extensions, edits `php.ini`, or restarts PHP/web-server services from a request. - Settings discovery never accepts credentials or writes account state. Provider XML uses verified HTTPS, public-DNS resolution pinned into cURL, disabled proxies/redirects, strict response limits, XML network/entity rejection, and a small settings allowlist. - Verk/Kontor publishing is opt-in and sends only proposal ID, hostname, account ID, folder, message UID, and redacted workflow shape (mode, step cap, code-required boolean). External reviewers must also hold `mailbox-api` and `mailbox-confirm-links`; Mailbox remains authoritative and blocks requester self-approval. - Squad is separately opt-in and receives only bounded agent-safe message DTOs with URL targets replaced by hashes. Mailbox forces Squad cache, prompt cache, and web search off, never accepts caller-selected keys/system prompts/history, strips raw responses/tool history, and exposes no approve/execute tool. Message text still leaves the site for Squad's configured external provider. - Admin AI summary and reply drafting are AJAX-only, POST+CSRF, and reuse the PHP agent API plus `mailbox-api` boundary. Their prompts are fixed server-side, optional draft guidance is bounded, output enters the page only through `textContent`/textarea value, and AI cannot send. AJAX reply delivery is a distinct user submit requiring `mailbox-send` and enabled SMTP. - Agent DTOs redact executable URLs and raw paths. Confirmation execution re-resolves the URL locally from the message hash. - Confirmation requests require a different reviewer, a host allowlist, public DNS, pinned resolution, verified HTTPS, bounded output, no followed redirect, expiry, and single-use execution state. - Advanced confirmation workflows are separately gated. They allow only URL-encoded POST, one unambiguous form, bounded hidden fields, one confirmation-labelled submit, and optionally one code-like text field whose value is re-read from mail and verified by keyed fingerprint. They reject general form filling, uploads, passwords, JavaScript, and redirect following. - Raw message HTML is never inserted into the admin document. The HTML view uses a sandboxed, credentialless, no-referrer, CSP-restricted iframe after server-side removal of scripts, forms, frames, event handlers, unsafe links, and unsafe CSS; all active links are blocked by default. Remote images are also blocked by default. The explicit global `allowRemoteMessageImages` acknowledgement permits only HTTPS image/CSS URLs on public-looking hostnames and port 443. The separate `allowExternalMessageLinks` acknowledgement permits only absolute HTTPS anchors on the same public-hostname/port boundary, strips ping/download behavior, forces a new tab with no opener, and layers no-referrer policy across the admin response, iframe, embedded document, and anchor. Attachments remain forced downloads with no-sniff/no-store, safe names/types, decoded-size bounds, and the separate `mailbox-attachments` permission. - IMAP mutations are disabled by default and separately require `mailbox-write`. REST mutations are POST+CSRF; CLI mutations require `--execute`. Move/delete avoid expunge unless the caller supplies an explicit permanent-expunge confirmation, because expunge can purge other messages already marked deleted in the folder. - SMTP sending is disabled by default and separately requires `mailbox-send`. It is TLS-only through PHPMailer 6.12+, validates remote certificates except for an explicit loopback bridge, validates and bounds all envelope/body fields, and records only keyed recipient hashes/counts in the audit log. Set `$config->mailboxAuditSecret` for dedicated audit-hash key separation. CLI bodies use bounded stdin rather than process arguments. - Outgoing attachment names/types/counts and decoded bytes are bounded before PHPMailer receives them. Generated MIME is never returned. Optional Sent append happens only after SMTP success; append errors are redacted and never trigger a delivery retry. - Webhooks are disabled by default and contain redacted identifiers only. Signing keys stay outside module settings/database; delivery is HMAC-signed, encrypted at rest in the queue, deduplicated, public-DNS-pinned HTTPS-only, no-proxy/no-redirect, response-bounded, and suppressed during the initial index seed. - Admin folder lists, message summaries, and opened text views are cached only in the account-scoped `mailbox_view_cache` table. Both cache identities and payloads are keyed/account-bound, payloads use authenticated encryption, and mutations or account-setting changes invalidate the affected cache. ## Residual risks - A full server compromise can access both the database and the `config.php` secret and can therefore decrypt credentials. - Enabling remote message images discloses the viewing client's IP address, browser/network characteristics, and request time to image hosts. Unique URL tokens can identify the mailbox or exact message even in a credentialless, no-referrer iframe; hostname syntax checks cannot establish that a public-looking domain will never resolve to a private address from the browser's network. - Enabling external message links prevents the ProcessWire admin URL from being sent as Referer, but clicking still discloses a request, client IP/browser characteristics, time, and any message- or recipient-specific token embedded in the destination URL. A public-looking hostname can also resolve differently from the viewing client's network. - Database/configuration backups created by pre-release development builds may still contain old plaintext module configuration and must be protected or retired according to the site's retention policy. - Any trusted PHP code running inside the ProcessWire installation shares the server trust boundary. Low-level transport APIs deliberately do not implement per-call user authorization. - Roles with `mailbox-view` can switch among every enabled account in the shared Mailbox workspace. Use separate ProcessWire installations or an additional site-level authorization layer when different users must see different mailbox subsets. - Enabling the CLI trusts local OS users who can read and bootstrap the ProcessWire installation. Use filesystem permissions and a dedicated service account. - Sending message content to an external AI provider discloses that content to the provider even though link targets are redacted. Apply data-processing, retention, and jurisdiction rules before enabling an agent. - A confirmation GET is an external side effect and may be irreversible. Classification can be wrong; the reviewer must understand the target action. - A reviewed advanced workflow may perform up to three POST side effects on the originating allowlisted host. Hidden form values are controlled by the remote service and can change between requests; strict form-shape validation reduces but does not remove semantic risk. Use dedicated accounts and the smallest host allowlist. - HTTP success and disappearance of a confirmation-like form do not prove the remote business action succeeded. Mailbox reports status, response size/hash, redirect host, and step count; provider-specific verification remains the consuming integration's responsibility. - A key-provider outage or premature removal of an old named key makes ciphertext using that key unreadable. Keep previous keys through rotation verification and the applicable backup-retention window. - OAuth refresh-token revocation, provider policy changes, or loss of offline consent requires an administrator to reconnect the account. - Access and refresh tokens have mailbox authority and remain sensitive even though they are encrypted at rest. - Enabling SMTP increases account authority and creates an external side effect. A compromised authorized session can send mail as the configured account; use a dedicated identity, narrow role grants, outbound provider limits, and audit monitoring. - Outgoing attachments can disclose arbitrary caller-supplied data to recipients. REST/CLI base64 increases memory use up to the bounded 15 MiB request envelope. Incoming attachments are never forwarded implicitly; preserve `mailbox-attachments` authorization before fetching and reusing them. - Enabling IMAP Sent copies creates a second write side effect and can duplicate messages on providers that already save SMTP submissions. A failed append after successful delivery reports `sent_copy=failed`; retrying the SMTP request can duplicate the delivered message. - Reply/forward quote only the normalized text view. Incoming attachments are not copied implicitly; an authorized caller must fetch and explicitly reattach selected bytes. Optional Sent append is post-delivery and can coexist with provider-created copies. - Server-side IMAP search may reveal message metadata across every selectable folder to an already authorized reader. Whole-account searches are capped by folder/result limits; attachment-presence filtering may inspect MIME structures and is reported as truncated when the candidate cap is reached. - Attachment retrieval rejects an oversized total message before fetching its body, so a small selected attachment inside a large message may be unavailable. PHP IMAP structure parsing may still cause server-dependent downloads, so keep limits conservative. - The encrypted local index still exposes account IDs, account-bound keyed folder hashes, row counts, job states, UIDVALIDITY, and message timestamps. A compromise of both the database and active key source decrypts indexed metadata. The rolling recent window prunes older rows, can be stale, and is not an authoritative mailbox archive. - The encrypted admin view cache exposes account IDs, row counts, and refresh timestamps. It can be stale for the configured refresh window and may contain complete normalized text for messages an administrator opened; compromise of both the database and active key source decrypts it. - Webklex IDLE is a long-running blocking connection and may fetch more message data than the summary ultimately stores. Server capability, disconnect, duplicate, and supervisor restart behavior must be tested for each provider. - Autodiscovery candidates are advisory. DNS SRV and provider XML can be wrong or compromised, the email address is disclosed to the mailbox domain's `autoconfig` host, and a discovered endpoint receives credentials only after an administrator explicitly saves it and initiates a connection test. Review hostnames and keep certificate validation enabled. - A webhook reveals that an account/folder UID received mail at a given time and creates an external side effect. Endpoint compromise, signing-key compromise, delayed/replayed requests, DNS changes between jobs, or receiver bugs remain possible; receivers must validate signatures and timestamp freshness and persist event IDs for replay rejection. ## Operational requirements - Prefer a dedicated least-privilege mailbox and app password or narrowly scoped delegated OAuth client. - Keep OAuth client secrets outside the database and rotate them through the provider and external secret store. Use maintained `webklex/php-imap` releases; Mailbox rejects versions older than 5.3 because of a known security issue. - Prefer named `$config->mailboxKeys` or a local `mailboxKeyProvider` backed by Vault/KMS; keep database, configuration, and secret-provider backups protected separately. - Keep agent API, CLI, mutations, SMTP, and confirmations disabled unless required. - Use maintained PHPMailer releases; Mailbox rejects versions older than 6.12. Reconnect Microsoft OAuth after enabling SMTP so the administrator explicitly consents to `SMTP.Send`. - The optional Runtime screen installs only packages already pinned in the module `composer.lock`. It is superuser-only, POST+CSRF protected, accepts no browser-supplied package names or options, selects Composer only from trusted absolute paths, disables plugins/scripts, uses a process lock and timeout, discards process output, and validates PHPMailer afterward. It writes only the module runtime and never installs system packages/extensions, changes PHP configuration, or restarts services. - Grant permissions narrowly and review `mailbox-actions` logs. - Grant `mailbox-attachments` only to users and agents allowed to receive attachment contents; mailbox read permission alone is insufficient. - Keep background sync disabled unless local indexing is required. Run CLI queue/IDLE workers as a dedicated OS user under a supervisor with execution-time, memory, restart, and single-instance limits. LazyCron runs during web requests and must stay tightly bounded. - Keep webhook secrets in a secret manager/config provider, rotate them with an explicitly coordinated receiver overlap, and never place them in module settings, logs, or webhook URLs. - Test install, migration, secret-loss recovery, and uninstall only on a disposable site before production rollout. - Never put credentials, full message bodies, confirmation URLs, or query tokens in logs, tasks, prompts, or support tickets. Report vulnerabilities privately to the repository owner rather than opening a public issue containing sensitive details.