Security Flare — security whitepaper
Audience: a prospect's security or risk officer evaluating whether to trust Security Flare with their ISO/IEC 27001 evidence. This document is written for non-engineers. Every claim links to the underlying code or architecture decision so a technical reviewer can verify.
TL;DR:
- Customer data lives in Sydney (
ap-southeast-2) under Cloudflare Data Localisation Suite, never leaves the region. - Every record is row-level-security isolated per organisation in Postgres. The exceptions are catalogued, audited, and bounded.
- Microsoft authentication; Security Flare never stores customer passwords.
- Evidence files land in Cloudflare R2 with Object Lock in compliance mode and 7-year retention. Nobody — including Security Flare staff — can delete them during the lock period.
- Forensic Worker logs ship to R2 with the same retention. Customer audits get the full access trail on demand.
- The full architecture is public in the ADRs at
docs/adr/.
1. What does Security Flare do, and what data does it touch?
Security Flare is a multi-tenant SaaS that helps Australian Microsoft-centric MSPs and their customers reach and maintain ISO/IEC 27001:2022 certification. The first module is an ISMS readiness tracker.
To do that, Security Flare reads (never writes) from your Microsoft 365 tenant via Microsoft Graph: Conditional Access policies, Intune device inventory, audit logs, security events, sensitivity labels. Those map to ISO 27001 Annex A controls.
You can also upload evidence files (screenshots, PDFs, exports) manually.
Files go to Cloudflare R2 under
evidence/{org_id}/{control_id}/{evidence_id}/{sha256}.{ext}. The
SHA-256 is recorded so any post-upload tampering is detectable.
We don't read your email, files, OneDrive, SharePoint, or Teams messages. The Graph permission scopes we request are listed in §3 below.
2. Tenant isolation — how is my data kept separate from another
customer's?
Two layers, defence in depth.
2.1 Postgres Row Level Security
Every tenant-scoped table has Postgres row-level security enabled with the policy:
USING (organisation_id = current_setting('app.org_id', true)::uuid)
The application sets SET LOCAL app.org_id = $1 at the start of every
authenticated transaction. Postgres physically refuses to return rows
from a different tenant — the database is the boundary, not the
application code. Even a bug in the application can't cross tenants
because RLS is the floor.
CI fails if a new table lands without RLS — the static check is at
packages/db/tests/rls-coverage.test.ts.
2.2 BYPASSRLS sites are audited
Four classes of operation legitimately need to step outside RLS:
| Class | Example | Trust root |
|---|---|---|
| First-sign-in bootstrap | Find-or-create an org on a brand-new Entra tenant | Microsoft-signed OIDC token (verified) |
| Operator console | List all customer orgs from /admin |
ADMIN_TOKEN bearer, constant-time compare |
| MSP cross-tenant rollup | Aggregate readiness across an MSP's child orgs | Session + an RLS-pre-filtered whitelist |
| Cron / system | Daily evidence collection per customer | Cloudflare scheduled handler (no HTTP path in) |
Each individual site is documented in ADR-0013 — 21 call sites audited as of end of May 2026. The standing rule: any new bypass site must be added to the audit + bounded by a specific id, specific token, or RLS-pre-filtered whitelist. The reviewer's question is always "show me where this bypass is bounded" — that boundary is made explicit in code and doc.
2.3 Multi-tenant model: MSP + customer orgs
An MSP organisation can parent customer organisations. The
organisations RLS policy allows the MSP to see their own row plus
any child orgs (parent_msp_id = app.org_id) — Postgres enforces it,
not application code. Customer orgs see only themselves.
3. Authentication — how do users sign in?
Microsoft Entra ID OIDC. Security Flare is a multi-tenant Entra app.
Customer users sign in with their Microsoft work account; the
id_token issuer, audience, nonce, and signature are validated against
the Microsoft JWKS cached in Cloudflare KV.
Security Flare never sees, stores, or proxies customer passwords.
3.1 Out-of-band verification for new tenants
The very first user from a new Entra tenant lands in org.status = 'pending' with a one-shot verification code. A Security Flare operator
phone-verifies the customer against their company's
publicly-listed ABN/ASIC contact details before flipping the org to
active. Until then the customer can browse the catalogue but can't
write — see ADR-0002.
This prevents domain-spoofing — an attacker who registers an Entra tenant on a look-alike domain can't auto-admit themselves to an existing org.
3.2 Sessions
Sessions live in Cloudflare Durable Objects, not JWTs. Specifically:
- One Durable Object per organisation. Holds the live session set.
- The browser cookie carries only the session id (256-bit random).
- HMAC-signed cookie. Cookie name is
__Host-sf-session, prefixed for the strictest host-binding. Cookie flags:Secure,HttpOnly,SameSite=Lax,Path=/. NoDomainattribute (which would weaken the host binding). - Session TTL: 8 hours.
- Instant revocation: delete the session in the DO; the next request 401s. We don't have to wait for a JWT to expire.
3.3 Microsoft Graph application tokens
The cron evidence pipeline uses Microsoft Graph application
permissions (not delegated). Customers grant admin consent via
/integrations ([ADR-0009 + #79]); tokens cache in Cloudflare KV
encrypted with an AES-GCM key stored in Cloudflare Secrets Store.
Tokens are keyed by Entra tenant id — a token for tenant A can't be
used against tenant B because the cache lookup is itself
tenant-scoped.
The Graph scopes Security Flare requests (read-only):
Policy.Read.All— Conditional Access + authentication policiesReports.Read.All— sign-in + audit + service reportsDirectory.Read.All— users, groups, app registrationsSecurityEvents.Read.All— Microsoft 365 Defender alertsIdentityRiskEvent.Read.All— Identity Protection risk eventsAuditLog.Read.All— Entra audit + directory activityDeviceManagementConfiguration.Read.All— Intune configDeviceManagementManagedDevices.Read.All— Intune device inventoryRoleManagement.Read.Directory— privileged role assignmentsInformationProtectionPolicy.Read— sensitivity labels
No write scopes. No Mail, Calendar, OneDrive, SharePoint, or Teams content scopes.
4. Evidence integrity
Evidence files land in Cloudflare R2 with Object Lock in compliance
mode and 7-year retention on the evidence/, policies/, and
exports/ prefixes per
CLAUDE.md §3.4 and
ADR-0003.
What this means in practice:
- Once a file lands, it cannot be deleted or overwritten before the retention period expires — not by an attacker, not by Security Flare staff, not by a compromised root account.
- Each file's SHA-256 fingerprint is recorded in the
evidencetable at upload time. Post-upload tampering is detectable on the next read. - Every evidence-collection run from the Graph pipeline writes a row
to
evidence_runsrecording: tenant, control, Graph endpoint, response status, output hash, R2 key, timestamp. The run record itself is an audit artefact.
5. Audit trail
Three layers, each with a different durability profile (see ADR-0014):
Layer 1: forensic Worker logs
Every authenticated request emits a structured JSON line capturing
{org_id, user_id, user_role, method, path}. Cloudflare Logpush ships
these to R2 with Object Lock + 7-year retention — same durability as
evidence files.
When you ask for an access report for a customer audit, this is what
gets surfaced. The data is structured so a 5-minute rclone + jq drill
answers "what did Alex at Acme do at 14:32 on 2026-05-30?"
Layer 2: operational metrics
Cloudflare Workers Analytics Engine — request count, p95 latency, error rate per route. Standard SaaS observability, 30-day retention. We use this for on-call paging, not customer audits.
Layer 3: tenant audit log
The audit_log Postgres table records every tenant mutation in plain
English:
- Control status changes:
control.status_changed,control.bulk_status_changed - Risk register:
risk.created,risk.updated,risk.closed - Policies:
policy.cloned,policy.updated,policy.approved - Evidence:
evidence.uploaded,evidence.downloaded,evidence.deleted - Org lifecycle:
org.activated,org.suspended,org.unsuspended - Integrations:
graph.admin_consent_granted - Billing:
billing.customer_linked,billing.subscription_created, etc.
Customers see their own org's rows at /audit-log. Security Flare
operators see across all orgs (BYPASSRLS-bounded) at
/admin/audit-log. Both views are filterable + cursor-paginated.
6. Australian data residency
- Database: Neon Postgres, region pinned to
ap-southeast-2(Sydney). All queries and writes happen within Australia. - Object storage: Cloudflare R2 with jurisdictional restriction set
to
AU. Buckets refuse cross-region replication that would leave Australia. - Worker compute: Cloudflare's Australian edge locations. The Worker code itself runs on Cloudflare's global edge for low latency, but the regulated data never leaves the Sydney data plane.
- Cloudflare Data Localisation Suite: enabled. Cache keys, logs, rate-limiter state — all bound to the AU region.
- Privacy Act 1988 + OAIC Notifiable Data Breaches scheme: 30-day
breach notification window built into the operational procedures
(see
docs/operations/RUNBOOK.md§8).
7. Operational hygiene
7.1 Secrets
Every credential lives in Cloudflare Secrets Store, never in environment variables, never in KV, never in the database. This includes:
- Postgres connection string
- Cookie HMAC signing key
- Microsoft Entra app client secret
- Microsoft Graph token encryption key
- Stripe webhook signing secret
- Operator-only admin token
Secrets rotation is a wrangler secret put command — no redeploy
required; the Worker reads the new value at the next request.
7.2 Disaster recovery
Documented in docs/operations/RUNBOOK.md §10:
- Neon point-in-time restore: 7-day window on the free tier,
30 days on paid. Procedure: spin up a branch from the target
timestamp, validate, swap the
DATABASE_URLsecret. - Worker rollback:
wrangler rollback <version-id>is instant and DB-state-agnostic. - Migration rollback: forward-only — write an undo migration. Catastrophic cases fall back to point-in-time restore.
- Quarterly DR drill: recommended cadence to validate the above.
7.3 Code review + CI
Every code change passes through:
- TypeScript strict mode + lint (Biome)
- Unit tests (507 as of end of May 2026)
- RLS coverage check — CI fails if a new tenant table lands without RLS
- OpenAI code review on PR
The repository is small, contained, and reviewable: one Worker, one Postgres database, one R2 namespace.
8. Pen testing / customer security review
We support customer-driven pen tests against a staging environment.
Contact hello@securityflare.com.au to scope and schedule.
For document review:
- Architecture decisions — read
docs/adr/in order. ADRs 001 through 015 cover the full design space. - Operations — read
docs/operations/RUNBOOK.mdfor deploy, observability, recovery procedures. - BYPASSRLS audit — ADR-0013 walks every place we step outside row-level security and pins the isolation invariant.
- Repository hard rules —
CLAUDE.md§3 lists the non-negotiable invariants every change in the codebase must satisfy.
9. Compliance certifications
Security Flare is currently pre-certification — we are dogfooding our own product to walk the ISO 27001:2022 path. Target: certification within 12 months of GA. Until then we publish:
- The full architecture (ADRs).
- The full runbook (operations docs).
- Customer access logs on request.
- This whitepaper, updated as the system evolves.
The data-handling guarantees in this document are enforced by code and infrastructure, not by certificate. Verify them by reading the linked ADRs.
Appendix A — ADR index
| ADR | Topic |
|---|---|
| 0001 | TanStack Start as the SSR layer |
| 0002 | Out-of-band verification for new tenants |
| 0003 | R2 Object Lock + 7-year retention on evidence |
| 0004 | SPA mode deferral |
| 0005 | Neon WebSocket vs Hyperdrive |
| 0006 | TanStack Start foundation |
| 0008 | Prerender /trust /privacy /terms /status |
| 0009 | Cron + Queues + Workflows orchestration |
| 0010 | Per-user invitations security model |
| 0011 | Register architecture pattern |
| 0012 | Queues disabled until Workers Paid |
| 0013 | withServiceRole BYPASSRLS audit |
| 0014 | Observability strategy |
| 0015 | Stripe billing MVP |