Security

The four questions your auditor will ask before you sign.

One short page for the finance, healthcare, and government buyers we now onboard. Where customer data lives — and what is never stored. Encryption at every hop. The exact Microsoft Graph permissions Wardwell requests, with per-scope justification. And the documented audit-log retention policy.

Residency · managed
TLS end-to-end
Read-only Graph
No file bodies
30-day rolling
Timestamps everywhere
01

Data residency & what is never stored

Tenant scan data sits on a managed Postgres instance. DATABASE_URL resolves from the platform's rdbms capability (declared in polsia.toml), and data residency inherits the managed region that provider runs in. Wardwell does not co-locate a database anywhere else.

Sub-processors

  • Managed Postgres host. One: the platform's rdbms capability. Tenant scan rows (Scan, ScanIssue, ScanFinding), waitlist signups, and the better-auth session table all live there.
  • Microsoft Graph.The scan walker reads site and file metadata against the signed-in admin's own Entra tenant. Token issuance starts and ends inside that tenant — Wardwell never holds a multi-tenant application token.

What is never stored

  • Raw file bodies.The walker reads SharePoint file metadata (path, etag, last-modified stamp) and walks redirect chains — it never copies the file contents into Wardwell's database.
  • End-user personal contact data. The scan does not pull Graph users, contacts, calendars, or any person-record API. The data subject is the tenant admin who registered the app.
  • End-user browsing history.Per-session activity outside the scanned hub URLs is out of scope; Wardwell's scan walker is bounded to the site list the admin registered.
  • End-user direct messages. Teams chat, Outlook mail bodies, and OneDrive personal content are never read. Wardwell holds no Graph scope that would let it.
02

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.The managed Postgres default encryption covers the full disk; table-level AES is layered on for any column carrying PII (such as a scan URL path that resolves to a person's page).
  • 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.
03

Microsoft Graph permissions & justification

Wardwell operates on a delegated Microsoft Graph token, not an application token. The scope list is exactly six values — nothing broader is requested, and nothing broader can land in the cache. The Entra app registration is single-tenant and consent is granted once per tenant by the registered admin.

openid profile email Files.Read.All Sites.Read.All offline_access

Per-scope justification

  • Sites.Read.All— read SharePoint site data across the registered hub's site collection. Used to enumerate the hub URLs Wardwell patrols. Read, never write; no Sites.ReadWrite.All or any write-capable scope is anywhere on the list.
  • Files.Read.All — read file metadata (path, etag, last-modified stamp). Used to assemble the redirect-chain picture per URL and surface broken-link candidates. File bodies are not pulled; only the metadata header is.
  • openid + profile + email— OIDC identity. Wardwell reads who signed in to display their name and email on the admin's profile. No contact graph, no People API.
  • offline_access— keeps the delegated token alive across runs. The cached refresh token is per-admin and rotated by Microsoft's token endpoint; revocation happens at account deletion.
Sites.Read.All
Files.Read.All
openid · profile · email
offline_access
04

Audit-log retention

The ScanFinding rolling-history table keeps the per-finding record an admin can read on the /issues page. Older rows are pruned by the platform's background job to a 30-day rolling window — that is the documented posture for any regulated buyer who asks "how long do you keep finding rows".

What this is — and is not

  • The 30-day window covers the per-finding rolling history only — ScanFinding rows. The parent Scan and the per-scan ScanIssue rows stay for as long as your tenant is on board; they are not part of the rolling window.
  • A weekly digest email (jobs/weekly-issues-digest.js) ships the same finding rows out of band so the audit trail isn't lost when the rolling window ticks over.
  • Better-auth's /api/authhandler records each session's issuance, refresh, and revocation — sign-in events carry a wall-clock stamp and stay for the session's documented lifetime.

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.