Security

The four questions your CISO asks before you sign.

One short page for the CISO, security gatekeeper, and the procurement lead running the security review alongside the legal review. Encryption at every hop. The access-control surface that gates every read and write. The documented incident-response ladder. And where the SOC 2 attestation stands today.

TLS end-to-end
Read-only Graph
AES at column
Least-privilege
Session forensics
SOC 2 in flight
01

Encryption at rest & in transit

Encryption at every hop. The platform's reverse-proxy layer (proxy.ts) enforces a strict-transport posture on the edge, and the database-side encryption is layered with table-level AES for the columns that carry PII.

What this means in practice

  • At rest.Managed Postgres default disk encryption covers the full volume; table-level AES is layered on for any column carrying PII (such as a scan URL path that resolves to a person's page, or the cached microsoftRefreshToken row).
  • In transit. proxy.ts sets a per-request CSP nonce, connect-src 'self' caps outbound calls, and all fetch() traffic from route handlers is forced TLS. http:// URLs are dropped at the build gate.
  • Header posture. A strict frame-ancestors 'none'policy blocks embedding; the CSP nonce rotates per request so a stale script tag cannot reuse yesterday's permission.
02

Access controls

The access-control surface is layered. Per-user reads and writes in Wardwell are gated by better-auth; admin pages run through a tighter requireAdmin guard; and the Microsoft Graph grant that drives the scan walker is the narrowest possible — exactly six delegated scopes, never broader.

Tenant admin gating

  • Per-user reads & writes. Every /api mutation runs through requireAuth() (better-auth) — the canonical seam is src/lib/require-auth.ts. Queries scope on where: { userId: user.id }, so a user can only see their own rows; there is no shared account and no shared cookie.
  • Admin pages. Anything under /admin runs through requireAdmin() (src/lib/require-admin.ts) — the role-gated guard that consults the same better-auth session.role field. A non-admin request short-circuits to 401 before any handler executes.
  • Session lifecycle.Better-auth stamps every issuance, refresh, and revocation — sign-in forensics carry the issuing IP and user agent, so "who was signed in at 14:03 last Tuesday" is a single page away. Account deletion tombstones the tenant row, nulls microsoftRefreshToken, and anonymizes ScanFinding rows in the same transaction — so the durable kill switch is the database row, not a third-party response.
  • MFA on the Microsoft side.Better-auth is the canonical seam in Wardwell; MFA posture is enforced on the customer's own Entra tenant — the tenant admin configures the Entra-tenant MFA policy when they grant admin consent, and Wardwell inherits whatever policy that admin set. Wardwell does not weaken or bypass that policy: the sign-in completes only after Entra's MFA check succeeds.

Principle of least privilege

openid profile email Files.Read.All Sites.Read.All offline_access
  • The exact six scopes Wardwell requests, on a delegated (not application) Microsoft Graph token. Admin consent is granted once per tenant by the registered admin in Entra.
  • What is not on the list is documented: no *.ReadWrite.* scope, no People API, no mail, no calendar, no Teams, no OneDrive personal. The walker cannot mutate tenant content, and it cannot issue scopes it does not hold.
  • offline_accesskeeps the delegated token alive across runs. The cached refresh token is per-admin and rotated by Microsoft's endpoint; revocation happens at account deletion via RFC 7009.
03

Incident response

A documented severity ladder, three detection surfaces, and one durable per-tenant kill switch. Every Sev1 is contained in a single transaction; every containment leaves an audit row a reviewer can read after the fact.

Severity ladder & comms window

  • Sev1 — customer data exposure. Direct contact to the registered tenant admin within 4 hours of detection. Containment runs in a single transaction (RFC 7009 Graph revoke + durable null of the cached refresh token + anonymization of ScanFinding rows).
  • Sev2 — degraded trust boundary. Direct contact within 1 business day. Examples: a non-2xx on the Graph token endpoint whose outcome string is partial_revoke_failed; an unrecoverable scan walker failure (per-scan error row on Scan.errorMessage) above the documented threshold.
  • Sev3 — minor UX or readout anomaly. Included in the next weekly digest and surfaced inline on the admin's /issues view. Examples: a transparent UI bug that does not affect data integrity.

Detection surfaces

  • Per-scan error rows. Scan.errorMessage on the scan row is the primary source for Sev2 — a non-empty errorMessage above the documented threshold is the trigger.
  • Weekly digest anomalies. The same jobs/weekly-issues-digest.js cron that ships the per-tenant summary also surfaces anomalous scan-step counts (significant deviation from the trailing baseline) — that flag elevates the row to a Sev2.
  • Inline admin report. A tenant admin (or anyone reviewing the audit surface) can flag a row straight to the security mailbox at wardwell-7@polsia.app — subject line Wardwell security incident.
Report a security incidentSubject line: Wardwell security incident. Same mailbox as the security-review contact — one inbox, documented.
04

SOC 2 roadmap

Wardwell is in private beta today — no SOC 2 letter yet. What follows is a roadmap, not an attestation: a TSC-by-TSC view of where the controls already run, what is staged, and the timeline the audit window opens.

Trusted Services Criteria · where the controls are today

  • Security (CC1–CC9). The bulk of what is already in production today — per-user admin gating, session forensics, least-privilege Graph grant, in-transit and at-rest encryption, audit-logged scan runs, and the documented incident ladder on topic 03.
  • Availability (A1). Tracks the managed Postgres posture and the cron-declared background jobs (the weekly-issues-digest cron in polsia.toml) — the same surface a singleton recovery exercise is run against.
  • Confidentiality (C1). Maps to the encryption lineage on topic 01 combined with the read-only Microsoft Graph posture on topic 02.
  • Processing Integrity (PI1). Maps to the per-scan audit trail — Scan, ScanIssue, and ScanFinding rows — every scan run records exactly what was observed.
  • Privacy (P1–P8). Deferred. The data subject for GDPR purposes is the tenant admin, and no end-user PII is collected (no People API reads, no contact or calendar pulls, no Teams message inspection); the Privacy criteria are staged alongside the broader audit window.

Read this next

Next step

Get on the security review list.

Drop your work email and we'll come back with the SOC 2 mapping, the full scope-by-scope justification, and a security-review call within one business day. NDA on request.

We use this address only to schedule the security review. No marketing list, no re-marketing.