➜ AES-256-GCM + AES-KW — lib/server/crypto/secretBox.ts, sealed under a key you provide
Security & compliance,
cited to the file, not the deck.
A one-pager a reviewer can actually use. Exactly which requests go straight to your own storage provider and which touch our servers, how secrets are encrypted at rest, retention and export, the audit trail, our sub-processors, and where we honestly stand on SOC 2 - no PDF, no download gate.
01Data flows
Most of your data
never reaches our servers at all.
The filesystem is a Unix-style VFS that lives in your browser. Connect real storage and most providers talk to your browser directly - our server’s only role is minting a short-lived access token. Two credential-based backends (S3, WebDAV) are the exception: those go through our server per request, decrypted just-in-time, never written to disk on our end.
A Unix-style VFS lives entirely in the browser. It never crosses the network on its own.
Direct - your files go straight between your browser and that provider. Our server mints a short-lived OAuth access token; it never sees the file content.
These two backends use stored credentials, not OAuth: each request is proxied through our own API route, which decrypts the credential and calls the provider on your behalf. Content passes through that one request; it is never persisted or logged.
Execution happens server-side (a Vercel function, or a sandboxed VM for long-running jobs). The task and its result touch Redis as job state; the conversation itself goes to whichever model provider your policy allows.
What actually touches Redis
- Job/run state, org policy, budgets, rate-limit counters, and the audit trail (lib/server/audit.ts, lib/server/teams/*)
- Encrypted secrets only - provider API keys and stored storage credentials, sealed by lib/server/crypto/secretBox.ts before they are ever written
- The gateway response/semantic cache (lib/server/gateway/responseCache.ts, semanticCache.ts) - model outputs, not your filesystem
- Never your VFS files, and never a Google Drive OAuth refresh token in plaintext (it is sealed the same way a provider key is)
Runs execute server-side
An agent run, a schedule, or a gateway call executes on our servers - either a bounded Vercel function step, or a persistent sandboxed VM for a long-running job. That execution is what lets governance (Settings → Governance) actually enforce a policy: the check runs where the model call is made, not inside a browser tab that could be tampered with.
02Encryption
Envelope encryption,
one scheme, two stores.
Every secret this app holds at rest - a provider API key for a background run, a Google Drive refresh token, a stored OneDrive/S3/WebDAV/Box credential - is sealed by the same small module: a fresh, random AES-256-GCM key encrypts the secret, and that per-record key is itself wrapped with AES-KW (RFC 3394) under a single wrapping key an operator provides as an environment variable, RUNNER_VAULT_KEK. Without that key, the ciphertext sitting in Redis is useless.
Two independent stores share that scheme by design: the agent runner’s own key vault, and the storage-connection credential store for OneDrive, SharePoint, Dropbox, Box, S3 and WebDAV. A compromise of the Redis data alone - with no access to the wrapping key - reveals nothing.
- Gateway API keys are never stored in plaintext at all - only a SHA-256 hash, with the key itself shown once, at creation
- Passwords use PBKDF2-SHA256 at 600,000 iterations with a random salt per password, not a fast general-purpose hash
- Every connection to this app, and every outbound call it makes on your behalf, is HTTPS - Vercel terminates TLS at the edge
- The sandboxed runtime surfaces (the in-browser VS Code and Windows tiers) run under cross-origin isolation (COEP/COOP), the same browser-level boundary a WASM-based OS needs regardless of transport security
sealSecret(envVar, plaintext)
kek = loadKek(envVar) /* RUNNER_VAULT_KEK, 256-bit */
recordKey = generateKey("AES-GCM", 256)
ciphertext = encrypt(recordKey, iv, plaintext)
wrappedKey = wrapKey(recordKey, kek, "AES-KW")
return { wrappedKey, iv, ciphertext }a real, simplified read of secretBox.ts's sealSecret - not a live console
03Retention & export
You set the clock.
You can take the record with you.
An org owner sets how long audit history and members’ background runs are kept; left unset, nothing is deleted by retention. A cron sweep applies it across every org, and Settings applies a shortened setting immediately rather than waiting for the next sweep.
An owner can pull a single compliance-export JSON at any time: org and membership records, governance policy plus every per-member override, MCP and skill registrations with any secret stripped, recent shared-run metadata, and the org’s full audit ring.
04Audit
Every governed action,
signed and forwardable.
Governance changes, spend events, runs, and guardrail triggers all land in an audit ring per account and per org, kept 400 days by default. Turn on audit streaming and the same entries are delivered, HMAC-signed, to a webhook URL you control - retried with exponential backoff, up to 20 attempts, before an item is given up on and logged as such.
Every audit entry carries who did it, what org it belongs to, and a plain-language description - the same text an admin reads in Settings and a SIEM sees in the payload.
05Sub-processors
Who else
ever sees a piece of this.
A short list, and it stays that way on purpose - every addition here is a new provider you’d have to trust, so the bar for adding one is real necessity, not convenience.
| Sub-processor | Role |
|---|---|
| Vercel | Hosting, serverless/edge functions, and sandboxed execution for long-running agent jobs. |
| Upstash (via Vercel Marketplace) | Redis, over its REST API - job/policy/audit/budget state and the gateway cache. See "what touches Redis" above. |
| Square | Billing and payment processing for paid plans. |
| Resend | Transactional email - team invites and notifications. Gated on RESEND_API_KEY/MAIL_FROM; a deployment without them simply sends no mail. |
| The model provider(s) your org routes to | Anthropic, OpenAI, Mistral, or Perplexity, only for the providers your governance policy allows - each call carries only what that request needs. A locally-run Ollama model never leaves your own machine and is not a sub-processor. |
| Google / GitHub (optional) | OAuth sign-in only if you choose them. Signing in with Google also connects Drive (the drive.file scope) - see the data-flow row above for why that traffic never touches our servers. |
In progress, not yet complete. See the questionnaire below for exactly what that means today.
06Security questionnaire
The questions a
vendor review actually asks.
Answered directly, in plain HTML, so it’s copy-pasteable into whatever tool your procurement process uses - no PDF, no gated download.
| Question | Answer |
|---|---|
| Is data encrypted at rest? | Every secret we hold at rest - provider API keys, stored cloud-storage credentials, a Drive refresh token - is sealed with AES-256-GCM under a per-record key, itself wrapped with AES-KW under an operator-provided key (RUNNER_VAULT_KEK). Your filesystem content lives in your browser or your storage provider, not on our servers, so there is no separate "files at rest" store to encrypt on our end. |
| Is data encrypted in transit? | Every request to this app, and every request it makes on your behalf, is HTTPS. Vercel terminates TLS at the edge for every route. |
| Where is my data stored? | Your filesystem lives in your browser (IndexedDB) and, if you connect one, your own Google Drive / OneDrive / SharePoint / Dropbox / Box / S3 / WebDAV account - see the data-flow section above for exactly which of those we ever touch in transit. Operational state (policy, budgets, audit, job records) lives in Upstash Redis, provisioned through Vercel. |
| Who are your sub-processors? | Vercel, Upstash, Square, Resend, and the model provider(s) your own governance policy allows. Full list and role above. |
| What is your data retention policy? | An org owner sets how long audit history and members’ background runs are kept (lib/server/teams/retention.ts) - unset means kept indefinitely, bounded only by the audit ring’s own 400-day TTL. A shortened setting is applied immediately, not just on the next sweep. |
| Can we export our data? | Yes - an owner can pull a single JSON compliance bundle: org and membership records, governance policy and per-member overrides, MCP/skill registrations (secrets stripped), recent shared run metadata, and the full audit ring (lib/server/teams/export.ts). |
| Is there an audit log? | Yes. Every governance change, spend event, run, and guardrail trigger is recorded per account and per org, kept 400 days by default (lib/server/audit.ts). It can also stream, HMAC-signed, to a webhook URL you own for ingestion into your own SIEM (lib/server/teams/auditStream.ts). |
| Do you support SSO? | An org owner can require sign-in through a specific provider (Google or GitHub today) and/or restrict it to specific email domains before a session is accepted at all (lib/server/teams/store.ts’s SSO enforcement). We do not yet support a SAML/OIDC identity-provider-initiated flow. |
| Do you support SCIM / automated provisioning? | Not yet as a live connector. The org role model (six roles: owner, admin, member, and the lateral billing_admin/policy_admin/viewer) already maps cleanly onto group-based provisioning, so wiring a SCIM connector to it is a scoped addition, not a redesign. |
| Do you require multi-factor authentication? | Not yet natively - Google and GitHub sign-in inherit whatever MFA you’ve configured on those accounts. Password sign-in (PBKDF2-SHA256, 600,000 iterations, lib/server/auth/passwords.ts) has no separate second factor today. |
| Have you completed a SOC 2 audit or a third-party penetration test? | SOC 2 Type II: in progress, not yet complete. No third-party penetration test has been performed as of this writing. If either is a hard requirement for your vendor review before that changes, tell us directly - we’d rather say so plainly than round up. |
| How are API keys and passwords stored? | A gateway API key’s plaintext is shown once, at creation, and never again - only its SHA-256 hash is stored (lib/server/gateway/keys.ts). Passwords are hashed with PBKDF2-SHA256 at 600,000 iterations with a random salt per password (lib/server/auth/passwords.ts). |
| Who at your company can access our data? | This is an independently operated product with no dedicated support or security team; the operator holds the production credentials and the encryption key, the same as any small team would before it scales one. What limits that access is architectural, not organizational: content that flows browser-direct to your storage provider never reaches our servers to begin with, and everything that does touch our infrastructure is scoped, audited, or encrypted as described on this page. |
cited to the file, not the deck