Security

Security isn't a feature. It's the product.

Canopy holds your authentication, your identities, and your authorization logic. The controls below are the ones in the code today, stated so your security team can check each one: how credentials and sessions are protected, how your end users' second factors work, how tenants are kept apart, how tokens are signed and verified, what is sealed at rest, and what is written to the audit log.

Defense in depth Isolation by default Least privilege Auditability

Authentication & credential protection

How sign-in is protected for Console administrators and for your end-user identities.

Argon2id password hashing with a server-side pepper

Passwords are hashed with Argon2id, the OWASP-recommended memory-hard algorithm, combined with a pepper held in the deployment's secret store rather than the database. Login takes the same time for an unknown address as for a known one, and registration, password reset and verification answer the same way whether or not an account exists.

Breached-password screening

Every new password is checked against HaveIBeenPwned using k-anonymity: only the first five characters of a SHA-1 hash leave the server, never the password. A known-breached password is refused. If the service cannot be reached the check is skipped rather than blocking sign-up, and we say so here because the distinction matters.

Progressive lockout & password policy

Five failed attempts lock an account, for 15, 30, 45, then 60 minutes on the Console and 5, 15, 30, then 60 minutes for end users. Console administrators cannot reuse their last five passwords and must meet a complexity rule; end-user passwords follow NIST guidance, 8 to 64 characters with no arbitrary composition rules, and your own sign-up form can read the policy from the API.

Refresh-token rotation with replay detection

Refresh tokens are stored as peppered SHA-256 hashes, used once and rotated, each chained to its successor. Replaying a token that was already rotated revokes every session the account holds. A harmless browser race, two tabs refreshing within five minutes from the same user agent, is recognised and kept signed in; the OAuth refresh grant, a server-to-server call, gets no such allowance. Revoking an administrator's session takes effect on their next request and closes any live Console connection they hold at once.

One-time tokens hashed at rest

Email verification, password reset and email-change tokens are stored as SHA-256 hashes and matched by hashing what is presented. The raw token travels only in the email. Changing a password ends every other session; the device making the change stays signed in.

Self-hosted bot protection

End-user login, email-code sign-in and self-service sign-up can require a proof-of-work CAPTCHA that Canopy issues and verifies itself. No third-party service sees your users, there are no site keys to manage, and each solution is accepted once. It is switched on per Environment.

Multi-factor authentication for your end users and your administrators

Second factors for the identities that sign in to your application, enforced per Environment, and second factors for the people who administer your Canopy account, enforced per account.

TOTP, WebAuthn, email codes & recovery codes

Authenticator apps, passkeys and hardware security keys through WebAuthn with counter checking, eight-digit emailed one-time codes, and ten single-use recovery codes hashed with Argon2id. TOTP seeds are sealed with AES-256-GCM under a key held outside the database.

Per-Environment enforcement, grace periods & trusted devices

MFA can be required per Environment with a grace period of up to 30 days, an allowlist of permitted factors, and a remember-this-device window of up to 90 days. An organization's own policy can tighten these further. Five failed factor attempts lock the challenge.

Step-up verification & assisted recovery

Removing a factor, regenerating recovery codes and changing an email address require a fresh second-factor check. An administrator can reset a locked-out end user's factors, which clears their trusted devices and grace timer at the same time.

Administrator two-factor authentication

Every administrator can enrol an authenticator app or a passkey on their profile, with ten recovery codes; the factor spans every account they belong to and is challenged at sign-in before any account is entered. An account can require it of all its administrators, with a grace period, a remembered-device window of up to 90 days, and a choice of whether single sign-on satisfies it. Nobody in an account can reset another administrator's factors: recovery codes, then Canopy support, are the way back in.

Enterprise SSO, directory sync & organizations

Identity federation and lifecycle automation, for your administrators and for each of your customers' organizations.

SAML 2.0 & OIDC with strict verification

Federate Console administrators and your end users through SAML 2.0 or OIDC. SAML assertions must be signed, are verified against the pinned provider certificate, checked for audience and validity window, and recorded so a replayed assertion is refused. OIDC uses PKCE, state and nonce, and verifies ID tokens against the provider's keys. Routing is by DNS-verified email domain, and Console SSO never creates an administrator who was not invited first.

