Trust Center
Last updated 11 September 2026
We have not completed a SOC 2 or ISO 27001 audit, or any other third-party certification. Rather than assert one, this page describes the actual controls in place today, in enough detail to be checked.
What this page is
Most trust centers point at a badge. We don’t have one yet, so this page is the substitute: a plain description of where data lives, how it is protected, who can reach it, and what has gone wrong so far — written to be evaluated on its own merits rather than taken on faith. If a claim below turns out not to hold up, that is a bug in this page, not a footnote.
Data inventory
Free scans and paid audits are stored in DynamoDB with an automatic retention TTL; audit screenshots in a private S3 bucket with a matching lifecycle rule; account, organisation and monitoring data in Supabase Postgres under row-level security. Nothing below is stored longer than it takes to serve the product.
| Data | Why | Where | Retention | Deleted by |
|---|---|---|---|---|
| Scanned URL | Identifies the job | DynamoDB, us-west-2 | Free scan 30 days · Audit 365 days | Automatic TTL |
| Request hosts + path hashes | Finding third parties | Embedded in the scan result, DynamoDB | Same as above | Automatic TTL |
| Cookie metadata | Cookie inventory | Embedded in the scan result, DynamoDB | Same as above | Automatic TTL |
| Storage key names (never values) | Storage inventory | Embedded in the scan result, DynamoDB | Same as above | Automatic TTL |
| Screenshots / HAR evidence | Visual evidence for a paid audit | Private S3 bucket, SSE-S3, all public access blocked; served only through a token-checked route | 12 months, matching the audit | Bucket lifecycle rule |
| Email address | Account and alerts | Supabase Auth and Postgres (row-level security); alert recipients in the org's own rows | Until the account is deleted; alert recipients until removed | Account owner |
| Payment details | Billing for a $99 audit | Stripe only — card data never reaches us | Stripe's own retention policy | We hold nothing to delete |
| Submitter IP address | Rate limiting and abuse control | DynamoDB counter, keyed by IP | Up to 24 hours | Automatic TTL |
What is never collected
- The contents of browser storage on a scanned site — only the key names.
- Request payloads or POST bodies.
- Full URLs, including query strings — we store the request host and a hash of the path, because query strings routinely carry a scanned site’s own visitors’ personal data.
- Behavioural profiles, of you or of a scanned site’s visitors.
- Card numbers or other payment details — Stripe holds those, not us.
Architecture
- Single-use scan workers. A free scan runs in AWS Lambda; a paid audit runs in Fargate, since a 100-page crawl takes longer than Lambda’s 15-minute ceiling. Both use the same worker image and code — one image serving two entrypoints, so a free scan and a paid audit cannot silently run different logic. The Fargate task’s security group has no ingress rule at all, and its VPC has no NAT gateway or peering to anything internal — there is nothing private in reach even if a scanned page could somehow escape the browser.
- Five egress regions. us-west-2, us-west-1, us-east-1, eu-central-1 and eu-west-2. Consent platforms decide what to show a visitor based on IP address, so a scan claiming to run from the EU has to actually egress from the EU to be an honest result. Every result carries an
egressVerifiedflag rather than assuming the routing worked. - SSRF guard. Before a scan is dispatched, the target hostname is resolved and rejected if any address falls in private, loopback, link-local or otherwise non-public space — which is also where the cloud metadata endpoints live. Inside the worker, every navigation re-resolves and re-checks the same rule, which catches DNS rebinding and a redirect into private space after the initial check passed.
- Signed results. A worker reports its findings back to the web app over an HTTP callback signed with HMAC-SHA256 over the raw request body. An unsigned or wrongly-signed call is refused before its body is even parsed as JSON, so a forged callback cannot write findings into someone else’s scan.
- No long-lived AWS keys, anywhere. The web app authenticates to AWS by exchanging Vercel’s own OIDC identity for a short-lived role session scoped to one DynamoDB table and one SQS queue. CI does the same through GitHub’s OIDC identity, scoped to one repository, to publish the worker image. There is no access key in either environment to leak or rotate.
- Evidence access. Screenshots live in a private S3 bucket with SSE-S3 and every public-access block enabled — never a public or guessable object URL. A route checks the scan’s own token on every request and proxies the image through the web app itself, so the bucket’s hostname never reaches the browser.
Encryption
- TLS on every connection; HSTS is preloaded on the web app.
- DynamoDB uses AWS-managed server-side encryption.
- Both SQS queues use SQS-managed server-side encryption.
- The evidence bucket uses S3 SSE-S3, matching the rest of the stack. Supabase encrypts its Postgres storage by default.
Access control
- A free scan or paid audit result is readable only with the random token issued when it was created — there is no account to log into, so the token is the entire access control. A missing scan and a wrong token return the same generic 404, so neither can be used to learn which one is true.
- Administrative access to the AWS account is, today, one person through a browser-authenticated root session. That is a real gap: the intended next step is a scoped IAM role for routine administration, not yet created. We are not describing it as already in place.
Logging and retention
Worker log groups (Lambda and Fargate) are set to a 14-day retention explicitly in the CloudFormation templates, rather than left at the indefinite default. They can incidentally contain the domain name someone asked us to scan — never scan tokens, the callback signing secret, Stripe keys, or the cookie and storage contents a scan observed. Visitor IP addresses are used only as a rate-limit key, for up to 24 hours, and are never written onto a scan record. The web app’s own runtime logs live under Vercel’s standard retention, which we do not configure.
Monitoring and incidents
An hourly probe checks both the public scan path and the paid checkout path in production; a failure opens a tracked issue rather than waiting to be noticed by hand. A CloudWatch alarm also watches the scan dead-letter queue and fires when a job has failed three times, alongside alarms on scan-executor errors and throttling. All of them route to an on-call email through SNS in every region (delivered to the application over HTTPS and forwarded by email); the account also carries spend budgets that email at 80% forecast and 100% actual, because the free scanner is the one thing here that could run up a bill.
We publish the one real incident on our own record below, on the reasoning that a trust center which only ever says everything is fine is not worth reading.
Paid checkout returned an error for three days
STRIPE_AUDIT_PRICE_ID was switched to a live Stripe price while STRIPE_SECRET_KEY remained a test key. Stripe objects are mode-scoped on both sides, so the mismatch made every checkout attempt fail. The free scanner was unaffected throughout, and no customer or scan data was exposed or lost — this was an outage in taking payment, not a data incident. It was found by manually probing production, not by an alarm, which is the reason the hourly probe above exists now. Fixed by reverting to a matched test configuration.
Subprocessors
| Subprocessor | Purpose | Location |
|---|---|---|
| Vercel | Application hosting | United States |
| Amazon Web Services | Scan and audit workers, storage, email delivery (SES) | us-west-2, us-west-1, us-east-1, eu-central-1, eu-west-2 |
| Supabase | Accounts, organisations, monitors and alert recipients | United States (us-west-2) |
| Stripe | Payments | United States |
| GitHub | Source control and CI | United States |
Vulnerability disclosure
If you find a security issue, report it through Saasquatch Labs support. Tell us what you found and how to reproduce it. In return: we will acknowledge your report, we will not pursue legal action against good-faith research conducted under this policy, and we are glad to credit you publicly if you want that. Please do not access, modify or exfiltrate anyone else’s data beyond what is needed to demonstrate the issue, and give us a reasonable window to fix it before disclosing it publicly. The machine-readable version of this policy is at /.well-known/security.txt.
Software bill of materials
Generated at build time, directly from the lockfiles — CycloneDX format, one for the web app and one for the scan worker, so neither can drift from what is actually shipping.
Security package
A standard vendor-security questionnaire, answered in full at /trust/security-package, covering company, hosting, encryption, access, retention, logging, supply chain, incident response, business continuity, compliance posture, subprocessors and vulnerability management.