Trust

Security and data handling

CSP reports come from your users' browsers and describe your own pages: the URL someone was on, what the policy blocked, and — where your policy asks for it — a sample of the script involved. Before you point a header at anyone, you should know what happens to that. This page answers it precisely, including where the answer is “not yet”.

No certifications, stated up front

HeaderHawk holds no SOC 2 report, no ISO 27001 certificate and no PCI DSS attestation. It is built and run by one engineer. What it offers instead is specificity: every statement on this page names the thing in the system it describes, and the list of what has not been done is as complete as the rest.

What is stored, and where it goes

A report takes one path: the ingestion endpoint writes the raw body to S3 and puts an event on a bus; a processor normalises it into an analytics row. Four stores, and they hold quite different things.

Amazon S3

The raw report body, exactly as the browser sent it

Every report is written to S3 unchanged, inside an envelope holding a generated report id, your tenant and site ids, a correlation id, the time it arrived, the user agent, and the IP address it was posted from. Objects are partitioned by tenant, site and date. This is the only store that keeps the source IP for longer than a couple of minutes — the rate limiter below holds it briefly, and nothing else records it at all.

Tinybird

One analytics row per report, which is what the dashboard queries

Page URL and host, blocked URL and host, the violated and effective directives, disposition, source file, line and column, the script sample where your policy includes 'report-sample', the policy in force at the time, the referrer, the user agent and what we parsed from it, and the grouping signature. No IP address is written here.

Neon Postgres

Your account, not your reports

Organisations, users and their roles, registered sites and their domains, plan and subscription state, trial grants, alert rules, waitlist and contact-form submissions, and a Slack bot token if you connect Slack. No report ever lands here.

DynamoDB

Operational counters, all of which expire on their own

Monthly quota counters, idempotency markers so a redelivered event cannot be processed twice, the set of script hashes already seen for a site, counters for reports dropped on a domain mismatch, per-IP ingestion rate counters, and alert deduplication windows. Every one of these tables has a TTL; none of them holds report content. The rate-limit counters are keyed on the source IP itself and expire two minutes after the window they count.

Two things worth saying because they are easy to assume the other way. HeaderHawk never fetches your site — it only receives what browsers post to it, so nothing here is a crawl of your pages. And a report is only accepted for a domain matching a site you registered; anything else is counted and dropped rather than stored against your account.

Where it runs

The infrastructure we control runs in AWS us-east-1(N. Virginia): the ingestion endpoint, every Lambda, the raw report bucket, the operational tables, the event bus and the CDN. Postgres is Neon in AWS us-east-1. The analytics store is Tinybird in AWS us-west-2 (Oregon), which is the one place your report data crosses into a second region.

There is no EU region and no data-residency option. If you are in the EU, your report data is processed in the United States. The subprocessor table below says, per service, whether we pin its region or it is the vendor's to choose — a vendor default is not a commitment we made, and this page will not write it as one.

How long it is kept

Report retention is a plan limit. A scheduled job runs daily, works out each organisation's cutoff from its plan, and deletes everything older than that from both the raw store and the analytics store — violation reports and script reports alike.

PlanReport retention
Free15 days
Team30 days
Professional60 days
Business60 days

The backstops, and what outlives the window

A deletion job that fails silently is how data stays forever, so each store also expires itself independently: the raw report prefix in S3 has a 90-day lifecycle rule, and each analytics table has a 90-day TTL of its own. Both sit above the 60-day maximum plan retention, so they never delete anything you are still entitled to — they bound the damage if the job stops working. The job is what keeps the promise; these are what keep the worst case finite.

Three things live outside those windows, and they are metadata rather than report bodies. Application logs are kept 90 days in production. The internal event bus archives events for 30 days in production; those events carry hosts, a directive and a pointer to the S3 object, not the report itself. Account records — your organisation, users and sites — are kept while the account is open and deleted within 30 days of closing it, and billing records are kept for seven years, as the privacy policy sets out.

Subprocessors

Everyone who processes data on HeaderHawk's behalf. Nothing is sold, and nothing goes to anyone not on this list except where you direct it: an alert you route to Slack sends the directive and blocked host to Slack, and choosing Google or GitHub to sign in tells them you signed in. Those are your integrations rather than our subprocessors, and both are yours to disconnect.

Last reviewed against the running infrastructure on 2026-09-11.

Amazon Web Services

Compute, raw report storage, operational counters, the event bus, the CDN, and every outbound email.

Data:
Raw report bodies, operational counters, application logs, and the address any alert or auth email is sent to.
Region:
US East (N. Virginia). Not pinned: the CDK app falls back to it when CDK_DEFAULT_REGION is unset, which is how every environment is deployed.

Neon

Managed Postgres for accounts, sites, plans and alert rules.

Data:
Your email address, organisation and site names, site domains, plan and subscription state, alert rules, and a Slack bot token if you connect Slack.
Region:
AWS US East (N. Virginia), pinned by our Terraform configuration.

Tinybird

The analytics store behind every dashboard query.

Data:
One row per violation or script report: page URL, blocked URL, directive, source file and line, the script sample where your policy asks for one, the policy in force, referrer and user agent. No IP address.
Region:
AWS US West (Oregon), pinned by the API host we call.

Descope

Authentication: the login flow, sessions, and team membership.

Data:
Your email address, your name if you give one, and login history. No password — there is none to store.
Region:
Vendor-managed (api.descope.com). Our configuration pins no region.

Stripe

Subscription billing for paid plans.

Data:
Billing email, payment method and invoice history. Card numbers never reach HeaderHawk.
Region:
Vendor-managed. Our configuration pins no region.

