Security

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

Last updated 8 October 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 configuration holds no client secret. SAML federation uses your published metadata and an entity id, neither of which is confidential, and the signing keys stay with the authentication service. Everything stored is encrypted at rest by our database and storage providers.

7.An insert-only audit trail

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.

Each organisation’s entries are hash-chained. As an entry is written it takes the next position in the organisation’s chain and a sha256 hash over its own fields and the hash of the entry before it. The Audit log page in the console checks the whole chain and says how far it verified. Where retention removes a run of old entries it leaves a checkpoint, so the chain still checks across what was pruned.

The chain lives in the same database it protects, so somebody with direct database access could still rewrite entries and every hash after them consistently. What shows that is a copy held elsewhere: every evidence pack carries the head of the chain when it was taken and is signed with an Ed25519 key we publish, and the audit event webhook stream hands each entry and its hashes to your own systems as it is written. Tamper-evident, not immutable.

Closing an organisation does not delete its trail. Before anything is removed, every entry, hashes included, is copied to an archive that no customer or application role can read or change, with the chain’s final position and hash. The archive is kept for seven years after closure and then removed. An open legal hold still refuses the closure.

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.
  • The Compliance Mode pack is not signed yet. The project evidence pack is signed; the governance pack for one governed task carries a sha256 digest only, and says so.
  • 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.
  • SAML depends on a backend switch. OpenID Connect sign-in is live and can be enforced per domain. SAML 2.0 is built and configured the same way, but signs people in only where the identity backend’s SAML add-on is enabled for the deployment; the identity settings page says plainly which state a deployment is in.
  • Directory Sync authenticates with a bearer token. SCIM 2.0 provisioning from Microsoft Entra, Okta and other directories is live on Business and Enterprise, with a per-organisation token that is stored hashed, can expire and is rotated or revoked from the console. OAuth client credentials for it are not offered yet.
  • 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.