PMH-SEC Matrix

38 controls.
All Live or Shipping.

Every control is a real artifact — not a checkbox. Each row maps to a verifiable technical enforcement. Customers receive the PMH-SEC matrix during onboarding and on request.

Control status at a glance

Live35
Shipping2
Roadmap1

Total38
PMH-SEC v1.0

Full security control matrix

Controls are enforced technically — not declared administratively. Each row is verifiable by the customer during onboarding or via the audit log.

Filter:35 Live2 Shipping1 Roadmap
Control IDTitleDescriptionStatus
SEC-001CORS allowlistAPI server CORS header Access-Control-Allow-Origin is statically configured to a fixed allowlist of known dashboard origins. No wildcard (*).Live
SEC-002Bearer JWTAPI authentication uses short-lived (15 min) HS256 JWTs signed with a server-held secret. No Bearer tokens in URL query strings.Live
SEC-003Workspace membershipRow-Level Security policies on every Supabase table ensure a JWT's workspace_sid claim is checked against the workspace_id column on every query. Cross-workspace SELECT is impossible.Live
SEC-004Viewer role blocks mutationsWorkspace roles: viewer (read-only, no send, no key creation), editor (send, no key/grants), admin (full access). Role is embedded in the JWT and enforced server-side. Viewer cannot trigger mutations.Live
SEC-005Editor+ for sendEmail injection endpoint POST /v1/send requires editor or admin role. A viewer-scoped token is rejected with 403 before any business logic runs.Live
SEC-006Optional MFA gateWorkspace owners can enforce MFA (TOTP or passkey) as a second authentication factor. When enabled, the MFA challenge must be completed before any sensitive operation.Live
SEC-007Rate limitsAPI gateway enforces per-workspace rate limits: Starter 60 req/min, Growth 300 req/min, Scale 1,000 req/min, Enterprise custom. Exceeded limits return HTTP 429 with Retry-After header.Live
SEC-008API keys never returnedPOST /v1/keys returns the key value only once — at creation time. Subsequent GET /v1/keys/:id returns a masked value (pmta_live_****_xxxx). Keys are stored as a bcrypt hash in the database.Live
SEC-009SMTP passwords hashedSMTP credentials (username/password for port 587 submission) are hashed with bcrypt before storage. The plaintext password is never written to disk or logs.Live
SEC-010Safe credential viewsSupabase Row-Level Security policies prevent service_role and anon keys from reading sensitive credential columns (smtp_password_hash, api_key_hash). Only helper functions running as service_role insert/update these columns.Live
SEC-011Revoked SELECT on credsSupabase RPC function revoke_credential(cred_id) sets a revoked_at timestamp. The KumoMTA credential provider excludes any credential with a non-null revoked_at from relay authentication, with no need to delete the row.Live
SEC-012Append-only auditThe audit_events table has no UPDATE or DELETE permissions for any role including service_role. Rows are only ever inserted. Audit entries cannot be retroactively altered or removed.Live
SEC-013Audit on send/keys/grantsEvery email injection (send), API key creation/revocation, and credit grant/debit is written to the audit_events table synchronously before the operation completes. The audit event ID is returned to the caller.Live
SEC-014Dev seed gatedThe /seed/dev path that pre-populates the dev workspace with test credentials, domains, and keys is only callable when NODE_ENV=development. It is not present in production builds.Live
SEC-015Kumo metrics lab bypass gatedKumoMTA exposes a /metrics endpoint for Prometheus scraping. In lab mode, this endpoint is accessible without authentication. In prod mode, it is accessible only from within the trusted VPC network range, blocked by VPC Security Group rules at the network layer.Live
SEC-016Bootstrap balance 0New workspaces are created with a credit wallet balance of 0. The first credit grant requires an admin-scoped JWT and a separate operator action. A workspace cannot send email until credits are explicitly granted.Live
SEC-017Credit grant admin/owner onlyPOST /v1/admin/credits/grant requires admin role. The grant operation is itself audited (SEC-013). The wallet balance is checked before every SMTP injection — send is rejected at the MTA layer if balance is insufficient.Live
SEC-018SMTP STARTTLSInbound SMTP submission (port 587) advertises STARTTLS. Clients that do not support STARTTLS cannot establish a session. The EHLO response includes the TLS version and cipher suite.Live
SEC-019Prod SMTP TLS requiredIn production mode, KumoMTA is configured to require TLS for all outbound delivery to external domains. Clear-text delivery is not permitted. TLS 1.2 or higher is required; SSLv3 and TLS 1.0/1.1 are disabled.Live
SEC-020Prod SMTP cert ACME renewalOutbound delivery SMTP certificates are issued via Let's Encrypt ACME (ACME protocol RFC 8555). Certificate renewal is automatic at 60-day intervals with a 30-day expiry headroom. Private keys are generated on-box; CSRs are submitted via ACME HTTP-01 challenge.Live
SEC-021Editor helperThe Supabase Edge Function pmh_editor_helper runs with service_role key and is used exclusively to write rows (insert/update) that require server-side-only columns (workspace_id, created_at, etc.). It is never callable from the browser and accepts only requests with a valid editor+ JWT.Live
SEC-022No service_role in browserSupabase service_role key is never embedded in client-side JavaScript or sent to the browser. All Supabase client instances in the browser use the anon key. Server-side operations (Edge Functions, API routes) hold the service_role key in environment variables only.Live
SEC-023DKIM secrets service_role onlyPrivate DKIM keys are stored in Supabase with RLS policies that deny all read access (anon, viewer, editor) and only allow insert/update by service_role. The key is decrypted in memory only at signing time and never exposed in logs or API responses.Live
SEC-024DKIM keys scoped per workspaceEach DKIM key pair is scoped to a specific workspace_id and sending_domain_id. A key from workspace A cannot be used to sign a message claiming to be from workspace B. Cross-workspace key reuse is architecturally impossible.Live
SEC-025Prod refuses lab flagsA Supabase function check_environment() reads the x-postmta-environment request header and compares it to the actual deployment environment. If lab is claimed but the deployment is prod, the request is rejected with 403 before any other processing.Live
SEC-026Verified From required prodPOST /v1/send checks that the From: header address matches a sending_domain that has passed DKIM verification (dkim_verified_at is not null). Unverified sending domains are rejected in production mode.Live
SEC-027DNS checklist scrubs private keysWhen a sending domain is verified, the PMH DNS checklist helper queries public DNS to confirm the DKIM TXT record is live. It then discards the DNS query response immediately — no DNS zone data is stored. The checklist does not retain any DNS zone file content.Live
SEC-028Shaping warmup laddersNew sending domains and IPs are placed on a warmup ladder: Day 1 caps at 100 emails/day, stepping up by 2× every 2 days, reaching full volume at day 14. The warmup state is stored in the shaping_policies table. Manual override requires admin role.Live
SEC-029Suppress lists honouredEvery email injection checks the suppressions table before delivery. A suppression match causes the message to be rejected at the MTA layer (SMTP 550 5.7.1 Suppressed), credited back to the wallet, and the event written to audit_events with reason code.Live
SEC-030Observability packPostMTA ships a Prometheus scrape target (Prometheus format) exposing: queue depth, delivery rate, bounce rate, complaint rate, credit wallet balance, SMTP connection count, TLS failure rate. Grafana dashboard JSON is available to enterprise customers.Live
SEC-031Signed backup GPGDatabase backups (WAL mode) are written to GCS with a GPG signature from a dedicated offline HSM-backed key. Backup integrity can be verified cryptographically before any restore operation. Backups are retained for 90 days.Live
SEC-032Air-gap signed bundleSoftware updates (KumoMTA binary + PMH agent) are packaged as signed GPG bundles. The signature is verified on-box using a pinned public key before any binary is executed. Updates are downloaded from a hardened update CDN; no direct internet access from the appliance is required.Live
SEC-033Delivery webhooks HMACDelivery webhook payloads are signed with HMAC-SHA256 using a per-endpoint secret. The signature is sent in X-PostMTA-Signature: sha256=<hex> header. Recipients are instructed to verify the signature before processing the payload.Live
SEC-034Reputation pulseThe reputation_pulse background job calculates per-sending-domain reputation scores hourly: complaint_rate (FBL data), hard_bounce_rate, spam_trap_hits, and sends to Google Postmaster Tools. Scores are stored in the reputations table and exposed in the panel.Live
SEC-035OIDC/SAML/MFA/AAL2Enterprise workspaces can configure OIDC (Okta, Azure AD, Google Workspace) or SAML 2.0 for SSO. MFA enforcement (TOTP or passkey) is configurable per workspace. Authenticator Assurance Level 2 (AAL2) is supported for MFA-gated operations.Shipping
SEC-036Multi-tenant isolation testPMH-SEC-036 is a smoke-gate test that runs on every deployment: it attempts to use a JWT from workspace A to read or write data belonging to workspace B, and fails the deployment if any operation succeeds. This test is automated in the CI pipeline and cannot be bypassed.Live
SEC-037Migration roundtripThe migration tool exports data from a source provider (SendGrid/Mailgun SES) to PMH format, then validates the import. A roundtrip test imports the export and compares record counts and content hashes to the source. The migration is accepted only if the roundtrip passes.Shipping
SEC-038Chaos failure injectionThe staging environment runs chaos experiments: random SMTP connection failures, Supabase unavailability, credit wallet inconsistency. The system must recover without human intervention within the SLA window. Production deployments require a passing chaos report from the preceding staging run.Roadmap
LiveFully implemented and verified
ShippingImplemented, final verification in progress
RoadmapRoadmap, not yet implemented

PMH-SEC-036: Multi-tenant isolation smoke gate

On every deployment, a smoke-gate test attempts to use a JWT from workspace A to read or write data belonging to workspace B. The deployment fails and rolls back if any cross-workspace operation succeeds. This test cannot be bypassed — it is a required step in the CI/CD pipeline and produces a signed attestation report.

Request the full matrix

The PMH-SEC matrix is available to customers and prospects under NDA. Fill in the contact form and mention "PMH-SEC matrix" in the use case field.

Request access