Every control described on this page exists in the running system today. Where a
control covers one part of the platform and not another, this page says which.
Where we hold nothing, it says that too.
What is in place
Controls, and exactly what they cover
Authentication
Passwords are hashed with Argon2 and never stored in plaintext. Primary authentication is delegated to Stytch, which holds the credential. Two-factor authentication is available on every account, by time-based one-time code or WebAuthn passkey.
Roles enforced from the token
Four dashboard roles (owner, admin, member, viewer) plus a separate auditor role. Enforcement reads the roles out of the verified session token rather than a column in our database, and a refusal happens at the request layer rather than being hidden by the interface. The auditor role is refused every mutating HTTP method without exception. The viewer role is refused every mutating method except acknowledging and resolving an incident, which it is deliberately allowed to do. Roles apply to interactive sessions: an API key or machine-to-machine token carries no role and is governed by its scopes instead.
API keys we cannot read back
A key is 96 hexadecimal characters of cryptographically random output. We store its SHA-256 digest on a unique index, plus the first eight and last four characters so you can recognise it in a list. The secret itself is never written to our database, so a lost key can be revoked and reissued but never recovered, by us or by anyone who reaches the row.
Tenant scoping, and its two exceptions
Every data-plane read is scoped to an account identifier before it reaches storage, as a typed UUID. On an ordinary request that identifier comes from the verified session and cannot be supplied by the caller. There are two deliberate exceptions, both checked for ownership before use: a partner may name one of its own managed client accounts in an X-SQ-Account header, which is rejected unless that account is a live child of that partner; and our internal staff surface addresses an account by id in the URL, behind the gates described below.
How our queries are built
PostgreSQL access uses parameterised statements with bind parameters. There is no string-concatenated SQL anywhere on that path. Almost all of it is additionally checked against the live schema at compile time by our query macros; a small number of statements use the runtime form, which still binds every parameter but is not schema-checked at build time.
Encryption in transit, on every connection you make
Every connection between you and Site Qwality is HTTPS. All eight public API hostnames enforce a TLS 1.2 minimum, and so does this marketing site. Five of our six content-delivery distributions enforce the TLS 1.2 (2021) policy and redirect HTTP to HTTPS. The sixth serves status-page logos and images from a CloudFront address rather than a hostname of ours, and still permits TLS 1.0; raising it is scheduled work, and we would rather name it than hide it behind a qualifier.
Encryption at rest, in the stores we name
All ten of our object-storage buckets are server-side encrypted with AES-256 and carry the full public-access block on all four settings, so nothing in them can be made world-readable by an accidental access-control list or a public bucket policy. Two of those buckets hold content that is deliberately public and is published through our CDN rather than from the bucket itself: the RUM script, and the logos and images you upload for a public status page. The volume holding every account’s logs, metrics, traces, RUM data and session-replay index is encrypted at rest. Encryption at rest is not yet universal across every store we run; see “What we do not claim” below.
Check bodies captured on failure only
A successful HTTP check stores no response body at all. When a check fails we keep an 8 KB snippet inline so the result list renders without a second request, and the full body in encrypted object storage. A failure stays diagnosable without us holding a copy of every page of a working site.
An audit log you can read
Every authenticated request you or your users make that changes something in your account is recorded with the actor, the action, the HTTP method and path, the response status, the request IP and the user agent. A denied request is recorded too, with its 403. Two endpoints that use a mutating method only to carry a read query are exempt. The write is best-effort by design, so a failure to log can never fail your request, which also means an outage of the log store can leave a gap. The last 90 days are readable in the dashboard by an account admin, the auditor role, or the account’s primary user.
What your audit log does not contain
Administrative actions taken by Site Qwality staff go to a separate internal staff log, not to yours. That covers plan and limit changes, SMS credit grants, suspension, reactivation and deletion, and triggering a member’s password reset. There is one exception, and it is deliberate: a staff member acting as one of your users through impersonation appears in your own log, attributed to the staff member who did it.
How our own staff reach production
The internal admin surface sits behind four gates, all of which run before any handler. Three we can point at in the code: an IP allowlist enforced at the CDN edge, an origin token injected by that edge and verified server-side with a constant-time comparison, and a staff-role check on the session token. The fourth is mandatory multi-factor authentication on our staff identity organisation, which is enabled. That one is a policy held at our identity provider with no representation in any repository, so it is enforced at sign-in rather than by our code, and it is the only one of the four we cannot show you in a file.
Impersonation is explicit and bounded
A staff member can open your account as one of your users only through an endpoint that records who did it, which user was targeted and a written reason. The session can be issued read-only, in which case every mutating method is refused. It expires after 60 minutes and cannot be extended.
Card data never reaches us
Card details are typed into a payment element served from Stripe’s own origin. A card number never touches a Site Qwality server, database or log. What we store is a Stripe customer identifier and subscription identifiers.
Alert email that is not rewritten
Outbound alert email goes through Amazon SES with DKIM signing, a dedicated MAIL FROM domain, SPF and DMARC. Engagement tracking is deliberately off, so a link in an outage alert is never rewritten to point through a third-party redirector. Bounce and complaint feedback is consumed and recorded, so an alert address a provider has silently suppressed becomes visible instead of failing quietly.
Being straight with you
What we do not claim
A trust page is worth nothing if you cannot tell which sentences were checked. These are
the things a security questionnaire usually asks for that we cannot answer yes to.
No certification of our own. Amazon Web Services holds SOC 2,
ISO 27001 and other certifications for the infrastructure we run on. Site Qwality
holds none. An earlier version of this page was written in a way that blurred the two,
and that was our error.
No third-party penetration test and no vulnerability-scanning programme.
We do not run either today. The responsible-disclosure address below is the route that
does exist, and we read it.
Encryption at rest is not universal. The stores named above are
encrypted. Not every store we operate is, and bringing the remainder into line is
scheduled work rather than finished work.
Not everything we hold expires on a schedule. Session-replay recordings,
and the searchable copy of your logs, metrics and traces, are deleted automatically at
the end of their window. A second archival copy of ingested log lines is written to
object storage and is not on a deletion schedule today, so it outlives the window that
governs the searchable copy. Several operational records have no enforced deletion
window either. We would rather say so than imply a uniform policy. Our
Data Retention page lists, category by category, either
the window our systems enforce or a plain statement that nothing deletes it yet.
Our own infrastructure is in the United States. All storage and all
compute take place in the AWS us-east-1 region. Every server, every database
and every object store that holds your data runs there, and monitoring checks are
executed from there as well. We do not offer an EU or UK data-residency option. That
sentence is scoped to our own infrastructure on purpose: some of the providers we depend
on run global networks, and our Subprocessors page states
the processing location for each one and says plainly which are still unresolved.
Our content-delivery network terminates connections outside the region, and it
is not only public assets. We run six distributions and all six use a price
class that includes European edge locations, so a request from Europe is terminated in
Europe rather than in Virginia. Two of the six serve public files only: the monitoring
script and status-page logos. Two serve status pages, one of ours and one on a
customer’s own domain. Two front our internal staff portal, and one of those
proxies staff requests to our API, so staff traffic carrying customer account data is
terminated at an edge too. None of the six has access logging enabled, so we keep no
record at the edge of who requested what. An earlier version of this page called the CDN
“the one thing that leaves the region” and described it as caching public
assets. That named two of six and it was wrong.
If you are filling in a vendor assessment and an answer here is not enough detail, write to
the address below and ask. We would rather send you a specific answer than have you infer a
generous one.
Responsible Disclosure
Found a vulnerability?
We take every security report seriously. There is no bug-bounty programme and no formal
response-time commitment, but the mailbox is read by an engineer and we will work with you
on a responsible resolution.