From 5b7ea046379012181ecb65ff0ea093ad1aff187b Mon Sep 17 00:00:00 2001 From: dormouse-bot <287024035+dormouse-bot@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:02:39 +0000 Subject: [PATCH 1/3] docs(security): name every dotfile directory in the audit scopes, and dormouse-lib as a root Two findings from the 2026-08-26T18:10Z security audit (#447), both cases of SECURITY.md disagreeing with what it describes. `.vscode/` and `.impeccable/` are top-level directories that no domain's qualitative scope named. The FAIL IF that guards this is explicit that anything no domain names is nobody's job, and the enumeration's "the root files" catch-all reads as covering dotfile directories without ever naming them -- the same shape that orphaned `.claude/` the first time. `.vscode/` goes to ci-and-secrets alongside `.claude/`, since `tasks.json` and `launch.json` name shell commands; `.impeccable/design.json` is the design-token snapshot behind DESIGN.md, so it goes to application-security. Both audit subagent prompts get the same edit, since the FAIL IF checks their scopes rather than SECURITY.md's restatement of them. Separately, the "Dependency Supply Chain" prose named five roots and called `dormouse-lib` a workspace edge, while its own FAIL IF says "the six named above" and `productDependencyFilters` lists six including `dormouse-lib`. The code is right: vscode-ext imports the lib through relative paths into `../lib/src/` rather than declaring a dependency on it, so the extension's dependency walk never reaches it. Neither finding is a live exposure; both would have been re-reported by every future audit run. --- .github/audit/application-security.md | 9 +++++---- .github/audit/ci-and-secrets.md | 9 ++++++--- SECURITY.md | 8 ++++---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/audit/application-security.md b/.github/audit/application-security.md index 530baca0..74cf89bb 100644 --- a/.github/audit/application-security.md +++ b/.github/audit/application-security.md @@ -43,7 +43,8 @@ changed or is worse than described. You also own the **rest of the repository** qualitatively, so that no top-level path is outside every domain: `lib/`, `server/`, `server-lib-common/`, `standalone/`, `vscode-ext/`, `dor/`, `dor-lib-common/`, `canopy/`, `deploy/`, -`docs/`, and the root files. Remote control is where the depth goes; the rest -is a sweep for anything that would be a security hole in a terminal that runs -local shells — command construction, path handling, deserialization of -persisted state, IPC that crosses a trust boundary. +`docs/`, `.impeccable/` (the design-token snapshot behind `DESIGN.md`), and the +root files. Remote control is where the depth goes; the rest is a sweep for +anything that would be a security hole in a terminal that runs local shells — +command construction, path handling, deserialization of persisted state, IPC +that crosses a trust boundary. diff --git a/.github/audit/ci-and-secrets.md b/.github/audit/ci-and-secrets.md index 20572a80..2a239238 100644 --- a/.github/audit/ci-and-secrets.md +++ b/.github/audit/ci-and-secrets.md @@ -35,6 +35,9 @@ write scopes. ## Qualitative pass You own `.github/` (including `.github/audit/`, which holds this audit's own -prompts), `.config/`, `.claude/`, `scripts/`, and `website/public/` — the Tauri -updater manifest shipped apps fetch lives there, so it is a release artifact -rather than marketing. You also own any code anywhere that touches a secret. +prompts), `.config/`, `.claude/`, `.vscode/`, `scripts/`, and `website/public/` +— the Tauri updater manifest shipped apps fetch lives there, so it is a release +artifact rather than marketing. `.vscode/` is yours for the same reason +`.claude/` is: `tasks.json` and `launch.json` name commands a maintainer's +machine runs on a keystroke. You also own any code anywhere that touches a +secret. diff --git a/SECURITY.md b/SECURITY.md index 50d0a0fe..62d49f99 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -128,7 +128,7 @@ Every dependency Dormouse **puts on a user's machine** is listed at Date: Wed, 26 Aug 2026 19:13:44 +0000 Subject: [PATCH 2/3] docs(security): give supply-chain all of website/ except public/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scope division named website/public/ and website/src/, which left website/scripts/, package.json, vite.config.ts, react-router.config.ts, and tsconfig.json in no domain's qualitative scope — the same partial- coverage reading error the dotfile directories hit, one level down. State the split as a whole minus its exceptions instead of naming two children, and record that rule in the FAIL IF. --- .github/audit/supply-chain.md | 8 +++++++- SECURITY.md | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/audit/supply-chain.md b/.github/audit/supply-chain.md index 4f95d297..76276cfb 100644 --- a/.github/audit/supply-chain.md +++ b/.github/audit/supply-chain.md @@ -25,7 +25,13 @@ enumeration is the shortcut that goes stale. ## Qualitative pass -You own the dependency graph, the lockfile, and `website/src/`. Look at: +You own the dependency graph, the lockfile, and all of `website/` except +`website/public/`, which is `ci-and-secrets`' because the Tauri updater manifest +lives there. So `website/src/`, the build config (`package.json`, +`vite.config.ts`, `react-router.config.ts`, `tsconfig.json`), and +`website/scripts/` — `generate-deps.js` is the generator behind the disclosed +dependency snapshot you check above, so the whole of it is yours, not just the +`productDependencyFilters` array the root-completeness bullet names. Look at: - newly added or upgraded runtime dependencies since the last audit - anything in the lockfile that resolves outside the registry diff --git a/SECURITY.md b/SECURITY.md index 62d49f99..0d73c52b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -266,10 +266,10 @@ gh secret set AUDIT_PAT --env security-audit --repo diffplug/dormouse --body 'gi - FAIL IF `.github/audit/` is missing a prompt file the workflow names, or `scripts/security-audit-local.sh` stops running the audit from those same files. A local runner with its own copy of the prompts is worse than no local runner: it drifts, and the drift is invisible until a nightly disagrees with a local pass. - FAIL IF `.github/audit/` is outside `workflow-audit.yaml`'s diff window. The prompts decide what gets audited and by whom, so a change to them is a change to the security automation — the same reason `.config/tend.yaml` is in that window. - FAIL IF a `## ` section of this document is in no subagent's scope, or is in two. Every section is owned by exactly one domain: a section owned by none is unaudited, and one owned by two produces contradictory verdicts. Each domain file in `.github/audit/` names its sections as exact `## ` headings on their own lines, so this is a real grep over four markdown files rather than a reading of prose embedded in YAML. -- FAIL IF the union of the subagents' qualitative scopes does not cover every top-level path in the repository. The per-domain scopes replaced a single roving "flag any other security hole you find", so anything no domain names is now nobody's job — and the first version of this split silently orphaned `canopy/`, `.claude/` (named as prompt-injection surface two sections above), `docs/`, the root files, and all of `website/` outside `src/data/`, which includes the Tauri updater manifest that shipped apps fetch. Dotfile directories are the ones this keeps re-orphaning, because a phrase like "the root files" reads as covering them without ever naming them — so every one is listed below, and a new one has to be added here rather than left to a catch-all. The division: +- FAIL IF the union of the subagents' qualitative scopes does not cover every top-level path in the repository. The per-domain scopes replaced a single roving "flag any other security hole you find", so anything no domain names is now nobody's job — and the first version of this split silently orphaned `canopy/`, `.claude/` (named as prompt-injection surface two sections above), `docs/`, the root files, and all of `website/` outside `src/data/`, which includes the Tauri updater manifest that shipped apps fetch. Dotfile directories are the ones this keeps re-orphaning, because a phrase like "the root files" reads as covering them without ever naming them — so every one is listed below, and a new one has to be added here rather than left to a catch-all. The same reading error works one level down: where a top-level directory is split between domains, the division has to be stated as a whole minus its exceptions, because naming two of its children makes the parent *look* covered while the rest of it is nobody's job. The division: - `application-security` — `lib/`, `server/`, `server-lib-common/`, `standalone/`, `vscode-ext/`, `dor/`, `dor-lib-common/`, `canopy/`, `deploy/`, `docs/`, `.impeccable/`, and the repository root files. - `ci-and-secrets` — `.github/`, `.config/`, `.claude/`, `.vscode/`, `scripts/`, and `website/public/` (the updater manifest is a release artifact, not marketing). - - `supply-chain` — the dependency graph, the lockfile, and `website/src/`. + - `supply-chain` — the dependency graph, the lockfile, and all of `website/` except `public/`. That is `website/src/`, plus the build config and `website/scripts/` — where `generate-deps.js` produces the disclosed dependency snapshot this domain audits, so the generator belongs with the thing it generates. - FAIL IF the `Redact secrets from agent output` step is removed, stops covering any sink that is later published (`audit-report.md`, the three per-domain fragments, and the transcript), or stops failing closed by deleting those files when the redactor itself throws. It is the only thing between an accidental `printenv` and a world-readable artifact, and until this bullet existed nothing would have tripped on its deletion. - FAIL IF the orchestrator can report `PASS` while a subagent left no report fragment. A domain that dies silently must not pass the audit — a missing fragment is indistinguishable from a domain that found nothing, and only one of those is safe to publish a release on. It must not be published as `FAIL` either, unless some domain actually returned one: the prompt writes no status file when a fragment is missing and no domain failed, which routes an audit that ran out of time to the INCONCLUSIVE issue rather than filing it as a security finding and relabelling an open issue up to `FAIL`. Both outcomes exit non-zero and hold the release gate shut, so the distinction costs nothing and is the whole reason there are three of them. - FAIL IF the audit has been weakened in any other way — e.g. the prompt no longer requires the qualitative pass, a `FAIL IF` can be ignored, the failure-reporting step that opens a `security-audit-failure` issue and exits non-zero has been removed, or the `AUDIT_PAT` pre-check is removed or bypassed. This bullet is a judgement item, not a checklist: the examples are the ones that have come up, not the ones that exist. Two weakenings found by the audit's own first run were not covered by any example here, and both became their own bullets above. From 1f2f69fd93bbf7b4730d80f76e68e064f98e547f Mon Sep 17 00:00:00 2001 From: dormouse-bot <287024035+dormouse-bot@users.noreply.github.com> Date: Wed, 26 Aug 2026 19:32:35 +0000 Subject: [PATCH 3/3] docs(security): qualify website/package.json in the supply-chain build-config list --- .github/audit/supply-chain.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/audit/supply-chain.md b/.github/audit/supply-chain.md index 76276cfb..86629afe 100644 --- a/.github/audit/supply-chain.md +++ b/.github/audit/supply-chain.md @@ -27,11 +27,12 @@ enumeration is the shortcut that goes stale. You own the dependency graph, the lockfile, and all of `website/` except `website/public/`, which is `ci-and-secrets`' because the Tauri updater manifest -lives there. So `website/src/`, the build config (`package.json`, +lives there. So `website/src/`, the build config (`website/package.json`, `vite.config.ts`, `react-router.config.ts`, `tsconfig.json`), and -`website/scripts/` — `generate-deps.js` is the generator behind the disclosed -dependency snapshot you check above, so the whole of it is yours, not just the -`productDependencyFilters` array the root-completeness bullet names. Look at: +`website/scripts/` are all yours — `generate-deps.js` is the generator behind +the disclosed dependency snapshot you check above, so you audit the whole file, +not just the `productDependencyFilters` array the root-completeness bullet +names. Look at: - newly added or upgraded runtime dependencies since the last audit - anything in the lockfile that resolves outside the registry