Skip to content

Split the application-security audit domain into remote and local - #1105

Merged
nedtwigg merged 3 commits into
mainfrom
audit-domain-split
Oct 16, 2026
Merged

nedtwigg merged 3 commits into
mainfrom
audit-domain-split

Conversation

@nedtwigg

@nedtwigg nedtwigg commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

application-security owned both product security specs (127 FAIL IF bullets, a reading list of about 50 files) and was the catch-all for every product directory no other domain claimed. Its prompt sent the depth to remote control and left the rest as an open sweep. As a result, the Tauri IPC commands and capabilities, the webview CSPs, the updater's trust root, and agent-browser were named by no prompt, and twelve of the canary pool's thirteen seeds (and their misses) were this domain's. This PR splits it into two Opus domains, so the audit runs five domains and its canary four code domains.

Ownership map

Domain Model Spec (FAIL IF owner) Qualitative share
remote Opus docs/specs/security-remote.md relay/, remote-lib-common/, deploy/local/, SELF_HOST.md, and inside lib/ and vscode-ext/: lib/src/remote/, lib/src/host/remote/, lib/src/host/relay-origin.ts, lib/pocket/, lib/one-time/, vscode-ext/src/burrow*.ts. It reads remote-security-model.md (with its rationale), relay.md, remote-api.md, pocket-app.md, one-time.md, remote-network.md, and SELF_HOST.md for context.
local Opus docs/specs/security-local.md The catch-all, by recursive subtraction from supply-chain.md, ci-and-secrets.md, hosted.md, and remote.md. Today that is the rest of lib/ and vscode-ext/, plus standalone/, dor/, dor-lib-common/, dor-tools-builtin/, dor-tools-lib/, canopy/, deploy/blyg/, docs/, .impeccable/, and the remaining root files. Its qualitative pass works a named desktop-shell checklist covering Tauri IPC and capabilities, the webview CSPs, the updater's trust root, VS Code webview messages, spawned helpers and their listeners (agent-browser and Chrome, the sidecar, VS Code's PTY host), command construction and quoting for every shell, terminal output turned into typed input (recovery capture), and the built-in Tools. Each item names its files.
hosted, supply-chain, ci-and-secrets unchanged unchanged unchanged

Work streams: remote keeps remote-trust, remote-transport, remote-deploy, and qualitative. local keeps local-terminal, local-listeners, and qualitative. Each domain has exactly one qualitative pass.

