You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
141 lines
5.0 KiB
141 lines
5.0 KiB
<script lang="ts">
|
|
import Callout from '../_components/Callout.svelte';
|
|
import CodeBlock from '../_components/CodeBlock.svelte';
|
|
import PageNav from '../_components/PageNav.svelte';
|
|
|
|
const securityRules = `$auth -> proves identity and binds successful authentication to session
|
|
$session -> owns session continuity, refresh, revocation and browser sync
|
|
$perm -> decides authorization for an authenticated actor
|
|
$cache -> must scope private data by actor, tenant, permissions and locale
|
|
$prefs -> owns user preference intent and effective environment-derived values
|
|
$storage -> persists non-secret local state and can back the prefs storage bridge
|
|
$logger -> receives security diagnostics with redacted payloads`;
|
|
|
|
const noSecrets = `// Good: store a theme preference
|
|
App.storage.entry('theme', 'base').set('forest');
|
|
|
|
// Bad: never persist secrets in client storage
|
|
App.storage.entry('access-token', token).set(token);
|
|
App.storage.entry('csrf-token', token).set(token);`;
|
|
|
|
const csrfFlow = `1. Client asks auth for a CSRF token.
|
|
2. Auth returns a short-lived token and helper cookie.
|
|
3. Client sends the token in the configured CSRF header.
|
|
4. Server validates token, cookie, origin/fetch metadata and expiry.
|
|
5. State-changing auth action continues only if validation passes.`;
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>Security — Active</title>
|
|
</svelte:head>
|
|
|
|
<article class="article">
|
|
<header class="breadcrumbs">
|
|
<a href="/active">Get Started</a>
|
|
<span aria-hidden="true">/</span>
|
|
<span>Security</span>
|
|
</header>
|
|
|
|
<h1>Security model</h1>
|
|
<p class="lead">
|
|
Active treats identity, sessions, permissions, storage and cache as separate security surfaces.
|
|
The active client improves UX, but the server layer remains authoritative.
|
|
</p>
|
|
|
|
<Callout variant="warn" title="1.0 security gate">
|
|
<p>
|
|
Active should not be tagged as <code>1.0</code> until the security release checklist is
|
|
closed and documented in <code>docs/SECURITY.md</code>. MFA and production
|
|
WebAuthn/passkeys are intentionally outside the stable surface unless the release notes
|
|
explicitly include them.
|
|
</p>
|
|
</Callout>
|
|
|
|
<h2>Report privately</h2>
|
|
<p>
|
|
Do not open public issues for suspected vulnerabilities. Use the private advisory flow described
|
|
in <code>docs/SECURITY.md</code>. Include the affected module, commit hash, reproduction steps and
|
|
whether cookies, tokens, permissions or cross-tenant data are involved.
|
|
</p>
|
|
|
|
<h2>Module boundaries</h2>
|
|
<p>
|
|
Security-sensitive work starts by choosing the correct owner. Avoid moving authority to the
|
|
client just because it is convenient for a page.
|
|
</p>
|
|
<CodeBlock code={securityRules} lang="text" />
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Surface</th>
|
|
<th>Rule</th>
|
|
<th>Failure mode</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>auth</code></td>
|
|
<td>Flows are server-side and short-lived.</td>
|
|
<td>Login CSRF, stale flow replay, OAuth state confusion.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>$session</code></td>
|
|
<td>The session cookie is the continuity source.</td>
|
|
<td>Session fixation or logout that resurrects state.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>perm</code></td>
|
|
<td>Authorization decisions are actor and tenant scoped.</td>
|
|
<td>Actor A decisions leak into actor B after hydrate.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>$cache</code></td>
|
|
<td>Private cache entries require explicit scope.</td>
|
|
<td>Cross-user, cross-tenant or stale-permission data leaks.</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>$storage</code></td>
|
|
<td>Client storage is not a secret vault.</td>
|
|
<td>Tokens readable by script or browser extensions.</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h2>Cookies and CSRF</h2>
|
|
<p>
|
|
The session cookie belongs to <code>$session</code>. <code>auth</code> owns helper cookies and CSRF validation
|
|
for auth actions. Defaults are intentionally strict for the browser case.
|
|
</p>
|
|
<CodeBlock code={csrfFlow} lang="text" />
|
|
|
|
<h2>OAuth and PKCE</h2>
|
|
<p>
|
|
OAuth state and the PKCE verifier are created by <code>startOAuth</code> and persisted in the
|
|
server flow. <code>completeOAuth</code> reads the stored verifier, verifies that it still matches
|
|
the flow hash and passes it to the provider adapter. The callback does not trust a browser-supplied
|
|
verifier.
|
|
</p>
|
|
|
|
<h2>Storage rule</h2>
|
|
<p>
|
|
Use <code>$storage</code> for drafts and non-secret local state. Use <code>$prefs</code> for
|
|
user preference intent. Do not persist access tokens,
|
|
refresh tokens, passwords, OTPs or CSRF tokens in client storage.
|
|
</p>
|
|
<CodeBlock code={noSecrets} lang="ts" />
|
|
|
|
<h2>Regression requirements</h2>
|
|
<ul>
|
|
<li>Every security bug fix needs a regression test.</li>
|
|
<li>Every exported stable method must work or be removed from the stable surface.</li>
|
|
<li>Every memory adapter must warn in production mode.</li>
|
|
<li>
|
|
Every private cache or permission decision must include an actor, tenant or explicit scope.
|
|
</li>
|
|
<li>Every public route, cookie, header, event and logger message must come from constants.</li>
|
|
</ul>
|
|
|
|
<PageNav />
|
|
</article>
|