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.
How sign-in is protected for Console administrators and for your end-user identities.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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.
Identity federation and lifecycle automation, for your administrators and for each of your customers' organizations.
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 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.
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.
A three-tier boundary that keeps blast radius contained, enforced by guards and by every query.
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.
How integrations authenticate, how tokens are signed and verified, and what is sealed at rest, stated precisely.
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.
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.
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.
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.
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.
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.
How permission decisions reach your app, what that costs in freshness, and where a refresh token lives on each kind of client.
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.
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.
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.
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.
A comprehensive, queryable trail: customer-facing forensics and internal accountability. We do not call it tamper-evident, because it is not hash-chained.
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 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.
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.
How the platform is run, described at the level that's true of the real deployment.
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.
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.
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.
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.
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.
The Canopy API, hosted authentication, our applications, and official SDKs.
Denial-of-service, social engineering, and physical attacks. Don't access other tenants' data.
Acknowledgement within a few business days, and coordinated disclosure once a fix ships.
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.