Everything keyed by domain

  • Fragments: audit-remote.md and audit-local.md replace audit-application.md. They are added to AUDIT_FRAGMENTS and to the orchestrator's spawn, wait, verdict, and merge, and to the --agents JSON in the audit and canary jobs.
  • Canary: CANARY_DOMAINS, CANARY_FRAGMENTS, the canary orchestrator prompt, and the Domain: headers of the 12 application-security seeds on main are re-headed (10 to local, 2 to remote). The draw is dealt round-robin across the code domains (Audit canary: three seeds per code domain, dealt across the domains #1102), and each must hold at least three seeds, so a third remote seed is added: pairing-approval-outlives-uv-raise, a class seed that reverses part of Reduce Pocket passkey prompts with persisted sessions and Burrow presence windows #1044 (a user-verification raise while a pairing awaits local approval no longer voids the proof it rests on). The pool is now local 10, remote 3, hosted 3, supply-chain 3.
  • CANARY_SEEDS: 6 to 8, two whole rounds of the four code domains; 6 would deal two domains one seed each. scripts/security-audit-local.sh canary defaults to the same count.
  • Other files: scripts/security-audit-local.sh, the domain table in docs/specs/security.md, and docs/specs/security-audit.md (Domains, Canary recall, Future) plus its rationale. The pinned status issue (Publish the security audit's status in a pinned issue, and disclose findings after a fix or 7 days #1108) and the skipped-run report no longer say "four" domains.
  • Computed verdicts and the public body: both already derive from each prompt's **Scope and **Output file, so the new domains flow through without code changes.
  • Ledger: keys hash a rule's spec, heading, and number, or a finding's file and root cause. They never include a domain, so no open finding is reopened or orphaned. The preamble now routes an open finding that cites no rule to the domain whose share holds its file.
  • Top-level coverage FAIL IF: now pinned by scripts/security-audit.test.mjs. The test requires exactly one catch-all, and its subtraction must name every other domain's prompt.

Cost

The model reads roughly the same amount as before:

  • Added: the second domain reads the preamble and security.md one more time (about 5,500 words), and the two prompts together are about 530 words longer than the old one (2,531 vs 1,998).
  • Removed: local now reads three sections of dor-browser.md and two of dor-cli.md instead of both specs whole (about 6,500 fewer words). The checklist's named files take the place of the open sweep.
  • New spend: two Opus contexts instead of one, and the canary now runs four code domains instead of three.

Tests

scripts/security-audit.test.mjs:

  • every security spec's FAIL IF section is claimed by exactly one domain (or by the deterministic check alone)
  • every domain's fragment gets a verdict read and is merged
  • the public body has a row for every domain, including audit-remote.md and audit-local.md
  • security.md's table matches each domain's scope
  • exactly one domain takes the remainder of all the others

scripts/security-audit-canary.test.mjs:

  • the canary runs every code domain, and its prompt names each one
  • a draw's first eight seeds are two rounds of all four code domains
  • CANARY_SEEDS is whole rounds that the smallest domain's pool can fill

Each new assertion was checked to go red when its guarded line is reverted: a duplicated or dropped scope claim, a dropped emit, wait, or verdict entry, the old AUDIT_FRAGMENTS, the old security.md row, a catch-all that forgets remote.md or a second catch-all, the old CANARY_DOMAINS, a canary prompt that drops local, and CANARY_SEEDS deleted, at 6, or at 16.

The new seed applies alone and over the whole pool, and with it applied the lib remote suites (1265 tests), e2e-lint, outbound-lint, and loopback-lint stay green.

Locally green: node --test scripts/security-audit.test.mjs scripts/security-audit-canary.test.mjs (153/153), scripts/github-state-check.test.mjs, pnpm lint:specs (security.md budget ratcheted 2850 to 2900 for the added row, security-audit.md 4550 to 4600), lint:md, lint:workflows, workflow-lint.test.mjs, e2e-lint, lint:public-docs, and actionlint.

Post-merge verification

A canary or audit run on main is the only place the split can be measured, so nothing was dispatched from this branch. After merge:

  1. Dispatch security-audit.yaml on main (gh workflow run security-audit.yaml --ref main). Its decrypted audit-transcript should hold separate, finished audit-remote.md and audit-local.md fragments, and the public issue's table (if any) should list both.
  2. Dispatch a canary (-f canary=true). The scorecard's domains should show remote and local scored separately, with no domain left unfinished.

Merge note: this changes .github/audit/ and .github/workflows/security-audit.yaml, which are inside workflow-audit.yaml's window, so the maintainer merges. It is merged up to main with #1102 and #1108.

🤖 Generated with Claude Code

`application-security` owned both product security specs (127 FAIL IF
bullets) and was the catch-all for every unclaimed product directory, so
the desktop shells' surfaces went unowned and every canary miss landed
there. It becomes two Opus domains:

- `remote` owns docs/specs/security-remote.md and claims relay/,
  remote-lib-common/, deploy/local/, SELF_HOST.md, and the remote trees
  inside lib/ and vscode-ext/.
- `local` owns docs/specs/security-local.md, works a named desktop-shell
  checklist (Tauri IPC and capabilities, webview CSPs, the updater's trust
  root, VS Code webview messages, spawned helpers and agent-browser, shell
  quoting, recovery capture, the built-in Tools), and takes the remainder
  by subtraction.

Every per-domain artifact follows: fragments, AUDIT_FRAGMENTS, the
orchestrator's spawn, wait, verdict and merge, the agents JSON in both
jobs, the canary's domains and seed headers, the local runner, and
security.md's domain table. Ledger keys never named a domain, so no open
finding moves. New tests pin per-section ownership, the merge and verdict
of every domain's fragment, the public body's rows, security.md's table,
the single catch-all, and the canary's domain set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 94f4e8d
Status: ✅  Deploy successful!
Preview URL: https://0001665d.mouseterm.pages.dev
Branch Preview URL: https://audit-domain-split.mouseterm.pages.dev

View logs

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This split and #1102 will break each other, whichever one merges second, and the conflict goes beyond the Domain: re-heading the description mentions. #1102 replaces the pool-size check in scripts/security-audit-canary.test.mjs with one check per domain:

for (const d of CANARY_DOMAINS) assert.ok(pool.filter((s) => s.domain === d).length >= 3, `${d} has fewer than three seeds`);

With this PR's CANARY_DOMAINS = ['supply-chain', 'remote', 'local', 'hosted'], remote has 2 seeds (local-networks-unreadable-pair, relay-mode-fails-open). That check would fail on main as soon as both PRs are in. #1102 also deals CANARY_SEEDS: "6" round-robin across the domains. Over four domains that gives 2/2/1/1, not the two per domain its rationale describes. So whichever PR lands second needs a third remote seed and a fresh look at CANARY_SEEDS (8 would give two per domain again).

nedtwigg and others added 2 commits October 16, 2026 12:28
# Conflicts:
#	docs/specs/security-audit.md
#	scripts/security-audit.test.mjs
The remote domain held two seeds, under the pool's floor of three. The
new seed reverses part of #1044: a user-verification raise while a
pairing awaits local approval no longer voids the proof it rests on. It
is a class seed, beside remote's two rule seeds.

CANARY_SEEDS goes from 6 to 8, two whole rounds of the four code
domains; six would deal two domains one seed each. A test pins the count
to whole rounds the smallest domain can fill, and the local runner's
default follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dormouse-bot dormouse-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description's merge note is now stale: #1102 is merged into this branch, and this push adds the third remote seed and moves CANARY_SEEDS to 8, so "this PR leaves them alone" and "2 to remote" no longer describe the diff.

@nedtwigg
nedtwigg merged commit 67683cc into main Oct 16, 2026
13 checks passed
@nedtwigg
nedtwigg deleted the audit-domain-split branch October 16, 2026 19:57

This branch is waiting to be deployed

1 waiting deployment
hosted-preview — 94f4e8d6 Waiting Oct 16, 2026 by nedtwigg via cleanup #1388
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