# MySafeSigns — Security & Architecture White Paper

**Version:** 1.0
**Date:** 2026-04-28
**Owner:** SymbioTeK Pty Ltd (ACN 694 230 334)
**Audience:** IT security and compliance teams of councils, utilities, and government departments evaluating MySafeSigns for procurement.

---

## 1. Executive summary

MySafeSigns is a browser-based safety-signage compliance auditing tool. It is built around a deliberately narrow data-sharing surface: **audit photographs, GPS coordinates, site/client/auditor names and compliance findings stay on the auditor's device.** Only a single class of data — the captured sign photograph — is transmitted to a cloud AI service, and only when the auditor explicitly invokes AI-assisted detection. Account credentials and credit-balance metadata are stored in a Supabase database in AWS Singapore (`ap-southeast-1`); compute runs in AWS Sydney (`ap-southeast-2`).

The application enforces signed-webhook verification on its billing path, forced row-level security on its database, AES-256-GCM encryption on optional user-controlled backups, browser security headers (HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy), and sub-resource integrity on third-party JavaScript. Source-of-truth disclosures (sub-processors, residual risks, hosting regions) are published at `https://mysafesigns.symbio-tek.com/security.html`.

We do not hold ISO 27001 or SOC 2 attestations. We have not been IRAP-assessed. The architectural compensation for that gap is the on-device-by-default data model: a buyer who is uncomfortable with our certifications can still adopt MySafeSigns because most regulated data never leaves the auditor's device.

---

## 2. Architecture

> **[Figure placeholder — Trust-Boundary Diagram]**
> A non-patent-encumbered trust-boundary diagram showing: Auditor's Device (IndexedDB) | Browser HTTPS boundary | Netlify CDN (static assets) | Supabase Edge Functions (Sydney) | Supabase Database (Singapore) | Stripe (Australia) | Anthropic (United States).
> *To be inserted after patent-attorney clearance — see §11.*

The application is a static single-page web app served by Netlify. It runs entirely client-side in the browser after initial load. Three classes of network call leave the browser at runtime:

1. **Authentication and credit lookup** — to `auacwdtbncawmpjqcnal.supabase.co/auth/*` and `…/rest/v1/credits`. These calls carry the user's Supabase session JWT.
2. **AI detection (when invoked)** — to `auacwdtbncawmpjqcnal.supabase.co/functions/v1/vision-proxy`. The request body contains the user's UUID, the model preference, and the sign photograph as base64. The Edge Function checks credit balance, debits one credit, and forwards only the photograph to Anthropic's Claude Vision API.
3. **Stripe Checkout redirect** — to `checkout.stripe.com`. No card details touch our infrastructure.

Webhook callbacks from Stripe land at `auacwdtbncawmpjqcnal.supabase.co/functions/v1/stripe-webhook` and are verified using the platform-issued signing secret before any database write.

---

## 3. Data flow per data class

| Data class | At rest | In transit | Sub-processors that see it |
|---|---|---|---|
| Audit photograph (sign image) | Browser IndexedDB on auditor's device | TLS 1.2+ to Supabase Edge Function (Sydney) → Anthropic API (US), only when AI detection is invoked | Anthropic, Supabase (transit only — not stored) |
| Audit context photograph (wider environment shot) | Browser IndexedDB | Never transmitted | None |
| GPS coordinates | Browser IndexedDB | Never transmitted | None |
| Site name, client name, auditor name | Browser IndexedDB | Never transmitted | None |
| Compliance findings, notes, sign metadata | Browser IndexedDB | Never transmitted | None |
| Account email | Supabase database (AWS Singapore) | TLS 1.2+ to Supabase Auth | Supabase |
| Password (hashed) | Supabase database | Never transmitted in plaintext after initial set | Supabase |
| Credit balance | Supabase database | TLS 1.2+ on read/write | Supabase |
| Credit transaction log | Supabase database (immutable, append-only) | TLS 1.2+ on insert | Supabase |
| Card / payment details | Stripe vault | TLS 1.2+ direct to Stripe | Stripe (PCI-DSS Level 1) |

**Key architectural property:** the audit content (photographs except the AI-submitted sign image, GPS, names, findings) never crosses our trust boundary. A SymbioTeK insider compromise cannot exfiltrate audit content, because we don't have it.

---

## 4. Hosting and sub-processors