SCIM 2.0 provisioning & deprovisioning

SCIM 2.0 syncs users from your directory with group-to-role mapping. Each directory's credential is stored like an API key and reaches only that directory's people. When someone leaves your identity provider, their membership, roles, invitations and sessions in that Environment end in one transaction.

Organizations with their own policy

Each of your customers can be an organization with its own DNS-verified domains, its own SSO connection that their administrator sets up without you, and a policy that can require SSO or tighten MFA for its members. Sessions carry the organization they act in, and a verified domain routes to one organization only.

Tenant isolation & access scoping

A three-tier boundary that keeps blast radius contained, enforced by guards and by every query.

Account
The customer tenant, fully isolated from every other account.
Tenant boundary
Application
A product within the account. Each owns one or more isolated environments.
Scoped
Environment
dev / staging / production: where your hierarchy, roles, assignments and signing keys live.
Sealed

API keys, publishable keys and end-user tokens are bound to one Environment, and a request that names another is refused and written to the audit log, not merely filtered. Identities are directory records at the Account level and gain access to an Environment only through an explicit membership. People in a production Environment are visible in the Console only to administrators holding a dedicated capability.

Tokens, API & data protection

How integrations authenticate, how tokens are signed and verified, and what is sealed at rest, stated precisely.

Every Environment is its own issuer with its own signing keys

Access tokens are RS256-signed JWTs. Each Environment has its own key set and issuer, with its own discovery document and public JWKS, so a token from one Environment never verifies against another's keys. Private keys are sealed with AES-256-GCM under a master key held outside the database. Keys rotate automatically every 90 days: the successor is published a week ahead and the retiring key stays published for a day, so nothing live is refused. An administrator can rotate on demand, or retire every key at once in an emergency, and every rotation is in the audit log.

OAuth 2.0 / OIDC with mandatory PKCE

The authorization-code flow requires PKCE with S256, authorization codes live sixty seconds and are used once, clients are confidential only with bcrypt-hashed secrets shown once, redirect URIs match exactly, and tokens can be revoked under RFC 7009. Access tokens default to 15 minutes and are configurable per Environment from 1 to 60; sessions from 1 to 365 days.

Environment-bound API keys, hashed at rest

Each API key names one Environment and is either scoped to a declared set of permissions or explicitly full-access within that Environment; the mode is chosen when the key is created and recorded in the audit log. Keys are stored as a SHA-256 lookup hash plus a bcrypt verifier, can expire, and can be rotated. A leaked key can never act outside its Environment. An idempotency key is scoped to the credential that sent it and bound to the request it first carried, so a replay never returns another caller's response, and reusing a key for a different request is refused.

Signed webhooks & guarded outbound calls

Every delivery carries an HMAC-SHA256 signature over its id, timestamp and body, with the id and timestamp in headers for replay protection, and a 24-hour overlap when you rotate the secret. Signing secrets are sealed at rest. Any URL you give Canopy to call, for webhooks, back-channel logout or an identity provider's discovery, keys and token endpoint, must be a public HTTPS address: private, loopback and link-local addresses are refused, the resolved address is pinned to the connection, and redirects are not followed.

TLS in transit

The API and every application are served over HTTPS with HSTS preloading; plaintext is not accepted at the edge. The hop from the API to its managed database is encrypted. The cache is reached over the provider's private network.

Sealed secrets under keys held outside the database

TOTP seeds, identity-provider client secrets, webhook signing secrets and the signing private keys are AES-256-GCM ciphertext under master keys that live in the deployment's secret store, never in code, configuration or the database. Each master key can be rotated without downtime: rows record which key sealed them and are re-sealed nightly. Production refuses to start if any master key is missing or malformed.

Authorization & session delivery

How permission decisions reach your app, what that costs in freshness, and where a refresh token lives on each kind of client.

Decisions in your process, not a call per request

