If you find a vulnerability in Invincible, please open a private security advisory on GitHub (or email the maintainer) rather than a public issue with exploit details.
| Never commit | Where it lives |
|---|---|
AI_GATEWAY_API_KEY |
Vercel project env only |
HARNESS_ARTIFACT_TOKEN |
Vercel (Actions: Read PAT for artifact download) |
VERCEL_DEPLOY_HOOK_URL |
GitHub Actions secrets |
SANDBOX_TOKEN |
Vercel project env and sandbox process env (same secret) |
DATABASE_URL |
Vercel / local only (prefer pooled Neon/PgBouncer URL) |
CREDENTIALS_ENCRYPTION_KEY |
Vercel / local only — base64 32-byte AES-256-GCM AMK (wraps per-tenant DEKs; tokens encrypt under DEK) |
SEED_ADMIN_PASSWORD / SEED_SANDBOX_TOKEN |
Bootstrap only (prefer GHA db-tenancy-bootstrap; cloud-agent npm run db:seed alternate); never commit; re-seed resets bootstrap password + token ciphertext |
AUTH_SECRET |
Auth.js session secret — set on Vercel after migrate/seed (seed does not need it) |
AUTH_OIDC_CLIENT_SECRET |
Optional OIDC client secret — Vercel/server only; never NEXT_PUBLIC_* |
SCIM_BEARER_TOKEN |
Optional SCIM shared bearer — Vercel/server only; IdP → /api/scim/v2; never client/Wasm |
| Runner registration tokens, DO API tokens | Operator machines only |
Session blobs and Wasm must never contain API keys or sandbox tokens.
Never use NEXT_PUBLIC_SANDBOX_* (or any client-exposed sandbox secret).
When BUILTIN_HTTP_FETCH=sandbox, agent tools may fetch public HTTPS URLs via a
Vercel Sandbox microVM (hop B). App-side SSRF policy runs first (https-only; no
private/metadata hosts; no redirect follow). Never put Gateway, DO sandbox, or
MCP secrets into the Sandbox child env. No NEXT_PUBLIC_* for this feature.
See docs/builtin-http.md.
Residual (v1): Policy is preflight-only on the app (literal + DNS at check
time). Hop B re-resolves the hostname under Sandbox networkPolicy: allow-all.
A short-TTL / DNS-rebinding name could flip to a private or link-local address
between preflight and curl. Redirects are not followed. Accept for v1; harden
later (IP pin / egress deny) if product needs stronger guarantees.
Zig builds run on a self-hosted GitHub Actions runner (default labels: self-hosted, invincible, zig).
To reduce abuse risk when this repository is public:
- Default triggers on self-hosted workflows:
pushtomain(path-filtered) andworkflow_dispatch. build-harnessonly also runs onpull_request→mainwhen all of:- head branch is in this repository (
head.repo.full_name == github.repository) — not forks; author_associationis one ofOWNER/MEMBER/COLLABORATOR/CONTRIBUTOR;- path filter matches harness sources / this workflow.
- Never
pull_request_target. Never Vercel deploy hook on PR. Other self-hosted workflows stay main/workflow_dispatchonly.
- head branch is in this repository (
- Jobs include
if:guards:- Opt-in:
vars.SELF_HOSTED_BUILDS == 'true'(repository Actions variable, not a secret), or - Origin grandfather:
github.repository == 'btipling/invincible'(maintainer continuity if the variable is unset) - and event is
workflow_dispatch,pushtomain, or the same-repo contributorpull_requestrules above (build-harness).
- Opt-in:
- Clones / forks must set
SELF_HOSTED_BUILDS=trueafter attaching their own runner. Without that variable, self-hosted jobs skip (safe default). - Optional:
vars.RUNNER_LABELSas a JSON array (e.g.["self-hosted","invincible","zig"]). If unset, workflows use that default list viafromJSON. - Do not add fork PR or
pull_request_targetbuilds on self-hosted without a deliberate design review. Expanding PR CI beyond same-repo contributor heads is a security change. - Prefer GitHub setting: require approval for first-time contributors’ workflows; keep fork PR workflows off the self-hosted pool.
- Host inventory (IPs, droplet IDs) is not published in this repo — keep private notes offline.
| Setting | Kind | Purpose |
|---|---|---|
SELF_HOSTED_BUILDS |
Actions variable (true) |
Enable self-hosted jobs on this repository |
RUNNER_LABELS |
Actions variable (JSON array) | Optional runs-on labels override |
VERCEL_DEPLOY_HOOK_URL |
Actions secret | Post-artifact redeploy (origin; main / dispatch only) |
Maintainers: still harden the VM (SSH keys, firewall, unattended upgrades) using private runbooks; public docs stay abstract.
The agent sandbox is an optional remote workspace for model tools
(list_dir / read_file / write_file / exec). It is not the
self-hosted GHA runner that compiles Zig.
| Rule | Detail |
|---|---|
| Separate process | Dedicated OS user/unit; do not share Actions credentials with the sandbox env |
| Server-only calls | Only Vercel/Node server calls SANDBOX_URL with Bearer SANDBOX_TOKEN |
| Path jail | Workspace root + symlink-safe resolve; argv-only exec with timeouts |
| Public inventory | Host IPs / droplet IDs stay offline (docs/sandbox.md) |
| No PR trigger surface | Sandbox is not executed by untrusted PR workflows |
Inference is server-side only (POST /api/chat, POST /api/agent). Report
client-side key or sandbox-token exposure immediately.
| Rule | Detail |
|---|---|
| Triple-env gate | Tenancy on only when DATABASE_URL and AUTH_SECRET and CREDENTIALS_ENCRYPTION_KEY are set — no separate AUTH_ENABLED |
| Tokens at rest | Envelope: env AMK (CREDENTIALS_ENCRYPTION_KEY) wraps each per-tenant DEK; sandbox bearer secrets AES-256-GCM under that tenant’s DEK only. Decrypt server-side for agent tools / admin mask only |
| Provider secrets (BYOK) | Ciphertext under tenant DEK only (no AMK dual-read path). Admin mask only; never plaintext in client/Wasm/logs. Schema migrate: GHA db-migrate |
| Per-user MCP API keys | Ciphertext under tenant DEK on user_mcp_servers. Settings mask only; never plaintext in client/Wasm/logs. HTTPS-only URL policy + no redirect follow (SSRF). Schema: GHA db-migrate. Ops: docs/mcp.md |
| Tenancy-on inference | Chat/agent always attach request-scoped providerOptions.gateway.byok + only for a granted model. Never route via env DEFAULT_MODEL / AGENT_MODEL when tenancy is on. Unauthorized / empty grants → 4xx |
| Residual (platform) | Invincible does not fall back to host env-model routing under tenancy on. Vercel AI Gateway remains a third party: (1) BYOK requires paid AI Gateway credits on the Vercel team — free tier does not allow request-scoped BYOK even with valid provider keys (pricing); (2) misconfigured BYOK / provider errors still surface from the platform. Mitigate with always-send BYOK, only: [provider], top up credits, surface errors, redact secret material from error JSON |
| Redaction | Inference error paths redact provider secret material via resolve redact lists |
| Dual-read cutover | TENANT_TOKEN_DECRYPT_MODE: default dual (DEK then AMK) until backfill verified; then dek-only. Order: dual-read app live → GHA db-tenancy-backfill-deks (confirm=backfill, job sets ALLOW_TENANT_DEK_BACKFILL=1) → verify → dek-only. Never backfill under AMK-only runtime |
| DEK rotate | Owner-only (rotateTenantDek / /admin); re-encrypts that tenant’s sandbox tokens, provider secrets, and MCP header ciphertexts; never shows DEK/token/plaintext. Other tenants untouched |
| AMK rotate | Not automated. Changing Production AMK without a re-wrap tool breaks all DEK unwraps. Keep GHA CREDENTIALS_ENCRYPTION_KEY === Vercel Production AMK (dual-store). Re-wrap is a future sequel |
| Never client | No NEXT_PUBLIC_* for DB, Auth.js secret, AMK/DEK, sandbox token, provider API keys, MCP API keys, OIDC client secret, or SCIM bearer |
| Preview isolation | Prefer separate DB or tenancy off on public previews; avoid reusing Production AMK, OIDC client secret, or SCIM_BEARER_TOKEN casually |
| Seed vs backfill | Seed = greenfield / bootstrap via GHA db-tenancy-bootstrap (resets password hash + token ciphertext; keeps existing DEK). Existing Production data = GHA db-tenancy-backfill-deks only — not seed |
| Bootstrap / backfill / schema surface | Prefer GitHub Actions: db-tenancy-bootstrap (seed), db-tenancy-backfill-deks (AMK→DEK data), db-migrate (schema-only, e.g. provider secrets / user_mcp_servers) — or cloud agent workspace. Not personal-laptop primary ops |
| OIDC (optional) | AUTH_OIDC_ISSUER + AUTH_OIDC_CLIENT_ID + AUTH_OIDC_CLIENT_SECRET (+ optional AUTH_OIDC_LABEL); provider id oidc; callback /api/auth/callback/oidc; email auto-link requires verified email_verified claim |
| SCIM (optional) | SCIM_BEARER_TOKEN + tenancy triple; base /api/scim/v2; off → 404; bad Bearer → 401; DELETE = suspend |
| Hybrid roster | SCIM is additive — non-SCIM users remain; /admin lists all provision sources; SCIM list = SCIM-managed only |
| Break-glass | Credentials login remains when tenancy is on; SCIM must not suspend break-glass credentials owner |
Unauthenticated API when tenancy is on returns 401 with JSON
{ "error": "Authentication required." } (stable error constant
AUTH_REQUIRED_ERROR). Sandbox grant failures return 403
{ "error": "Sandbox access denied." } (SANDBOX_FORBIDDEN_ERROR).
Inference grant / model failures return 403 / 400
(INFERENCE_FORBIDDEN_ERROR / INFERENCE_MODEL_REQUIRED_ERROR); temporary
resolve/catalog failures return 503 (INFERENCE_UNAVAILABLE_ERROR).
Cutover: docs/bring-your-own.md §4a.
OIDC / SCIM operator notes: docs/bring-your-own.md §4b.