| Sub-processor | Country / region | Role | Data they receive | Their compliance |
|---|---|---|---|---|
| **Anthropic, PBC** | United States | AI vision model (Claude) | Sign photograph only, in the body of API requests | [SOC 2 Type II](https://trust.anthropic.com/), commercial-tier non-training commitment |
| **Supabase, Inc.** | DB: AWS Singapore (`ap-southeast-1`)<br>Compute: AWS Sydney (`ap-southeast-2`) | Auth, database, edge function runtime | Account email, hashed password, credit balance, transaction log | [SOC 2 Type II](https://supabase.com/security), HIPAA-eligible plan available |
| **Stripe Payments Australia Pty Ltd** | Australia | Payment processing | Customer card details, billing email | [PCI-DSS Level 1](https://stripe.com/au/legal/pci-dss) |
| **Netlify, Inc.** | Global edge CDN | Static asset delivery | HTTP request metadata only (URL, IP, user-agent) | [SOC 2 Type II](https://www.netlify.com/security/) |

We notify customers of sub-processor changes with 30 days' notice. The current canonical list is at [/security/sub-processors.html](/security/sub-processors.html).

---

## 5. Identity and access

### 5.1 End-user identity

End-users authenticate via Supabase Auth (email + password). Sessions are JWT-based; tokens live in browser local storage with the `Secure` flag and are short-lived (1 hour) with refresh tokens. There is no SSO or SAML at v1.

### 5.2 Row-Level Security (RLS)

All tables that hold per-user data have RLS `ENABLE` and `FORCE`. Policies in effect:

| Table | Read policy | Write policy |
|---|---|---|
| `licenses` | `auth.uid() = user_id` | `service_role` only |
| `credits` | `auth.uid() = user_id` | `service_role` only |
| `credit_transactions` | `auth.uid() = user_id` | `service_role` only |

`service_role` is an internal Supabase identity used only inside our Edge Functions; the key never leaves Supabase's secret store. End-user clients cannot impersonate `service_role` even if they obtained the public Supabase anon key (which is intentionally exposed in the browser by Supabase's design).

### 5.3 Operator access (SymbioTeK)

Operator access to the production Supabase project is via the Supabase dashboard with email + TOTP MFA (founder account only at v1). Operator access to Stripe live mode is via the Stripe dashboard with TOTP MFA. Operator access to Netlify and the Git repository is via the GitHub account with TOTP MFA.

---

## 6. Encryption

| Data location | Encryption at rest | Encryption in transit |
|---|---|---|
| Supabase database | AES-256 (managed by AWS RDS) | TLS 1.2+ |
| Supabase Edge Functions | N/A (stateless) | TLS 1.2+ |
| Browser IndexedDB | Not encrypted by the app — relies on device-level disk encryption (FileVault, iOS Data Protection, Android FBE) | N/A (local) |
| User-exported backups (JSON) | Optional AES-256-GCM with PBKDF2-SHA256 (250,000 iterations); user-supplied passphrase | N/A (file) |
| All HTTP traffic | N/A | TLS 1.2+ enforced via HSTS (max-age 2 years, preload-eligible) |

Backup encryption envelope format (for customers needing to verify):

```
{
  "format": "SafetySignageAudit-backup-encrypted",
  "version": 1,
  "kdf": "PBKDF2-SHA256",
  "iterations": 250000,
  "salt": "<base64, 16 bytes>",
  "iv":   "<base64, 12 bytes>",
  "ciphertext": "<base64>"
}
```

Lost passphrases are unrecoverable; there is no key escrow.

---

## 7. Browser security posture

The application is a static web app, so its primary attack surface is the browser. The following controls are served by Netlify on every response:

| Header | Value | Defends against |
|---|---|---|
| `Strict-Transport-Security` | `max-age=63072000; includeSubDomains; preload` | TLS downgrade |
| `X-Frame-Options` | `DENY` | Clickjacking |
| `X-Content-Type-Options` | `nosniff` | MIME confusion |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Referrer leakage |
| `Permissions-Policy` | `camera=(self), geolocation=(self), microphone=(), payment=(self)` | Third-party iframe access to camera/GPS |
| `Content-Security-Policy` | `default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; …` (full policy in source) | XSS, exfiltration |

Sub-resource integrity (SRI) is enforced on the only third-party JavaScript dependency (Supabase JS SDK):

```html
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.45.0/dist/umd/supabase.min.js"
    integrity="sha384-NNePyabYRaJyedI6EQAY7SV5Z8/0sQkuQ5WVfhKm0H+j0KSugkI2ZMNzw/QtzAWz"
    crossorigin="anonymous">
</script>
```

If jsDelivr is compromised, the browser refuses to execute the modified script. Re-pinning the version requires recomputing and committing the hash; see `Security.md` §2.4 for the procedure.

`'unsafe-inline'` is currently allowed for `script-src` because the app uses inline event handlers in HTML injected via `innerHTML`. A refactor to remove this is scheduled for 2026-05-28; see §10 (Roadmap).

---

## 8. Billing path: signed-webhook integrity

The billing path is the only place where an external service (Stripe) writes to our database. It is the highest-risk path and is hardened as follows:

1. Stripe sends webhook events to `…/functions/v1/stripe-webhook`.
2. The Edge Function reads the request body as raw bytes (signature verification requires byte-exact input).
3. It calls `stripe.webhooks.constructEventAsync(body, signature, STRIPE_WEBHOOK_SECRET)`. If the signature is missing or invalid, the function returns HTTP 400 and never touches the database.
4. Only on successful verification does the function insert into `credit_transactions` and update `credits.<balance_column>`.
5. If `STRIPE_WEBHOOK_SECRET` is not set, the function returns HTTP 500 — fail-closed by design.

Verification:

```bash
curl -i -X POST https://auacwdtbncawmpjqcnal.supabase.co/functions/v1/stripe-webhook \
  -H "Content-Type: application/json" -d '{}'
# HTTP/2 400  {"error":"Missing stripe-signature header"}
```

CORS on this endpoint rejects all browser-originated requests; only server-to-server calls from Stripe with a valid signature succeed.

---

## 9. Incident response

### 9.1 Reporting

Email <symbiotek@symbio-tek.com> (a dedicated `security@` address is being established).

Machine-readable disclosure file: <https://mysafesigns.symbio-tek.com/.well-known/security.txt> (RFC 9116).

### 9.2 Response targets

| Stage | Target |
|---|---|
| Acknowledgement | 1 business day |
| Triage / severity classification | 5 business days |
| Customer notification of confirmed incident affecting their data | 72 hours from confirmation |
| Coordinated disclosure window | 30 days from fix availability |

### 9.3 What you can expect from us

- Acknowledgement of receipt and a tracking reference.
- A written assessment of severity once we've reproduced or refuted the issue.
- A fix or compensating-control rollout plan.
- A post-incident write-up for material incidents, published at `/security.html`.

### 9.4 What we don't currently offer

- A paid bug-bounty program.
- A 24×7 security operations centre. SymbioTeK is a small Australian company; the founder is the on-call.

---

## 10. Roadmap and known limitations

### 10.1 Known limitations

| Limitation | Mitigation today | Plan |
|---|---|---|
| No ISO 27001 / SOC 2 attestation | This white paper + CAIQ-Lite + DPA template + sub-processor list provides equivalent evidence at procurement-questionnaire level | Pursue SOC 2 Type I after first material customer contract |
| No IRAP assessment | Not suitable for federal-PROTECTED data without compensating controls | Pursue IRAP assessment when a federal customer engagement justifies the cost |
| AI sub-processor (Anthropic) in the United States | Only the photograph is sent (not GPS, names, notes); auditor controls when AI is invoked; offline detection fallback available | Evaluate Australian-region AI vendors as they mature |
| Supabase database in Singapore (not Australia) | Account data only — no audit data; edge compute is in Sydney | Migration to AWS Sydney once Supabase enables in-place region migration, or as part of a customer-funded sovereignty upgrade |
| Browser IndexedDB is not encrypted by the app | Device-level disk encryption (FileVault, iOS DP, Android FBE) is the user's responsibility; passphrase-encrypted backups protect the export path | App-level IndexedDB encryption is on the backlog; trade-off is cold-start performance |
| `'unsafe-inline'` in CSP `script-src` | CSP still locks down origins; XSS surface is limited because the app is mostly static | Refactor scheduled 2026-05-28 to remove inline event handlers and drop `'unsafe-inline'` from `script-src` |
| Single-founder operations | Documented runbooks; founder MFA enforced everywhere | Hire / contract a second on-call after first customer contract |

### 10.2 Roadmap (12 months)

- **Q2 2026**: Drop `'unsafe-inline'` from CSP `script-src`. (Already scheduled.)
- **Q2 2026**: Engage AU privacy lawyer to review DPA template; release v1.1.
- **Q3 2026**: Commission a CREST-AU registered web-application penetration test. Publish summary findings (not the full report) in a successor white paper.
- **Q3 2026**: Migrate to a dedicated `security@symbio-tek.com` mailbox (currently uses `symbiotek@`).
- **Q4 2026**: Pursue SOC 2 Type I if customer demand supports the cost.
- **Q4 2026**: Publish Essential Eight Maturity Model v2 self-assessment (target: ML1 across all eight strategies).
- **2027**: Evaluate IRAP assessment based on federal-customer pipeline.

---

## 11. Patent figures and references

System architecture, two-phase capture, detection pipeline, adaptive confidence, compliance framework, and data-model figures are filed under SymbioTeK's pending patent application. They are intentionally **not** reproduced in this white paper pending patent-attorney clearance for publication. A non-patent-encumbered trust-boundary diagram (referenced in §2) will be inserted in a future revision once cleared.

---

## 12. References

- `Security.md` — internal-tone canonical security controls document, with verification commands.
- `Launch-Checklist.md` — pre-launch operational checklist.
- [/privacy.html](/privacy.html) — Privacy Policy (APP-aligned).
- [/security/sub-processors.html](/security/sub-processors.html) — sub-processor list.
- [/security/MySafeSigns-DPA-Template-v1.md](/security/MySafeSigns-DPA-Template-v1.md) — Customer DPA template.
- [/security/MySafeSigns-CAIQ-Lite-v1.md](/security/MySafeSigns-CAIQ-Lite-v1.md) — Pre-filled CSA CAIQ-Lite.
- [/security/Essential-Eight-Self-Assessment-v1.md](/security/Essential-Eight-Self-Assessment-v1.md) — ACSC Essential Eight self-assessment.

---

*This document is owned by SymbioTeK Pty Ltd. Last reviewed 2026-04-28. Address material questions to <symbiotek@symbio-tek.com>.*