Axiom

Application logs from the Lambda functions.

Data:
Structured request logs and error traces: tenant and site identifiers, timings, outcomes and stack traces. The ingestion path also logs the source IP of a request it rate-limits, or of one where the limiter itself failed. Report bodies are not logged.
Region:
Vendor-managed. Our configuration pins no region.

Umami

Page analytics, on the marketing site and in the dashboard.

Data:
Page views, and product events carrying only an onboarding step or plan name — no email address, name or identifier. No advertising or cross-site cookie.
Region:
Vendor-managed (cloud.umami.is). Our configuration pins no region.

Outbound email — login codes, alerts and digests — is sent through Amazon SES on our own domain, not a third-party sending service. The authentication provider sends login codes by assuming a role into that same SES identity.

Authentication

There are no passwords. Not “passwords plus an optional second factor” — there is no password field in the product and no password hash anywhere in the database. You sign in with a one-time code emailed to you, or with Google or GitHub. Credential stuffing has nothing to stuff, and a database breach would leak no password to reuse elsewhere.

Sign-in and sign-up are the same flow, deliberately. Two flows would mean a sign-in page that answers an unknown address with “that email does not exist”, which is an account-enumeration oracle for anyone who wants to know who your customers are. One flow cannot answer that question.

Social login runs on OAuth clients we own rather than shared ones, so the consent screen names HeaderHawk and the client is ours to rotate. Sessions are JSON Web Tokens verified on every API request against the auth provider's published keys, and every query is scoped to the organisation in that token.

How it is protected

In transit
HTTPS everywhere, including the ingestion endpoint browsers post to. The marketing site, the docs and the dashboard are all served under an enforced Content-Security-Policy — a product about CSP that did not run one would be a poor advertisement.
At rest
The raw report bucket is server-side encrypted and blocks all public access; nothing in it is reachable without credentials. Postgres and the analytics store are encrypted at rest by their providers.
Third-party credentials
A Slack bot token you grant us is envelope-encrypted under a customer-managed KMS key with rotation enabled, and bound to your organisation, before it is written to the database. Read access to the database is not enough to use one; the decrypt permission is granted only to the two functions that need it.
Tenant isolation
Every API request resolves to one organisation and every query is filtered by it. Raw objects are stored under a per-tenant prefix, and analytics rows are keyed by tenant before anything else.
What the endpoint accepts
Reporting endpoints are public by necessity — browsers post to them with no credentials. Reports are only accepted for a domain matching the site they were addressed to, the rejection is deliberately indistinguishable from acceptance so it cannot be used to enumerate which domains you have registered, and a counter in your dashboard tells you it happened. Per-source rate limiting sits in front of it.
Infrastructure
Everything is defined in code and deployed by a pipeline rather than by hand, and there are no servers to patch — the compute is all functions. Administrative access to the AWS account belongs to the one engineer who runs HeaderHawk. That is a small blast radius and also a single point of failure, and it is worth being clear that those are the same fact.

Your data, and getting rid of it

For the reports you send us, you are the controller and HeaderHawk is the processor: it is your policy, your pages and your visitors. We use report data to run the service for you and for nothing else — it is not sold, not shared, and not used to train anything.

Deleting a site removes it from your account immediately and stops it accepting reports. The reports it already sent are not purged on the spot; they age out on your plan's retention window, and the 90-day backstops sit behind that.

Deleting an account, or asking for report data to be erased sooner than the retention window, is an email rather than a button today — there is no self-service delete in the dashboard. Write to privacy@headerhawk.com and it is done by hand.

The same address handles access, correction and objection requests. Be aware of the limit on portability: there is no export feature, so an access request is answered by hand from the stores described above.

The privacy policy is the governing document; this page is the engineering detail behind it.

What has not been done

Collected in one place, because a trust page that only lists strengths is not one. If something you need is here, say so — knowing what is asked for is how it gets prioritised.

  • There is no SOC 2 report, no ISO 27001 certificate, and no PCI DSS attestation of compliance. Not “in progress” — not started. If your procurement process requires one, HeaderHawk will not clear it today, and it is better that you learn that here than three calls in.
  • There is no signed DPA on the shelf. If you need one, send yours and I will work through it. Standard contractual clauses for transfers out of the EU would be part of that conversation rather than something already executed.
  • There is no data export. No CSV, no JSON download, no archive. Reports are read in the dashboard, and that is the whole of it — which matters here because it also limits what a data-portability request can be answered with.
  • There is no public status page and no uptime SLA on the published plans.
  • There is no bug bounty. Reports are welcome and answered; there is no money behind them.
  • SSO is listed on the pricing page as coming soon and has not shipped. Access control today is the owner, admin and member roles inside an organisation.
  • There has been no third-party penetration test.

Reporting a vulnerability

Email security@headerhawk.com. It reaches the person who wrote the code, not a queue. You will get an acknowledgement within 3 business days, an assessment after that, and an honest answer about the fix timeline — including “this is a known limitation and here is why” where that is the truth.

Include what you did, what happened, and anything needed to reproduce it. Please test against your own account and data only, do not run denial-of-service or automated scans against the ingestion endpoint, and give us a chance to fix an issue before publishing it. Reports made in that spirit are welcome and will not be met with legal threats.

The machine-readable version of this is at /.well-known/security.txt, per RFC 9116.

Something here not answered?

Security questionnaires, architecture questions, a DPA you need reviewed — send them through the contact form and you will be talking to the person who built the thing. If the answer is “not yet”, you will get that instead of a maybe.