Security

How the product keeps published work private, what our own staff can reach, and the controls it does not have yet.

Last updated 2 September 2026

The short version

  • Reports are served from an origin that never sees your product session, so a report cannot read it.
  • Access is re-checked on every request and every asset — not once at the door.
  • API keys and share links are stored as hashes. The audit trail rejects edits, including ours.
  • Support access is read-only, reason-required, capped at an hour, and audited under the engineer’s own name.
  • No SOC 2, no ISO 27001, no malware engine. Section 9 lists the rest.

A summary, not a substitute. The numbered sections below are the ones that bind us.

1.The premise

Deliverd hosts HTML that an AI wrote. We treat every byte of it as untrusted content, in the same way a code-hosting service treats an uploaded file. Every design decision below follows from that one assumption.

The second assumption is that a URL is not a permission. A private report link that is forwarded, pasted into a group chat or found in a browser history grants nothing on its own.

2.Two origins, and why

The product and the content it serves run on different hostnames, and in production on different registrable domains. The session cookie that signs you in to Deliverd is host-only to the product origin: the origin serving reports never runs session middleware and never receives it.

A report can therefore not read your session, call the API as you, or reach the control plane — not because we filter what it tries, but because the credentials it would need are not present in that browsing context at all.

The one cookie the content origin sets is a viewer token: HttpOnly, 15 minutes, signed with a dedicated secret, carrying only who you are. It authorises nothing by itself. On HTTPS it is set as a __Host- cookie, which browsers refuse to accept with a domain attribute — so one tenant’s subdomain cannot read another’s.

3.Access is decided per request

Reaching a report with no valid viewer token sends you to the product origin to authenticate. On success we mint a single-use code valid for 30 seconds, redeem it exactly once at the content origin, and set the viewer cookie.

Access is then evaluated again on the content request itself, and on every asset request after it. Revoking someone’s access takes effect on their next request, not at the end of a session.

Grants are resolved in this order:

  • the report’s owner;
  • a named person;
  • a workspace;
  • a directory group;
  • the whole organisation;
  • a verified external guest;
  • a share link — checked for revocation, expiry, email verification and any domain restriction, every time.

Organisation owners, administrators and security administrators can read within their own organisation, and doing so is audited.

4.What we check at publish time

Archives are extracted under constraints rather than trust. We reject path traversal, absolute paths, NUL bytes, symlinks, more than 500 files, more than 50 MB compressed or 150 MB uncompressed, compression ratios above 100:1, and any file type outside the allowlist.

We scan for credentials that should not be in a published document — AWS, GitHub, Slack, live Stripe, OpenAI and Anthropic keys, and private key material — and block the publish when one is found. Scripting patterns worth a second look, such as frame escapes, obfuscation and external form actions, raise a warning recorded on the version.

5.What we enforce when it is served

Every content response carries:

Content is streamed through our own gateway; raw storage URLs are never exposed to a browser. An organisation administrator can loosen individual toggles — external images, APIs, scripts, frames, forms — in security settings, which is a deliberate decision recorded against the organisation.

6.Credentials and identities

  • API keys are stored only as SHA-256 hashes, compared in constant time, scoped, revocable, and tracked for last use. A leaked key cannot be recovered from our database because we do not hold it.
  • Agent identities are separate principals with explicit allow-lists: which workspaces they may publish into, which reports they may update, which audiences they may share with, and whether they may share externally at all. An agent can never make a report public — that one is enforced in code as well as by policy, so it holds even if the policy is misconfigured. Every denial is written to the audit log.
  • Share links are stored as token hashes, with expiry, revocation, email verification and domain restriction evaluated on each request.
  • Identity provider secrets are encrypted at the application layer with AES-256-GCM before they are stored.

7.An audit trail nobody can edit

Audit events are insert-only. A database trigger rejects every update and every delete outright — including from our own service key, which means an attacker who obtained it could add noise but could not remove the record of what they did.

The single exception is retention: rows age out on your plan’s schedule through two functions that set a transaction-local flag the trigger honours, and security and billing events are exempt from even that. Lifetime view totals are never rewritten, so pruning detail cannot make a report’s history appear to go backwards.

8.What our staff can see

Support access exists, and it is worth knowing exactly what it is rather than being reassured in general terms.

A platform administrator can enter a read-only context for an organisation. They stay signed in as themselves — no token is issued for one of your users, and every audit row still names the real person. A written reason is required, the session expires after one hour at most, and read-only is enforced by making mutations unreachable rather than by guarding each one: the checks that gate publishing and administration both return false in that context.

The organisation’s own audit trail records it. If you would rather this never happened to your tenant, tell us and we will agree it in writing.

9.What we do not have yet

A security page that lists only strengths is not much use to the person who has to sign off on it. These are the gaps as they stand today.

  • No third-party certification. We do not hold SOC 2 or ISO 27001. We will not imply otherwise while that is true.
  • No malware engine. Uploads get the type allowlist, the secret scan and the script heuristics described above; there is an interface for a scanning engine, and nothing behind it yet.
  • Rate limiting is best-effort per instance rather than globally coordinated.
  • Single sign-on is configuration-only today. SAML 2.0 can be configured per organisation, and live sign-in currently runs on password and one-time email code through Supabase Auth.
  • No SCIM provisioning. Users are created on first sign-in. Removing someone from your directory stops them signing in, but you still remove their membership yourself.
  • Viewer cookies use SameSite=None so the embedded viewer works cross-origin. They are partitioned per top-level site, content requests are read-only, and access is re-authorised on each one — but it is a trade-off, not an absence of one.

10.Reporting a vulnerability

If you have found something, we want to hear about it before anyone else does. Email support@deliverd.dev with steps to reproduce, what you were able to reach, and how you would like to be credited.

What you can expect from us:

  • an acknowledgement within three business days;
  • an assessment and a plan within ten;
  • credit in the release notes if you want it.

What we ask of you:

  • test only against your own organisation and your own data — never another customer’s;
  • no denial of service, no spam, no social engineering of our staff or our customers;
  • give us reasonable time to fix it before publishing.

Research conducted within those bounds is authorised: we will not pursue legal action over it, and the acceptable use section of our terms carves it out explicitly. We do not currently run a paid bounty programme.

Working through a security questionnaire? Send it to support@deliverd.dev and we will answer it against this page rather than around it.