Skip to content

Audit: adversarial/reentrant state-transition edge cases in core runtime #341

Description

@smiggleworth

Purpose

This is an audit task, not a bug report — it produces a scenario list and spins off bugs, it doesn't fix anything itself.

The WebAuthn counter-rollback bypass found in askr-auth (#17 there) existed because tests covered normal state transitions but not the adversarial one the check was specifically meant to catch. This task looks for the core-runtime equivalent: state-mutation entry points where only the "expected" call pattern has ever been tested, not deliberately hostile/unusual ones.

Procedure

Identify every state-mutation entry point in core (state.set(), effect scheduling, resource resolution, component mount/unmount) and construct at least one adversarial sequence per entry point, e.g.:

  • Calling .set() reentrantly from inside another cell's updater function (see the reentrancy gap noted informally during the prior audit, adjacent to Scheduler.waitForFlush() leaks waiter entries on timeout #332)
  • Unmounting a component while its flush is still in-flight
  • An effect that schedules another effect that re-triggers the first, checking for infinite-loop protection or a clear error instead of a silent hang
  • A resource that resolves after its owning component has already unmounted
  • Calling waitForFlush() for a version that's already been superseded before the call is made (not just "never reached" as in Scheduler.waitForFlush() leaks waiter entries on timeout #332, but "already in the past")

Definition of Done

  • This issue's description contains a table: scenario | entry point | ✅ test exists and passes / 🔗 gap filed.
  • At least the five scenarios above are covered, plus any others found while doing this audit.
  • No row is left unmarked.

Audit results (2026-08-24)

Scenario Entry point Evidence Result
Reentrant .set() inside another cell updater state.set() tests/jsdom/runtime/hardening-matrix.test.tsx
Owner unmounts while flush is active component unmount / scheduler tests/jsdom/runtime/hardening-matrix.test.tsx
Effect schedules/retriggers effect effect scheduling tests/jsdom/runtime/effect-invariants.test.ts (bounded self-invalidating effect and sibling draining)
Resource resolves after owner unmount resource resolution tests/jsdom/operations/resource-unmount-inert.test.tsx; tests/jsdom/operations/state-resource-probes.test.tsx
waitForFlush() target already passed scheduler waiter tests/jsdom/runtime/hardening-matrix.test.tsx
Stale dependency generation resolves last resource resolution tests/jsdom/runtime/hardening-matrix.test.tsx; tests/jsdom/operations/resource-dep-ordering.test.tsx
Keyed row state changes during list reorder state / For reconciliation tests/jsdom/regressions/for-swap-local-state-coexistence.test.tsx

Implementation: #346. All adversarial sequences pass; no product gap was found.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions