Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/audit/application-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
9 changes: 6 additions & 3 deletions .github/audit/ci-and-secrets.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
9 changes: 8 additions & 1 deletion .github/audit/supply-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ 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 (`website/package.json`,
`vite.config.ts`, `react-router.config.ts`, `tsconfig.json`), and
`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
Expand Down
10 changes: 5 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ Every dependency Dormouse **puts on a user's machine** is listed at <https://dor
- every cargo dependency (direct is listed separately from transitive)
- the Node.js runtime bundled as a Tauri sidecar in the standalone app

The roots of that graph are the `productDependencyFilters` in `website/scripts/generate-deps.js`. A workspace package is a root if Dormouse writes its files onto a user's disk, whatever the route: `dormouse-standalone` and `dormouse` (the VS Code extension) are installed, `dormouse-sidecar` rides along inside the Tauri bundle as a `bundle.resources` tree with its `node_modules` intact, `dor` is staged onto every terminal's `PATH`, and `server` is built and installed by a selfhoster ([`SELF_HOST.md`](SELF_HOST.md)) — `web-push` most of all, which signs with a private key and makes outbound requests. `dormouse-lib`, `server-lib-common`, and `dor-lib-common` are reached as workspace edges from those. Note the roots are **package names**, not directory names, and the two differ once: `vscode-ext/` declares itself `dormouse`.
The roots of that graph are the `productDependencyFilters` in `website/scripts/generate-deps.js`. A workspace package is a root if Dormouse writes its files onto a user's disk, whatever the route: `dormouse-standalone` and `dormouse` (the VS Code extension) are installed, `dormouse-sidecar` rides along inside the Tauri bundle as a `bundle.resources` tree with its `node_modules` intact, `dor` is staged onto every terminal's `PATH`, and `server` is built and installed by a selfhoster ([`SELF_HOST.md`](SELF_HOST.md)) — `web-push` most of all, which signs with a private key and makes outbound requests. `dormouse-lib` is the sixth, and it is named explicitly rather than left to a workspace edge because `vscode-ext/` imports it straight out of `../lib/src/` instead of declaring it a dependency — the extension's own dependency walk never reaches it, so only `dormouse-standalone`'s edge would, and that is one refactor away from disclosing nothing. `server-lib-common` and `dor-lib-common` are reached as workspace edges from those roots. Note the roots are **package names**, not directory names, and the two differ once: `vscode-ext/` declares itself `dormouse`.

Two workspace packages are deliberately not roots. `canopy` is a Storybook-only rendering lab that no shipped build imports, and `website` runs in a visitor's browser rather than being installed anywhere — the page says as much about its own React and react-router. Excluding `website` is what makes "puts on a user's machine" the operative test rather than "a user runs", and it is a judgement worth re-making if the site ever ships something a visitor installs.

Expand Down Expand Up @@ -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. The division:
- `application-security` — `lib/`, `server/`, `server-lib-common/`, `standalone/`, `vscode-ext/`, `dor/`, `dor-lib-common/`, `canopy/`, `deploy/`, `docs/`, and the repository root files.
- `ci-and-secrets` — `.github/`, `.config/`, `.claude/`, `scripts/`, and `website/public/` (the updater manifest is a release artifact, not marketing).
- `supply-chain` — the dependency graph, the lockfile, and `website/src/`.
- 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 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.