Skip to main content
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 MODEL v1.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>