Compendium/Specs & Documents/Criteria & Constraints/Threat Model Table
#004
Threat Model Table
A three-column STRIDE-style threat model — threat, mitigation, and out-of-scope columns, rendered as a typeset legal exhibit.
STABLE GO
#list
#real-content
#dark-mode-tested
Live
EXHIBIT B / THREAT MODELv1.3.0
SEV
THREAT
MITIGATION
OUT-OF-SCOPE
HIGH
Credential stuffing on /auth
Rate-limit + lockout @ 10/min
Botnet behind residential proxies
MED
Session token replay
Bind to UA + IP fingerprint
Mobile NAT roaming churn
LOW
OAuth open-redirect
Allowlist redirect domains
Subdomain takeover detection
HIGH
PII in error responses
Sanitise + opaque error codes
Error timing oracles
Spec
Intent
Present the threat-model section of a spec as a dense, scannable table. Three columns: the threat, the encoded mitigation, and what the spec explicitly does NOT defend against (out-of-scope). The out-of-scope column is what distinguishes a real spec from wishful thinking.
Acceptance Criteria
[ ] Three columns with distinct headers
[ ] At least 3 threat rows with all three columns populated
[ ] Out-of-scope column visually de-emphasised but legible
[ ] Severity glyph per row
[ ] Does not rely on client JS to render
Constraints
Pure markup; no JS
Reads on light + dark themes
// threat-model-table.astro
---
import type { CompendiumMeta } from '../../../data/types';
export const meta: CompendiumMeta = {
id: 4,
slug: 'threat-model-table',
name: 'Threat Model Table',
category: 'specs-documents',
subcategory: 'criteria-and-constraints',
pillar: 'go',
description: 'A three-column STRIDE-style threat model — threat, mitigation, and out-of-scope columns, rendered as a typeset legal exhibit.',
spec: `## Intent
Present the threat-model section of a spec as a dense, scannable table. Three columns: the threat, the encoded mitigation, and what the spec explicitly does NOT defend against (out-of-scope). The out-of-scope column is what distinguishes a real spec from wishful thinking.
## Acceptance Criteria
- [ ] Three columns with distinct headers
- [ ] At least 3 threat rows with all three columns populated
- [ ] Out-of-scope column visually de-emphasised but legible
- [ ] Severity glyph per row
- [ ] Does not rely on client JS to render
## Constraints
- Pure markup; no JS
- Reads on light + dark themes`,
tags: ['list', 'real-content', 'dark-mode-tested'],
status: 'stable',
health: { ics: 100, a11y: 'aa', themed: true, animated: false },
};
const rows = [
{ sev: 'HIGH', threat: 'Credential stuffing on /auth', mit: 'Rate-limit + lockout @ 10/min', oos: 'Botnet behind residential proxies' },
{ sev: 'MED', threat: 'Session token replay', mit: 'Bind to UA + IP fingerprint', oos: 'Mobile NAT roaming churn' },
{ sev: 'LOW', threat: 'OAuth open-redirect', mit: 'Allowlist redirect domains', oos: 'Subdomain takeover detection' },
{ sev: 'HIGH', threat: 'PII in error responses', mit: 'Sanitise + opaque error codes', oos: 'Error timing oracles' },
];
---
<section class="w-full bg-paper text-black border-4 border-black shadow-nb-sm overflow-hidden">
<header class="px-3 py-2 border-b-2 border-black flex items-baseline justify-between">
<span class="font-mono text-[10px] uppercase tracking-widest font-black">EXHIBIT B / THREAT MODEL</span>
<span class="font-mono text-[9px] uppercase tracking-widest opacity-60">v1.3.0</span>
</header>
<table class="w-full text-[10px] leading-snug">
<thead class="bg-ink text-paper">
<tr class="text-start">
<th class="px-2 py-1.5 font-mono uppercase tracking-widest border-e-2 border-paper w-12">SEV</th>
<th class="px-2 py-1.5 font-mono uppercase tracking-widest border-e-2 border-paper">THREAT</th>
<th class="px-2 py-1.5 font-mono uppercase tracking-widest border-e-2 border-paper">MITIGATION</th>
<th class="px-2 py-1.5 font-mono uppercase tracking-widest">OUT-OF-SCOPE</th>
</tr>
</thead>
<tbody class="divide-y-2 divide-black">
{rows.map((r) => (
<tr>
<td class:list={[
'px-2 py-1.5 border-e-2 border-black font-mono font-black text-[9px] uppercase tracking-widest text-center',
r.sev === 'HIGH' && 'bg-attention text-white',
r.sev === 'MED' && 'bg-do text-black',
r.sev === 'LOW' && 'bg-paper text-black',
]}>{r.sev}</td>
<td class="px-2 py-1.5 border-e-2 border-black">{r.threat}</td>
<td class="px-2 py-1.5 border-e-2 border-black font-mono opacity-90">{r.mit}</td>
<td class="px-2 py-1.5 italic opacity-60">{r.oos}</td>
</tr>
))}
</tbody>
</table>
</section>