Skip to content

feat(security): household visibility threat model with executable fail-closed access cases - #4

Merged
OCPdev25 merged 1 commit into
masterfrom
security/threat-model
Sep 17, 2026
Merged

OCPdev25 merged 1 commit into
masterfrom
security/threat-model

Conversation

@obvious-autobuild

Copy link
Copy Markdown
Contributor

Human author: Gilbert Polanco (gilbertpolanco42@gmail.com)

Why

The journal's most sensitive data — raw caregiver transcripts, child-scoped timelines, extraction events — has no documented authorization model. In a co-parenting product, the realistic attacker is not a hacker but a credentialed, well-meaning caregiver of a different household whose every request is well-formed. Without an explicit model, the failure mode is ad-hoc checks that leak one boundary at a time.

What

  • security/THREAT-MODEL.md — actors (Caregiver A, Caregiver B, non-member caregiver, anonymous), assets (child profile, entries, raw transcripts — highest sensitivity, extraction events), trust boundaries (authentication edge, household membership, child scoping, publication state), 11 abuse cases with severity, and the fail-closed rules.
  • The two-dimension rule (core decision): publication state (Entry.status: draft|published, contract v0.1) and audience permission (household membership × child scoping) are SEPARATE dimensions — deliberately not one draft/shared enum. Publication never widens audience; membership never pierces drafts. Truth table + composition test (DIM-1) included.
  • security/access/policy.ts — evaluateAccess, a pure fail-closed decision function with contract-denial reason codes; visibleEntries timeline projection.
  • security/access/policy.test.ts — 17 executable named cases: cross-household read (AB-1), cross-household write (AB-2), non-member reads any child (AB-3), draft invisible to co-member but visible to author (AB-4 + PC-4), anonymous read/write (AB-5/6), published-but-still-scoped (AB-7/7b), attribution spoof (AB-9), draft-event side-channel (AB-10), fail-closed on missing scoping data (AB-12/12b), plus 4 positive controls proving denials are surgical. Every test documents the exact assertion that denies access (outcome AND code pinned — denying for the wrong reason fails the suite).

Dependency label (pending, per contract rule 1): the schema contract landed in spikes/effect-compat on branch spike/effect-contracts-adapters, which was not pushed to origin when this was written. Tests run against the documented shape mock (schema-mock.ts, field-for-field from art_I2TCG08V); wiring to the real package is pending — swap the import when the spike lands. Household/child scoping types are labeled required contract extensions (§8), not schema claims.

How to Review

  • Start with security/THREAT-MODEL.md §5 (two-dimension rule) and §6 (abuse-case table mapping each abuse case to its executable test).
  • security/access/policy.ts is the whole enforcement surface — 5 ordered checks, first failure denies.
  • Deliberately out of scope: Convex enforcement wiring, roster storage, session resolution (§9 — reference function only). Visual evidence is not applicable: backend-only authorization policy with no UI surface.
  • Zero dependencies; no package.json added (package-manager-neutral while the scaffold lands).

Test Evidence

bun test ./security — 17 pass, 0 fail, 22 assertions (bun 1.3.14). Named cases visible in run output:

(pass) PC-1…PC-4            positive controls (ALLOW)
(pass) AB-1: caregiver of household 1 reading household 2's child timeline → DENIED
(pass) AB-2: writing an entry to another household's child → DENIED
(pass) AB-3: non-member reading ANY child → DENIED (every child, both households)
(pass) AB-7/AB-7b: published entry still denied to non-member (publication ≠ audience)
(pass) AB-4: draft invisible to co-member, visible to author
(pass) AB-10: draft's extraction events invisible to co-member
(pass) DIM-1: one timeline, three principals, three projections (2/1/0 entries)
(pass) AB-5/AB-6: anonymous read/write → DENIED
(pass) AB-9: attribution spoof → DENIED
(pass) AB-12/AB-12b: missing scoping data → DENIED (fail-closed)

Also tsc --strict --exactOptionalPropertyTypes clean on security/access/*.ts.

🔗 Obvious Project · 🧵 Obvious Thread

…losed access cases

Delivers security/THREAT-MODEL.md (actors, assets, trust boundaries,
two-dimension rule, abuse cases with severity) and executable negative
access cases against contract v0.1 shapes (art_I2TCG08V): 17 tests, all
passing, each documenting the exact deny assertion. Spike package not
pushed; shapes mocked per contract rule 1, wiring labeled pending.

Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
@obvious-autobuild
obvious-autobuild Bot marked this pull request as ready for review September 17, 2026 17:36
@OCPdev25
OCPdev25 merged commit a86ce99 into master Sep 17, 2026
obvious-autobuild Bot pushed a commit that referenced this pull request Sep 17, 2026
…without a source

Product-owner ruling (binding): absence of logged sleep data may never be
stated as a confirmed absence ("No nap today") nor carry a behavioral
prediction ("expect an early meltdown"). The takeover brief fact-4
headline, plan note, Q&A answer, and both citation refs now state
absence-of-record ("No nap recorded", "coverage: ..."), ported verbatim
from the canonical arena fixture (c3c1dc3).

Tests: five-fact #4 asserts the corrected headline; the Q&A source
assertion pins "coverage:"; a new negative control sweeps the whole
fixture so "meltdown" can only appear in the drop-off event's own
context — never as a forecast — and bans confirmed-absence phrasing on
all prediction surfaces. Suite: 24 tests / 113 assertions, green on
this HEAD (negative control verified to fail on regressed text).

Co-authored-by: Gilbert Polanco <gilbertpolanco42@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants