Security model
Tenant isolation
Section titled “Tenant isolation”Every repository gets its own storage namespace, and every object key is derived from it:
<storage_namespace>/objects/<oid[0:2]>/<oid[2:4]>/<oid><storage_namespace>/cas/<tree relative path>No API path lets a caller name a key outside the namespace of the repository it
resolved. Path traversal is rejected explicitly: the static-tree handlers reject
any relative path containing an empty segment, . or .. before a key is ever
built.
Authorization runs before any storage interaction. A request for a repository you can’t reach fails at the guard, so it never reaches the object store — and never reveals whether the object exists.
Encryption
Section titled “Encryption”| Data | At rest | In transit |
|---|---|---|
| Object bytes (managed storage) | Cloudflare R2 server-side encryption | TLS 1.2+ to R2 |
| Object bytes (BYOS) | your bucket’s configuration — yours to set | TLS to your endpoint |
| Metadata (MongoDB) | provider-managed encryption at rest | TLS to the database |
| BYOS credentials | AES-256-GCM, application-level | TLS |
| API tokens | SHA-256 hash only — the secret is never stored | TLS |
| Invitation tokens | hashed | TLS |
BYOS secret access keys are encrypted with a master key held outside the database, are never returned by any API response, and are never written to logs. Rotating that master key requires re-encrypting the stored connections.
Credential handling
Section titled “Credential handling”API tokens — generated from a cryptographically secure random source, stored only as a SHA-256 hash, compared in constant time. The secret is shown once. Every use updates last-seen time and IP so you can spot a token being used from somewhere it shouldn’t be.
Presigned URLs — the data plane’s mechanism for keeping bytes off the control plane. They are bearer credentials for one operation, expiring in 15 minutes by default. CAVS never logs them, and neither should you: not in error reports, not in CI output, not in a support ticket.
SDKs and CLI are required to redact tokens from string representations, exception messages and logs, and never to log full request headers.
Transport
Section titled “Transport”TLS is required for cavsnode.com. HSTS is emitted with a one-year max-age and
includeSubDomains on any request that arrived over TLS.
Custom CA bundles are supported for self-hosted deployments and corporate
proxies via CAVS_CA_BUNDLE in the SDKs. Clients cap redirects at five and
always set connect and read timeouts.
HTTP hardening
Section titled “HTTP hardening”The API is a JSON service and never serves an HTML application surface, which lets the response headers be locked all the way down:
| Header | Value | Why |
|---|---|---|
Content-Security-Policy |
default-src 'none'; frame-ancestors 'none' |
An echoed payload can never be treated as a live document |
X-Content-Type-Options |
nosniff |
No MIME sniffing |
X-Frame-Options |
DENY |
No framing |
Referrer-Policy |
no-referrer |
URLs — including ones with ids — don’t leak |
Cross-Origin-Resource-Policy |
same-origin |
No cross-origin reads |
Cache-Control |
no-store |
Authenticated JSON is never retained by a shared cache |
Strict-Transport-Security |
max-age=31536000; includeSubDomains |
TLS requests only |
CORS allows only the configured dashboard origins, with an explicit method
and header allowlist (Authorization, Content-Type, Idempotency-Key,
Accept).
Request limits — control-plane bodies are capped at 1 MiB and unknown JSON fields are rejected rather than ignored, so a typo’d field name fails loudly instead of being silently dropped. Every request has a 60-second timeout.
Rate limiting — 30 requests/second sustained with a burst of 60, keyed per
token, else per user, else per IP. Exceeding it returns 429 rate_limited.
Error responses never leak internals: an untyped server-side error becomes a
generic 500 with a request_id, and the detail stays in the server logs.
Authorization model
Section titled “Authorization model”Five inputs compose on every guarded request, and any one can deny:
organization role + repository override + token scope + account status + organization statusTwo properties worth calling out:
- Token scopes are a hard cap. A
repo:admintoken held by aVIEWERcannot push. Losing a role narrows every personal token you hold, immediately. - Sensitive operations are unreachable by tokens. Billing, audit reads, token management, storage-connection management and organization deletion require a session-authenticated role. No token scope reaches them.
Full detail: Roles & permissions.
Content integrity
Section titled “Content integrity”Content addressing makes integrity structural rather than bolted on:
- An object’s name is the SHA-256 of its bytes.
- Chunks are addressed by BLAKE3-256, with a global Merkle root per object.
- Clients hash while streaming and reject anything that doesn’t match its oid — the file is deleted rather than written to its final path.
- Reconstruction is byte-identical or it fails. There is no partial success.
- A corrupt local chunk cache is detected, quarantined and repaired in place.
A malicious or buggy storage layer therefore cannot hand you the wrong bytes without the client noticing.
What CAVS stores about you
Section titled “What CAVS stores about you”| Category | Examples |
|---|---|
| Identity | email, display name, avatar URL, auth provider — from Firebase |
| Organization | name, slug, plan, subscription state |
| Repositories | names, descriptions, visibility, storage config |
| Objects | oids, sizes, physical sizes, chunk counts, timestamps, download counts |
| Git index | commit metadata and messages, tree entries and paths, refs, tags |
| Operational | audit events with IP and user agent, activity events, usage ledger |
| Billing | subscription and invoice references held by the payment provider |
CAVS does not store your passwords (Firebase handles authentication) or your payment card details (the payment provider does).
Shared responsibility
Section titled “Shared responsibility”CAVS is responsible for: platform availability, control-plane and metadata security, tenant isolation, encryption of BYOS credentials at rest, presigned-URL scoping and expiry, managed-storage administration (lifecycle, integrity, GC, backups), and the HTTP hardening above.
You are responsible for: who you invite and at what role, token hygiene (scope, expiry, rotation, storage), what you put in your repositories, your BYOS bucket’s configuration (encryption, versioning, lifecycle, public-access settings, IAM), verifying webhook signatures before acting on payloads, and not leaking presigned URLs.
Reporting a vulnerability
Section titled “Reporting a vulnerability”Report security issues privately, not in a public issue tracker. See the
SECURITY.md in the engine repository for
the disclosure process, or contact the platform operators directly. Include the
request_id from any relevant response.
- Roles & permissions — the authorization matrix.
- API tokens — hygiene and rotation.
- Bring Your Own S3 — keeping bytes in your own account.