Our SDK asks where an identity may act, the nodes each permission was granted at, rather than asking about one node per request. Checks are then answered locally by walking your hierarchy, so Canopy is not in the path of every authorization decision your app makes.

A revocation window you can see and set

Grant roots and the hierarchy are cached for 60 seconds by default, and never for more than five minutes, so an access change takes effect within a minute unless you lengthen the window. A revoked user can still act until then, and a token already issued, with any permissions claim it carries, stays valid until it expires. Set the window to zero to read fresh on every check, at the cost of a round trip per request. We state the numbers because your access-review process needs them.

Refresh tokens land where the client can protect them

Browser apps get an httpOnly cookie that page scripts cannot read. Installed mobile apps, which have no cookie jar, get the token in the response body to store in the OS keychain. Which one you get follows the publishable key you registered, never a request parameter, so a compromised web app cannot ask for the token the cookie exists to hide.

Delivery is fixed for the life of a session

A session records how it delivers its refresh token when it is created and keeps that through every rotation. The one exception is deliberate: a trusted backend that signs users in on their behalf can present a secret API key for the same Environment and receive the token in the body, because it has no browser to hand a cookie to.

Audit logging & monitoring

A comprehensive, queryable trail: customer-facing forensics and internal accountability. We do not call it tamper-evident, because it is not hash-chained.

Typed event catalog, multi-tier scoping

Every security-sensitive event is recorded against a typed catalog and scoped across account, application, and environment, so you can answer exactly who did what, where. Every refused request is logged too. Retention follows your plan, and exports leave through short-lived signed links.

Correlation IDs & durable snapshot labels

Correlation IDs let you reconstruct a full request across services. Snapshot labels preserve the human-readable context of an action even after the underlying entity is deleted.

Audit of the auditor

Reads of the audit log are themselves logged, including exports and live tails, without recording what you searched for. Access to the forensic record is part of the forensic record.

Infrastructure & operational security

How the platform is run, described at the level that's true of the real deployment.

Fail-loud configuration enforcement

In production, the service refuses to start if any secret pepper, encryption master key or CAPTCHA secret is missing, and if a master key is set but malformed. Rate limiting cannot be switched off in production by configuration. Misconfiguration fails loudly at boot rather than silently degrading.

Rate limits, headers & input validation

Redis-backed rate limits on every route, with strict per-route ceilings on sign-in, reset, second-factor and CAPTCHA endpoints. Strict security headers on the API and every application, including a refusal to be framed. Every request body is validated against an allowlist of fields and capped in size, and hosted forms refuse cross-site submission.

Strict CORS allowlisting

Cross-origin access is restricted to explicitly allowlisted origins, plus the origins each Environment registers for its own browser app. No wildcard origins in production.

Managed infrastructure, unprivileged containers

Production runs on Fly.io with Fly Managed Postgres, Upstash Redis and Cloudflare R2 for private object storage, with DNS on Cloudflare. Every container runs as an unprivileged user. Dependencies are scanned weekly, and every change reaches production through a pull request with required checks. Development runs locally with its own secrets and data; nothing is shared with production.

Responsible disclosure

Found something? We want to hear from you, and we'll work with you in good faith.

If you believe you've found a security vulnerability in Canopy, report it directly to our security team. We commit to acknowledging your report, keeping you updated, and not pursuing legal action against good-faith research that respects the scope below.

In scope

The Canopy API, hosted authentication, our applications, and official SDKs.

Out of scope

Denial-of-service, social engineering, and physical attacks. Don't access other tenants' data.

What to expect

Acknowledgement within a few business days, and coordinated disclosure once a fix ships.

PGP key available on request

Compliance & certifications

We state only what's real.

Canopy is early-stage, and we don't claim certifications we haven't earned. You won't see a compliance badge here until an audit is actually complete. Formal compliance work, including SOC 2, is on our roadmap. If your security review needs specifics (how we handle and process data, who our subprocessors are, or a completed security questionnaire), contact our team and we'll share what we can.

Doing a vendor security review?

Read the implementation details, or reach our security team directly for questionnaires and documentation under NDA.