Security model of a vibe app
Mirrored from
iblai/vibeΒ·docs/security-model.md. This page is generated β edit it in the repository, not here.
What credentials exist, where each one lives, why, and what that means for you. Written for the reader of iblai/vibe#167 ("Auth tokens and tenant state stored in plain localStorage") and for anyone doing a security review of an app built from vibe-starter.
Two kinds of credential
| Session tokens | Platform API Token | |
|---|---|---|
| What | axd_token, dm_token, edx_jwt_token (+ userData, tenants, current_tenant) | IBLAI_API_KEY (TOKEN in iblai.env) |
| Whose authority | the signed-in user β exactly their permissions in exactly their org | the organization β admin-level, org-scoped |
| Where it lives | the browser's localStorage for the app's origin | the server's environment only (.env.local, the hosting platform's env); config.apiKey() returns "" in the browser by construction |
| Sent as | Authorization: Token <dm_token> by the SDK; Bearer on the AXD host | Authorization: Api-Token <key>; Bearer only on the OpenAI-compatible /v1 |
| Lifetime | short; *_expires companions; refreshed by the SSO flow | until deleted in the OS (Integrations β APIs) |
| Threat it is exposed to | XSS on your origin (anything that runs JS on your page can read it) | server compromise, leaked env, a commit of .env.local |
Why session tokens are in localStorage
The platform's sign-in is a hosted SSO flow: login.iblai.app authenticates
the user and returns them to your app at /sso-login-complete with the token
set in the URL, which SsoLogin stores. Every ibl.ai front end (the OS, the
LMS, vibe apps, the embeddable chat) shares this design:
- Cross-app sync.
AuthProvider'senableStorageSynclets several ibl.ai front ends on related origins observe one sign-in/sign-out. - No app server needed. vibe apps can be fully static or run in a Tauri WebView with no session backend; the SDK's RTK Query layer attaches the token itself.
- Multi-org routing.
tenants(the orgs the user belongs to, withis_admin) is whatTenantProvideruses to decide the user may enter the org the app is pinned to β before any API call.
An httpOnly-cookie session would need a first-party backend on every origin, which is exactly what this design avoids. The trade-off is that XSS is the threat model, so the template invests in the mitigation for XSS:
Content Security Policy
middleware.ts calls the SDK's applyCsp, which sets a per-request,
nonce-based CSP with strict-dynamic:
- Production builds enforce by default (the SDK's own resolution).
next devruns report-only so React Refresh /eval()and the error overlay work β the template passesmode: 'report-only'only whenNODE_ENV === 'development', which Next inlines per build command.- Runtime override:
CSP_MODE=report-only(validated by the SDK; unknown values fall safe to report-only). Use it to diagnose a blocked third-party script in production, then remove it. app/layout.tsxsetsdynamic = "force-dynamic"because a statically prerendered page would ship nonce-less<script>tags that enforce mode blocks.
Check what a deployed app sends: curl -sI https://<your-app>/ | grep -i content-security-policy.
What the template does with the Platform API Token
- It is read only in server files (
lib/iblai/platform.ts,app/api/**). - Every admin route first proves who is calling (the browser forwards its
session
dm_token; the server verifies it againstGET /dm/api/core/token/verify/) and that they administer the org (requireAdmin), and only then acts with the org's key. iblai.env*and.env*are gitignored; the/iblai-vibe-ops-initskill never echoes the token;/iblai-vibe-ops-deploypasses it to the hosting platform as a server env var..npmrc(written by ops-init) pinsminimum-release-ageandsave-exactto blunt supply-chain attacks on dependencies.
What you should still do
- Keep third-party scripts out of the app, or add them with the nonce.
- Never put a secret in org metadata (it is a public read) or in user metadata (readable by the user and org admins).
- Rotate the Platform API Token if it ever appears in a log or a transcript (Integrations β APIs β delete, then Add API).
- For a Tauri build, the WebView origin is the app; the same rules apply. Mobile sign-in goes through the system browser and returns via a custom scheme β list that scheme in the org's allowed redirect origins.
Related: glossary.md Β· /iblai-vibe-auth Β· /iblai-vibe-api Β· /iblai-vibe-ops-deploy.