Repository navigation
[security-audit] INCONCLUSIVE on 2026-09-22 #747
Description
Audit reached no usable verdict at 2026-09-22T09:33Z. This is not a security finding: the run ended without deciding. Run · Transcript
- A domain could not determine every check. audit-application.md returned
VERDICT: INCONCLUSIVE; read itsUNVERIFIABLEchecks. Those checks do not count as passing. - The audit wrote no verdict.
audit-status.txtwas absent, empty, or notPASS/FAIL, so the run ended without deciding. Start at the report's own markers:UNVERIFIABLEis a check the agent could not determine,_Incomplete …_heads a domain that was cut off mid-report, and a domain section reading_No report …_is one that never reported — which is what an expired wait deadline looks like.
Security audit
Supply chain
VERDICT: PASS
FAIL IF results
- PASS:
node website/scripts/generate-deps.jsrun against a clean tree afterpnpm install --frozen-lockfileproduces no diff inwebsite/src/data/dependencies-{npm,cargo,runtime}.json(66 npm deps, 13 direct + 478 transitive cargo deps, 1 runtime dep; verified twice, before and after a fresh frozen install). Disclosure -> FAIL IF maybe: drop node-pty for a Rust backend #1. - PASS:
.github/workflows/ci.yml"Dependency disclosure is current" step (lines 39-46) runspnpm install --frozen-lockfile(line 25) thennode website/scripts/generate-deps.js, andexit 1s ongit diff --quiet -- website/src/data/failing. Disclosure -> FAIL IF Leaky-bucket mechanism for soft-TODOs #2. - PASS:
pnpm-workspace.yaml-> "assertWorkspaceCoverage" ran successfully as part of the generator (it throws on any unclassified/missing workspace package) and manual cross-check confirms all 11pnpm-workspace.yamlpackages are accounted for: 6 product roots (dor,dormouse/vscode-ext,dormouse-standalone/standalone,dormouse-lib/lib,dormouse-sidecar/standalone/sidecar,relay), 3 explicit exclusions (canopy,dormouse-website/website,dormouse-hosted/hosted — confirmed none of the three is depended on by any other workspace package viagrepfor their package names as dependency values), and 2 workspace-edge-only packages (dor-lib-common,remote-lib-common) reachable transitively from roots.doranddormouse-libare correctly listed as independent roots even thoughdor's files reach disk viascripts/stage-dor-cli.mjs(docs/specs/dor-cli.md-> "Bundling And PATH") rather than an npm dependency edge — confirmed neitherstandalone/package.jsonnorvscode-ext/package.jsonlistsdoras a dependency. Confirmedstandalone/src-tauri/tauri.conf.json'sbundle.resources: ["../sidecar/**/*"]is the sidecar's route onto disk. Disclosure -> FAIL IF Fix terminal spawning #3 (root-completeness). - PASS: root
package.jsonhasdevEngines.runtime.version="24.18.0", an exactMAJOR.MINOR.PATCH; novolta.nodeorengines.nodefield present. Bundled runtime -> FAIL IF maybe: drop node-pty for a Rust backend #1 and part of Fix terminal spawning #3. - PASS:
standalone/src-tauri/build.rs'sbundle_node_runtime()unconditionally callsread_pinned_node_version+verify_node_versionbefore every bundle (line 42-43), which runs<node> --versionand fails the build on any mismatch (lines 207-224); the one deliberate skip ishost != target(lines 197-205). Checked.github/workflows/release.yml'sbuild-standalonematrix (lines 26-36): all three entries (ubuntu-22.04/x86_64-unknown-linux-gnu, macos-latest/aarch64-apple-darwin, windows-latest/x86_64-pc-windows-msvc) are host-native for their GitHub-hosted runners, so the skip path is never exercised in the release matrix. Bundled runtime -> FAIL IF Leaky-bucket mechanism for soft-TODOs #2. - PASS:
build-standalone(release.yml lines 46-48) andbuild-vscode(lines 191-193) both install Node viaactions/setup-nodewithnode-version-file: package.json; rootpackage.jsoncarries novolta.node/engines.nodealternate pin. Bundled runtime -> FAIL IF Fix terminal spawning #3. - PASS:
pnpm-workspace.yamlline 40 setsminimumReleaseAge: 1440. Cooldown and alerts -> FAIL IF maybe: drop node-pty for a Rust backend #1. - PASS:
.github/renovate.jsonline 3enabledManagersincludes bothnpmandcargo; lines 122-135 (packageRules) setminimumReleaseAgeforpatch(1 day),minor(3 days), andmajor(14 days) update types matching bothnpmandcargomanagers. Cooldown and alerts -> FAIL IF Leaky-bucket mechanism for soft-TODOs #2. - PASS:
.github/renovate.jsonhas avulnerabilityAlertsblock (lines 8-21) that explicitly sets"minimumReleaseAge": "1 day"(line 18) rather than omitting the key. Cooldown and alerts -> FAIL IF Fix terminal spawning #3. - PASS (evidence via
GH_TOKEN=$AUDIT_PAT):gh api repos/diffplug/dormouse --jq .security_and_analysisreportssecret_scanning.status: enabledandsecret_scanning_push_protection.status: enabled;gh api repos/diffplug/dormouse/vulnerability-alerts -ireturnsHTTP/2.0 204 No Content(Dependabot alerts enabled, not the 404 that would indicate disabled). Cooldown and alerts -> FAIL IF split a pane and keep the cwd #4 (all three clauses).
Qualitative findings
- INFO: Two file-tarball, non-registry
overridesinpnpm-workspace.yaml(pgstencil: file:vendor/pgstencil-0.1.0.tgz,@pgstencil/auth: file:vendor/pgstencil-auth-0.1.0.tgz), pinned by sha256 invendor/build.json. Traced withpnpm why: both resolve only intodormouse-hosted(hosted/package.jsondependencies), which is correctly excluded from disclosure perdocs/specs/security-supply-chain.md-> "Disclosure" (dormouse-hosted— "no installed desktop or selfhost artifact imports it") and perdocs/specs/security.md("Only the self-hosted remote-control deployment ships. Hosted account code is implemented with production provisioning pending"). No FAIL today. Worth a forward note:pgstencil@0.1.0's owndependencies(notdevDependencies) pull intestcontainers->dockerode->ssh2/cpu-features/protobufjs(all correctly absent from the npm disclosure since they never reach a product root), which is unusual for a library's runtime dependency tree and worth revisiting when Hosted goes to production andhostedpotentially becomes a disclosed root. - INFO: One GitHub-Releases-tarball (non-registry) resolution in
pnpm-lock.yaml:@diffplug/xterm-addon-webgl-sdf@0.20.0-sdf304.0(tarball: https://github.com/diffplug/xterm.js/releases/download/...), the project's own xterm.js fork perAGENTS.md->canopy. Traced withpnpm why: depended on only bycanopy, whichwebsite/scripts/generate-deps.js'sexcludedWorkspacePackagesanddocs/specs/security-supply-chain.md-> "Disclosure" both correctly exclude ("a Storybook-only rendering lab no shipped build imports"). No FAIL; this is the one first-party non-registry package and it never reaches a user's disk. - INFO:
node-ptyis the only production (shipped) dependency in the graph carryingallowBuilds: trueinpnpm-workspace.yaml(native addon, required for the sidecar/vscode-ext PTY) and it is correctly present in the current disclosure. Traced every otherallowBuilds-listed package (sharp,esbuild,@swc/core,@vscode/vsce-sign,keytar,cpu-features,protobufjs,ssh2,workerd) withpnpm why: all resolve exclusively through devDependencies or through the excludeddormouse-hosted/dormouse-websiteworkspaces (storybook, vsce packaging, wrangler/miniflare, testcontainers), never through a product root'sdependencies/optionalDependencies. No production dependency with an install script is undisclosed. - INFO (spot check, not exhaustive): sampled recent history via
gh pr list/gh pr diff. Two recent feature PRs added genuinely new production dependencies to product-root workspaces — PR dor open: dispatch local files through user Tool associations #669 addedpicomatchtolib/package.json(rootdormouse-lib) and PR Move an authorized Pocket session off the Relay onto a direct WebRTC channel #613 addeddetect-libc+node-datachannel+ five@node-datachannel/*platform optionals tostandalone/sidecar/package.json(rootdormouse-sidecar) — and both PRs included the regeneratedwebsite/src/data/dependencies-npm.jsonin the same PR; all ofpicomatch,detect-libc,node-datachannel, and@node-datachannel/darwin-arm64are present with correct metadata in the current disclosure file. This corroborates the CI gate (Disclosure FAIL IF Leaky-bucket mechanism for soft-TODOs #2) working in practice, not just in principle. Routine Renovate dependency-bump PRs sampled (10+ over the last day of history) either updatedwebsite/src/data/dependencies-npm.jsonin the same PR when the package was disclosure-reachable (e.g.hono,tailwind-merge,yaml) or correctly omitted that update for dev-only/excluded-workspace bumps (wrangler,tsx,@types/node,@tauri-apps/cli, all confined todormouse-hosteddevDependencies or root devDependencies never reaching a product root's runtime graph) — no undisclosed production dependency found in the sample. - Caveat: this repository checkout is a shallow, single-commit clone (
git logshows only the tip commit), so no localgit diff/git logcomparison against "the last audit" was possible; the dependency-history review above relies entirely ongh pr list/gh pr diffagainst GitHub's API rather than local git history.
CI and secrets
VERDICT: PASS
FAIL IF results
- PASS — security.md: private vulnerability reporting is enabled.
gh api repos/diffplug/dormouse/private-vulnerability-reporting→{"enabled":true}. - PASS — security-ci.md "GitHub Actions Policies":
pull_request_targetappears as anon:trigger only in.github/workflows/tend-review.yaml(an exempttend-*.yamlfile); grepped every file under.github/workflows/*.y*mlfor the string, the only other hit is a comment inhosted-preview.ymlline 75, not a trigger. - PASS — security-ci.md "Admin-gating rulesets":
GET repos/diffplug/dormouse/rulesetsreturns exactly two active rulesets.Merge access(id 16757376):conditions.ref_name.include=["~DEFAULT_BRANCH"],rules=[{"type":"update"}]only,bypass_actors=[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"exempt"}]— sole bypass is admin.Tag operations(id 16757382):conditions.ref_name.include=["~ALL"],rules=[{"type":"creation"},{"type":"update"}], same sole admin bypass actor. - PASS — security-ci.md "dormouse-bot permission":
GET repos/diffplug/dormouse/collaborators/dormouse-bot/permission→permission:"write",role_name:"write",permissions.admin:false,permissions.maintain:false. Neithermaintainnoradminpresent. - PASS — security-ci.md environment ref-admission: enumerated
GET .../environments(7 total) and each one'sdeployment-branch-policies.hosted-preview(excepted by the rule) admitsmain+refs/pull/*/merge. Every other environment admits only refs covered by the two admin-only rulesets above:release-attest→v*tag;security-audit→mainbranch +v*tag;tend→mainbranch;hosted-production→mainbranch;hosted-release-tag→mainbranch;vscode-extension-publish→v*tag. - PASS — security-ci.md secret placement inventory. Enumerated
actions/secrets(repo level: exactlyCHROMATIC_PROJECT_TOKEN),actions/organization-secrets(empty), and each of the 7 environments' own secret listings:security-audit→{AUDIT_PAT, CLAUDE_CODE_OAUTH_TOKEN};tend→{CLAUDE_CODE_OAUTH_TOKEN, TEND_BOT_TOKEN};vscode-extension-publish→{OVSX_PAT, VSCE_PAT};release-attest→{} (0 secrets, 0 variables);hosted-preview/hosted-production/hosted-release-tag→{} (0 secrets each — Hosted production credentials are not yet provisioned, consistent with docs/specs/security.md's "production provisioning pending"). NoANTHROPIC_API_KEYanywhere. Matches the spec's normative table exactly. - PASS — security.md
default_workflow_permissions:gh api repos/diffplug/dormouse/actions/permissions/workflow→{"default_workflow_permissions":"read","can_approve_pull_request_reviews":false}. - PASS — security-ci.md non-agent-managed workflow write-permission bound. Read every workflow-level and job-level
permissions:block inci.yml(contents:read only),chromatic.yml(contents:read only),hosted-preview.yml(workflow contents:read; jobchangesadds pull-requests:read; no job grants write),hosted-production.yml(contents:read throughout, no job overrides), andrelease.yml(build-standalone/build-vscodejobs: contents:read + the explicitly allowedid-token:write/attestations:write;security-auditjob: contents:read + the explicitly allowedactions:write;publish-vscode: no job override, inherits contents:read). No non-agent-managed workflow has an effective write scope outside the two named exceptions. - PASS — security-ci.md agent-managed workflow permission bound (contents:write, pull-requests:write, issues:write, id-token:write, actions:read, or any read). Checked
security-audit.yaml(contents:read, actions:read, issues:write, id-token:write),workflow-audit.yaml(contents:read, issues:write, actions:read, pull-requests:read), and everytend-*.yamljob-level block (all combinations of contents:read/write, pull-requests:write, actions:read, issues:write). None exceeds the allowed set. - PASS — security-ci.md tend version floor: every
tend-*.yamlfile's generated header and itsmax-sixty/tend/claude@pin both read0.2.13, above the0.1.19floor. - PASS — security-ci.md unpinned-action bound inside
tend-*.yaml: everyuses:line in the ninetend-*.yamlfiles carries an explicit tag (actions/checkout@v7,astral-sh/setup-uv@v10.1.0,max-sixty/tend/claude@0.2.13) — none unpinned (no bare@main/no ref). - PASS — security-ci.md
pull_request_targetrestricted totend-*.yaml: confirmed above, onlytend-review.yaml(exempt) uses it. - PASS — security-ci.md Renovate cannot update
tend-*.yaml:.config/tend.yaml's... correction,.github/renovate.json'spackageRulesincludes{"matchManagers":["github-actions"],"matchFileNames":[".github/workflows/tend-*.yaml"],"enabled":false}. - PASS — security-ci.md
CHROMATIC_PROJECT_TOKENinsecrets.allowed:.config/tend.yaml→secrets.allowed: [CHROMATIC_PROJECT_TOKEN]. - PASS — security-ci.md
workflow-audit.yamlliveness:state: "active"; latest successful run2026-09-21T14:00:40Z, ~19h before the current audit time (2026-09-22T09:22:50ZUTC) — within 48h. - PASS — security-ci.md
workflow-audit.yamlgap-resistant window: read the workflow source —SINCEis drawn fromgh api .../workflow-audit.yaml/runs?status=successcreated_at(server-set), never from a pusher-controlled field; the fallback (25 hours ago) only applies when there is no prior successful run. - PASS — security-ci.md
.github/audit//.vscode/diff-window coverage:WINDOW=(.github/workflows/ .config/tend.yaml .github/audit/ .vscode/)is the single array; the commit list (git log ... -- "${WINDOW[@]}"),own_changes(), and both classifiers'WINDOW_NON_WORKFLOW=("${WINDOW[@]:1}")refusal all derive from it. - PASS — security-ci.md Hosted Deployments environment protections.
GET /environmentsshowshosted-preview,hosted-production,hosted-release-tagall havecan_admins_bypass:falseand nonemptyrequired_reviewers(nedtwigg, edgartwigg); branch policies restrict each tomain(hosted-previewalso admitsrefs/pull/*/merge, expected for PR previews per the workflow's own fork/ready checks). - PASS — security-ci.md
HOSTED_TAG_TOKENplacement:grep -rn HOSTED_TAG_TOKEN .github/shows exactly one reference,hosted-production.yml'stagjob, which runs in thehosted-release-tagenvironment; no repo/org-level secret listing contains it (all Hosted secrets are unprovisioned — 0 secrets in all three Hosted environments and at repo/org level, consistent with docs/specs/security.md's "production provisioning pending"). - PASS — security-ci.md Hosted preview/production workflow-graph bounds:
hosted-preview.yml'sdeployandcleanupjobs both gate ongithub.event.pull_request.head.repo.full_name == github.repository(fork exclusion);deployhasneeds: verify;cleanupchecks outref: refs/heads/main, never the PR ref;hosted-production.yml'stagjob hasneeds: deploy, anddeploy's last step (Verify live production revision and auth boundary) runs before that job's outputs are consumed. - PASS — security-ci.md VS Code extension release gating:
vscode-extension-publishenvironment has nonempty required reviewers,prevent_self_review:true,can_admins_bypass:false(from the environments API dump above).release.yml'spublish-vscodejob carriesenvironment: {name: vscode-extension-publish};grep -rn "VSCE_PAT|OVSX_PAT" .github/workflows/shows both secrets referenced only inside that job. - PASS — security-ci.md
release.ymldesktop-signing-in-CI bound:grepforsecrets.TAURI_SIGNING_PRIVATE_KEY/secrets.APPLE_SIGN_PASS/secrets.EV_SIGN_PINacross.github/workflows/returns nothing;release.yml's "Generate ephemeral Tauri updater key" step callstauri signer generate --ciand writes the key to a$RUNNER_TEMPfile, never a repo secret. - PASS — security-ci.md
sign-and-deploy.shthree-part bound:check_gh_attestation_support+gh attestation verify(attestations),check_sha256_manifest(SHA-256 manifests), andjsign --storetype PIV(PIV-backed Windows signing) are all present inscripts/sign-and-deploy.sh. - PASS — security-ci.md signing-credential argv bound:
TAURI_SIGNING_PRIVATE_KEYis passed only via an env-var prefix (TAURI_SIGNING_PRIVATE_KEY="$TAURI_SIGNING_PRIVATE_KEY" ... tauri signer sign), never on thetauri signer signargv;EV_SIGN_PINreachesjsignonly via--storepass env:EV_SIGN_PIN(a reference), never a literal value on argv. (APPLE_SIGN_PASSremains argv-only, which is the spec's own accepted/known gap, not a new finding.) - PASS — security-audit.md release-gate wiring:
.github/workflows/security-audit.yamlexists, is not disabled, and is triggered byschedule+workflow_dispatch;release.yml'ssecurity-auditjob doesgh workflow run security-audit.yamlthengh run watch "$run_id" --exit-status;publish-vscodedeclaresneeds: [build-standalone, build-vscode, security-audit]. - PASS — security-audit.md domain-ownership:
node scripts/spec-lint.mjs→spec-lint: OK (35 specs, 69 files checked), which enforces everydocs/specs/security*.mdis claimed by exactly one domain's**Scopeblock and that every named scope file exists. - PASS — security-audit.md dedicated
application-securitysubagent:.github/audit/orchestrator.md§1 spawns exactly three Task-tool subagents (supply-chain,ci-and-secrets,application-security) concurrently in one message;application-security.mdis a separate file, not merged into either mechanical domain's prompt. - PASS — security-audit.md model floor:
.github/workflows/security-audit.yaml'sclaude_argssets--model sonnetas the session default and overridesapplication-securityto"model":"opus"inside--agents;scripts/security-audit-local.sh'srun_domain()setsmodel_args="--model sonnet"and overrides to"--model opus"only forapplication-security. Both agree. - PASS — security-audit.md prompt-file/local-runner consistency:
.github/audit/contains_preamble.md,orchestrator.md,supply-chain.md,ci-and-secrets.md,application-security.md— every filesecurity-audit.yaml's--agentsand the orchestrator prompt name;scripts/security-audit-local.shloops over the same five basenames and errors out if any is missing. - PASS — security-audit.md domain qualitative-scope coverage:
application-security.mdstates it is "the catch-all domain... defined by subtraction, not by a list" over whatsupply-chain.mdandci-and-secrets.mdexplicitly claim, and instructsls -Arecomputation rather than trusting any stale enumeration — this makes full top-level coverage structural rather than a maintained list. - PASS — security-audit.md orchestrator wait/merge/verdict mechanics, verified by reading
.github/audit/orchestrator.mdand runningnode --test scripts/security-audit.test.mjs(41/41 passing) andnode --test scripts/workflow-audit.test.mjs(6/6 passing): non-turn-ending Bashuntilloop with a persisted$RUNNER_TEMP/audit-deadline, self-breaking under the Bash cap (BASH_DEFAULT_TIMEOUT_MS: "600000"insecurity-audit.yaml, above the loop's 540s break); orchestrator prompt forbids ending the turn beforeaudit-report.mdexists; merge/verdict treat the<!-- END OF REPORT -->sentinel, not fragment existence, as "reported." - PASS — security-audit.md reporting-step guards (read
.github/workflows/security-audit.yaml's "Surface result, file or close issue" step and confirmed by the same test suite): the missing-fragment loop and the no-sentinel/verdict-parsing loop both run unconditionally (not gated on$STATUS); issue prose is emitted as one independent note per condition (DISSENTING,MISSING_FRAGMENTS,UNREADABLE_VERDICTS,UNFINISHED,INCONCLUSIVE_DOMAINS,FILE_STATUS=MISSING), never a combination-keyed block; only exactVERDICT: PASSis accepted as passing,VERDICT: FAIL(with any suffix) is recognized as dissent and forcesSTATUS=FAIL,VERDICT: INCONCLUSIVEand any unrecognized first line are both tracked as non-passing; only literalPASS/FAILfile contents are honored forFILE_STATUS, anything else becomesMISSING. - PASS — security-audit.md redaction fail-closed bound: the
Redact secrets from agent outputstep's Node script coversaudit-report.md, all three$AUDIT_FRAGMENTS, and the transcript file in one argv list, and its shell wrapper (|| { rm -f ...; exit 1; }) deletes that same sink set on any script error. - PASS — security-audit.md
AUDIT_PATprovisioning gate: theVerify AUDIT_PAT is provisionedstep runs after checkout/setup-node/install and before the audit step, exits 1 and writesFAILtoaudit-status.txtwhen the secret is empty. - PASS — private vulnerability reporting: see first entry above (security.md scope).
Qualitative findings
No BLOCKER or WARNING findings in the ci-and-secrets domain (.github/, .config/, .claude/, .vscode/, scripts/, website/public/, and code touching secrets).
- INFO:
.vscode/tasks.jsonand.vscode/launch.jsonwere inspected forrunOn: "folderOpen"or similar checkout-time execution (the risk.github/audit/ci-and-secrets.mdcalls out by name) — neither file defines any such trigger;tasks.jsonhas one manualbuild-dormouse-vscodetask with norunOptions. - INFO:
.claude/settings.jsongrants a small, benignBash(...)allowlist (test/build/screenshot commands) and defines nohooksblock, so nothing there executes automatically on session start or checkout. - INFO: eight untracked
frag-*.mdscratch files and onedormouse.ymlsit in the repo root during this run.dormouse.ymlis a tracked, legitimatedor toolconfig unrelated to the audit. Thefrag-*.mdfiles are untracked working-directory artifacts (matching theapplication-securitydomain's per-topic scratch-file naming) from the concurrently runningapplication-securitysubagent sharing this checkout — not a repository or workflow issue, and outside this domain's scope to characterize further. - INFO:
release-attestandtendenvironments havecan_admins_bypass:true(unlike the Hosted and VS Code publish environments, which requirefalse). Neitherdocs/specs/security-ci.mdnordocs/specs/security-audit.mdstates a bypass requirement for these two environments specifically, andrelease-attestholds no secrets whiletendsends itsGITHUB_TOKEN-adjacent secrets through admin-gatedmain-only deployment anyway — so this is not aFAIL IFviolation, only a documentation gap worth naming since the spec is explicit about bypass beingfalseeverywhere it does state a requirement.
Application security
VERDICT: INCONCLUSIVE
FAIL IF results
Scope: docs/specs/security-local.md, docs/specs/security-remote.md,
docs/specs/security-hosted.md, plus the catch-all remainder of the tree.
Every FAIL IF in scope was decomposed to its individual clauses and each
clause given its own verdict against code, with an absolute path and line
number or command output recorded at the time of determination.
377 clause verdicts: 375 PASS, 0 FAIL, 2 UNVERIFIABLE (both in
security-hosted.md, both external-to-repo by design). The per-clause
evidence lines are grouped below by spec section; the bracketed number is how
many clauses that bullet was decomposed into.
security-local.md — Terminal output / Browser panes / dor control socket (31 clauses, all PASS)
- PASS — terminal-output/osc52 [4] —
isKnownUnsupportedIterm2Oscstill consumes OSC 52/50/1337 and everypty:dataparse site runsTerminalProtocolParserfirst —lib/src/lib/terminal-protocol.ts - PASS — terminal-output/bounds [9] —
TITLE_LIMIT/BODY_LIMIT/COMMAND_LINE_LIMIT/sanitizeTextpresent and applied,COMMAND_LINE_LIMITbinds after the\xNNunescape with a 4x pre-bound;MAX_CWD_LENGTH/boundedCwdValueinlib/src/lib/terminal-state.ts; OSC-incomplete cap 16,384 atterminal-protocol.ts:209-212; OSC 99 pending map capped 64 with a 60 s TTL at:83-84,:426-448 - PASS — terminal-output/osc8 [6] —
linkHandler(lib/src/lib/terminal-lifecycle.ts:164) always routes through the confirmation;classifyDisplayMatchdeceptive verdict renders no open action (ExternalLinkModal.tsx), andExternalLinkModalHost.tsx:24-27rejects a deceptive confirmation host-side - PASS — browser/shim-target [6] —
iframeShim/normalizeEmbedderOriginsinlib/src/host/iframe-proxy-rewrite.tstarget only the proxy origin plus the validated innermost origin, relay no nestedlocation, no foreign-origin and no unregistered message, and the proxy uses no unvalidated chain - PASS — browser/vscode-token [3] —
isHostMessageruns before any dispatch inlib/src/lib/platform/vscode-adapter.ts:154-158; token minted per serve and attached only byWebviewChannel.postinvscode-ext/src/webview-messaging.ts - PASS — dor/control-dir [5] —
ensureControlDirrequires real directory, non-symlink, this uid, exactly0700before bind (standalone/sidecar/dor-control-server.js:88-96);resolveControlSocketPathyieldsnullotherwise, and both hosts then never export the control env (standalone/sidecar/main.js:86-124,vscode-ext/src/pty-host.js:18-76) - PASS — dor/proofs [4] — raw token never written to a socket; both sides use SHA-256-then-
timingSafeEqualinproofMatches(dor-control-server.js:19-34,dor/src/control-client.ts:70-78); 24 CSPRNG bytes per host process
security-local.md — Loopback listeners, file viewer, persisted state, Dor Tools (74 clauses, all PASS)
- PASS — loopback/lint-inventory [1] —
node scripts/loopback-lint.mjsgreen; every non-test listener it prints inspected, plus a manual sweep forcreateServer/.listen(/serve(/WebSocket. The two non-test listeners outside the named bullets are guarded:scripts/direct-interop/run.mjs:169,181(isAuthorized+isOwnOrigin, per-run token) andhosted/server/dev.ts:26,37(allowedDevRequeston request and upgrade) - PASS — iframe/origin-rewrite [3] —
Originrewritten to the upstream's own only for own-origin callers, onhandleRequestandhandleUpgrade; a foreignOriginis forwarded untouched, not blocked - PASS — iframe/cookies [4] —
CookieandSet-Cookiestripped on HTTP and on WebSocket handshakes, refused upgrades included - PASS — iframe/host-check [2] —
Hostmust name the grant's own port on both paths - PASS — frame-ancestors [5] — upstream XFO/CSP replaced with exactly
frame-ancestors 'self' <validated chain>, no other source admitted, shim targeted only at the proxy origin and the chain's innermost origin; with no usable chain the upstream headers are preserved and nothing is injected (lib/src/host/iframe-proxy.ts:365-371) - PASS — idle-timer [3] — a foreign
Origindoes not refresh a grant's idle timer; an absentOrigindoes - PASS — stream-relay [5] — grant single-use, TTL-bounded, pinned to one target port,
Origindropped rather than rewritten - PASS — browser-dev/gates [6] — all four gates present (per-run token, loopback
Host,application/jsonon every non-GET, exact-origin ACAO), the first three in the pre-routing gate; Vite server denies cross-origin reads of token-bearing modules and keeps its DNS-rebindingHostcheck - PASS — file-viewer [20] — fresh 256-bit URL capability compared by SHA-256 then
timingSafeEqualincluding malformed lengths, case-insensitive loopbackHost, absent-or-same-listenerOrigin, GET/HEAD only,allowsFileViewerRequestgating every route, no foreign CORS, no caching, no capability in a referrer; grant construction permits regular files only, rejects escaping symlinks, bounds static dependency discovery, retains descriptors, and the viewer's CSP is own-origin plus inline script/style and data images - PASS — persisted/atomic-writes [5] —
write_file_atomically+restrict_to_ownerinstandalone/src-tauri/src/lib.rsrestrict directory and file on every platform arm (0700/0600unix; one-ACE protected DACL on Windows), mode reaching the temp file before any bytes; callers enumerated from the file - PASS — notepad/settings-sync [1] — no
setKeysForSync(call anywhere invscode-ext/src/, soNOTEPAD_ARCHIVE_KEYcannot be opted in - PASS — terminal-context-dirs [5] — context arguments validated as existing absolute directories and passed as one canonical argv element with no shell interpretation (
standalone/sidecar/pty-core.js,vscode-ext/src/message-router.ts) - PASS — dor-tools [14] — repo-local named Tools inert until chrome-granted trust (no trust-grant verb on the control socket, pending approval spawns nothing), inputs stay argv until the renderer quotes them, C0/DEL rejected in argv, substituted argv and local-file targets including after symlink canonicalization, grant key derived in the host, config reads bounded and repo-config symlinks refused
security-remote.md — Trust boundary (70 clauses, all PASS)
- PASS — burrow-final-authority [10] —
#onConnectionTransportconsumes its own challenge and verifies withverifyPresenceProofagainst a binding built from the Burrow's ownburrowId, connection id, challenge and handshake hash, then requires one activeBurrowAclRecordcarrying account + passkey credential + key hash + IK-authenticated static, all before any session — no Relay-supplied claim substitutes for any of them - PASS — local-confirmation-mints [4] —
BurrowAcl.approve(remote-lib-common/src/security/acl.ts:145) has exactly one production caller,BurrowRuntime.#approvePairing(lib/src/remote/burrow/burrow-runtime.ts:1161-1201); comparison constant-time, single-attempt latch at:1136so it happens exactly once per ceremony, matched against the immutablepairingId - PASS — expected-code-never-leaves [3] —
PairingQueueItemis the four-field projection atlib/src/host/remote/service.ts:705-710and the type atservice-protocol.ts:90-97; no invitation private half crosses - PASS — pending-maps-bounded [6] —
MAX_PENDING_PAIRINGSon both the runtime map and the mirrored service queue with oldest-first eviction,MAX_PENDING_CONNECTION_HANDSHAKES,MAX_TOKENS_PER_BURROW,MAX_CLIENT_ID_LENGTHapplied at the frame boundary before any map is touched, and a failed decrypt allocating nothing (burrow-runtime.ts:1020-1024,:1308-1311)
Truncated to fit: the full body is 64243 characters. The untruncated audit-report.md is in this run's audit-transcript artifact (download).
Activity
dormouse-bot commentedon Sep 22, 2026
No security finding here, and the two undetermined checks are undeterminable rather than unlucky: both are conditions on state outside the repository, so no nightly run can ever settle them while Hosted is unprovisioned. #748 moves them out of the
FAIL IFlist so the audit can reach a verdict again.The
application-securityfragment returned 377 clause verdicts — 375 PASS, 0 FAIL, 2UNVERIFIABLE, both indocs/specs/security-hosted.md:FAIL IFwhose actual condition — no marketing scripts, analytics, provider avatars, or remote fonts in the Hosted frontend — is fully determinable and passed.FAIL IFat all and states its own answer ("checked-in placeholders prove none of them").What makes this worth fixing rather than re-running is that the previous night read the same two sentences the other way. In run 35586089654 (2026-09-21) the sub-auditors also returned both as
UNVERIFIABLE, and the domain lead resolved both to PASS on the ground that the audited condition is the in-repo half — so the audit passed. Nothing in the tree changed between that night and this one. Until Hosted is provisioned, the release gatepublish-vscodedepends on closes or opens on which reading a given night produces.#746 does not reach it: the new
hosteddomain inherits the same two clauses and the same "treat a checked-in placeholder as no evidence about an external control" instruction, and an inconclusive domain still blocks a merged pass.#748 is a draft and needs review before it lands; every gating check on its head commit settled green. Leaving this issue open until then seems right — a passing audit closes it automatically.
Stage external obligations under Future, matching #757
github-actions commentedon Sep 23, 2026
Audit reached no usable verdict at 2026-09-23T09:31Z. This is not a security finding: the run ended without deciding. Run · Transcript
VERDICT: INCONCLUSIVE; read itsUNVERIFIABLEchecks. Those checks do not count as passing.audit-status.txtwas absent, empty, or notPASS/FAIL, so the run ended without deciding. Start at the report's own markers:UNVERIFIABLEis a check the agent could not determine,_Incomplete …_heads a domain that was cut off mid-report, and a domain section reading_No report …_is one that never reported — which is what an expired wait deadline looks like.Security audit
Supply chain
VERDICT: PASS
FAIL IF results
FAIL IF
node website/scripts/generate-deps.jschanges the three dependency JSON files afterpnpm install --frozen-lockfile. PASS — ran the generator against the pre-installed workspace (node_modulesalready matched the lockfile);git status --short website/src/data/andgit diff --stat website/src/data/were both empty before and after. Output: "Wrote 66 dependencies...", "Wrote 13 direct and 478 transitive Cargo dependencies...", "Wrote 1 bundled runtime...".FAIL IF
.github/workflows/ci.ymlstops running the generator under the install precondition, or stops failing on a diff. PASS —.github/workflows/ci.yml:22-46:pnpm install --frozen-lockfile(line 25) precedes theDependency disclosure is currentstep (lines 39-46), which runsnode website/scripts/generate-deps.jsthengit diff --quiet -- website/src/data/andexit 1on any diff.FAIL IF the disclosure omits a shipped workspace's graph or excludes a shipped package. PASS — derived independently from
pnpm-workspace.yaml's 11 packages (not the spec's enumeration): six product roots by package name —dor(dor),dormouse(vscode-ext, VS Code extension),dormouse-standalone(standalone),dormouse-lib(lib, named independently since vscode-ext's owndependenciesare onlynode-pty/wsand never reach lib via a workspace edge — confirmed by readingvscode-ext/package.json),dormouse-sidecar(standalone/sidecar),relay(relay); two workspace edges confirmed reachable from those roots via realdependenciesentries —dor-lib-common(workspace:* in bothdor/package.jsonandlib/package.json) andremote-lib-common(workspace:* in bothlib/package.jsonandrelay/package.json); three exclusions matchingexcludedWorkspacePackagesinwebsite/scripts/generate-deps.jsand justified by AGENTS.md's own description of each package's route (or lack of one) onto a user's disk —canopy(Storybook-only lab, not in the production build),dormouse-website(hosted,website, runs in a visitor's browser),dormouse-hosted(hosted, runs on Workers/browser, no installed artifact imports it). 6 roots + 2 edges + 3 exclusions = 11, matchingpnpm-workspace.yamlexactly.assertWorkspaceCoveragealso ran clean as part of check 1 (no "Unclassified workspace packages" / "reachable from a product root" throw), corroborating the independent derivation.FAIL IF root
package.jsonis missingdevEngines.runtime.versionor it is not an exactMAJOR.MINOR.PATCH. PASS —package.json:6-11:devEngines.runtime.versionis"24.18.0", exact three-part version.FAIL IF
standalone/src-tauri/build.rsstops verifying the bundled Node binary's--versionagainst the pin, or skips the check for a matrix entry that isn't host-native. PASS —verify_node_version(build.rs:191-227) runsnode --versionand fails the build (bundle_node_runtime.expect(...)at build.rs:24) on a mismatch; the only skip isif host != target(build.rs:197-205), acargo:warningplus early return. Checked.github/workflows/release.yml'sbuild-standalonematrix (lines 26-36):ubuntu-22.04/x86_64-unknown-linux-gnu,macos-latest/aarch64-apple-darwin(macos-latest is an Apple Silicon runner),windows-latest/x86_64-pc-windows-msvc— all three are host-native (host == target), so the skip path is never exercised by the release matrix.FAIL IF
build-standalonedoesn't install the pinned runtime vianode-version-file: package.json, or rootpackage.jsongainsvolta.node/engines.node. PASS —.github/workflows/release.yml:46-48usesactions/setup-nodewithnode-version-file: package.jsonin thebuild-standalonejob.package.json(full file read) has novoltaorengineskey at all — onlydevEngines.FAIL IF
pnpm-workspace.yamlis missingminimumReleaseAge: 1440. PASS —pnpm-workspace.yaml:44:minimumReleaseAge: 1440.FAIL IF
.github/renovate.jsonis missingnpm/cargofromenabledManagers, or missingminimumReleaseAgepackage rules for those managers. PASS —enabledManagers: ["github-actions", "npm", "cargo"](line 3);packageRulesincludes three rules matchingmatchManagers: ["npm", "cargo"]withmatchUpdateTypespatch/minor/major settingminimumReleaseAgeto "1 day"/"3 days"/"14 days" respectively (lines 121-135).FAIL IF
.github/renovate.jsonhas novulnerabilityAlertsblock, or it doesn't setminimumReleaseAgeexplicitly. PASS —vulnerabilityAlertsblock (lines 8-21) sets"minimumReleaseAge": "1 day"explicitly, with an inline comment explaining why omitting it would silently drop the cooldown.FAIL IF secret scanning or push protection is disabled, or Dependabot alerts are off. PASS —
GH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse --jq .security_and_analysisreturnedsecret_scanning: {"status":"enabled"}andsecret_scanning_push_protection: {"status":"enabled"};GH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/vulnerability-alerts -ireturnedHTTP/2.0 204 No Content(Dependabot alerts on). (dependabot_security_updatesreads "disabled" but that field is unrelated to the alert feed the FAIL IF names.)Qualitative findings
INFO — no new/upgraded runtime dependency of concern since the last audit. Git history in this checkout is a single flattened commit, so I cross-checked against the previous nightly run instead: issue #747 (2026-09-22, INCONCLUSIVE overall but supply-chain sub-verdict
PASS) recorded the same counts this run reproduced independently (66 npm deps, 13 direct + 478 transitive cargo deps, 1 runtime dep).gh pr list --state merged --limit 20shows the only dependency-shaped change since then is #737 "Update cargo to v2.11.6" (mistitled by its Renovate branch name — the diff isstandalone/src-tauri/Cargo.lockonly, bumpingtauri2.11.5→2.11.6 (patch) andtauri-plugin-updater2.11.0→2.12.0 (minor), both first-party Tauri crates, both still resolving fromregistry+https://github.com/rust-lang/crates.io-indexperCargo.lock:3430-3433,3580-3583). Nothing else in the recent merge list (#744 react-router, #743/#740 pnpm, #739 wrangler, #738 tailwind-merge, #735 yaml, #732 hono, #734 tsx) touches a product root's runtime graph in a way the disclosure snapshot doesn't already reflect (snapshot regenerated clean in check 1).INFO — three lockfile entries resolve outside their package registry, all correctly kept off a product root's graph.
grep -nE "resolution:\s*\{.*(tarball|git|commit)" pnpm-lock.yaml(after discarding one false-positive hash substring match onstrip-indent@3.0.0) found: (1)@diffplug/xterm-addon-webgl-sdfresolving to a GitHub Releases tarball on the diffplug/xterm.js fork — declared only bycanopy/package.json, andcanopyis an explicit, justified exclusion (Storybook-only lab, matchesdocs/specs/webgl-text.mdand AGENTS.md). (2)/(3)pgstenciland@pgstencil/auth, bothfile:vendor/*.tgzvendored tarballs pinned viapnpm-workspace.yaml'soverrides— declared only byhosted/package.json, anddormouse-hostedis an explicit, justified exclusion (runs on Workers/in-browser, no installed artifact imports it). Confirmed viagrep -rlthat no product-root package.json declares any of the three. Provenance of the vendored pgstencil tarballs issecurity-hosted.md's concern (thehosteddomain), not this one; noting it here only because it's the kind of thing this bullet asks me to surface.INFO — pgstencil pulls a
testcontainers→dockerode→ssh2/cpu-features/protobufjschain intodormouse-hosted's realdependencies(notdevDependencies).pnpm why cpu-features/ssh2/protobufjs -rall bottom out atpgstencil@0.1.0(itself adormouse-hosteddependenciesentry, not dev) pulling intestcontainers— normally a test-only tool — as a production edge. Sincedormouse-hostedis a correctly-excluded workspace this does not affect the disclosure (noFAIL IFviolated), but it does meanpnpm installin that workspace resolves Docker/SSH tooling as "production." Flagging for thehosteddomain's awareness rather than rating it here — it's their vendored dependency's own classification, and support for it (or not) issecurity-hosted.md's call.PASS — every install-time script in the resolved dependency graph is accounted for in
pnpm-workspace.yaml'sallowBuildsallowlist, and only one that is actually allowed reaches a shipped product. Walked everypackage.jsonundernode_modules/.pnpm/*/node_modules/**for apreinstall/install/postinstallscript: exactly 8 packages have one —@vscode/vsce-sign,cpu-features,esbuild(both versions),keytar,node-pty,protobufjs,ssh2,workerd— and every one of those 8 names is present inallowBuilds. Cross-referencing each against the workspace package.jsons withpnpm why <pkg> -r:@vscode/vsce-signandkeytarare transitivedevDependenciesofvscode-extvia@vscode/vsce/ovsx(packaging tooling, both blocked —false— inallowBuildsso their scripts don't run);cpu-features/ssh2/protobufjsare thepgstencil→testcontainerschain discussed above (all blocked —false);esbuildis adevDependenciesentry indor,standalone,vscode-ext,hosted(bundler tooling, never a shipped dependency, allowed —true);workerdis a transitivedevDependenciesentry viaminiflare/wranglerindormouse-hosted/dormouse-website(Cloudflare local-dev runtime, allowed —true, never ships).node-pty(allowed —true) is the only one of the 8 that is a realdependenciesentry of a product root (vscode-extandstandalone/sidecar), and it is already correctly disclosed (dependencies-npm.jsonentrynode-pty 1.2.0-beta.15, MIT, Microsoft Corporation).pnpm install --frozen-lockfileproduced no "ignored build scripts" warning beyondvscode-ext's own (non-third-party)postinstall(chmod +xon thenode-ptyprebuild).sharpand@swc/coreare named inallowBuildsbut currently resolve to versions with no install script of their own in this lockfile (sharpships prebuilt binaries viaoptionalDependencies, matching the node-datachannel pattern the spec already documents;@swc/coreis not presently resolved at all) — a defensive/future-proofing allowlist entry, not a gap.PASS —
node-datachannel's optional-sibling disclosure mechanism is working as specified.dependencies-npm.jsonlists all five prebuilt platform packages (darwin-arm64,darwin-x64,linux-arm64-gnu,linux-x64-gnu,win32-arm64-msvc,win32-x64-msvc) plus the corenode-datachannelpackage, all at the identical pinned version0.33.4— matchingoptionalSiblingsAtSameVersion's lockstep guarantee.PASS —
agent-browseris not declared as a dependency anywhere in the workspace.grep -rn '"agent-browser"' --include=package.json .(excludingnode_modules) returned nothing, consistent with the spec's claim that it is "a dependency of nothing here" and resolved offPATHby the user's own global install.INFO —
website/scripts/and the website build config carry no build-time network fetch or shell-out beyond the already-accounted-forcargo metadatacall ingenerate-deps.js. Greppedwebsite/scripts/*.js,website/vite.config.ts,website/react-router.config.tsforfetch(,exec*(,spawn(, and URL literals: the only executed subprocess isexecFileSync("cargo", ...)ingenerate-deps.js(already covered above); every other match is a static string literal (GitHub URLs used as data, not fetched).CI and secrets
VERDICT: INCONCLUSIVE
FAIL IF results
PASS:
pull_request_targetappears only in.github/workflows/tend-review.yaml(agent-managed, exempt) and as a comment mention inhosted-preview.yml:75. No violation in any other workflow. (security-ci.md "GitHub Actions Policies" FAIL IF maybe: drop node-pty for a Rust backend #1)PASS: Every action reference in non-
tend-*.yamlworkflows (chromatic.yml,ci.yml,hosted-preview.yml,hosted-production.yml,release.yml,security-audit.yaml,workflow-audit.yaml) is pinned by full 40-char commit SHA — grep foruses:\slines lacking a@[0-9a-f]{40}suffix returned none.PASS: Non-agent-managed workflows' effective write permissions are within the allowed exceptions.
release.ymlbuild-standalone/build-vscode:contents: read, id-token: write, attestations: write(environmentrelease-attest).release.ymlsecurity-audit job:contents: read, actions: write(explicit exception).publish-vscodejob has no job-level override, inherits workflow-levelcontents: read.chromatic.yml,ci.yml:contents: readonly.hosted-preview.ymlchangesjob:contents: read, pull-requests: read(read-only, not a write grant).hosted-production.yml,hosted-preview.ymlworkflow-level:contents: read; no other job overrides seen. (security-ci.md "GitHub Actions Policies" FAIL IF Leaky-bucket mechanism for soft-TODOs #2)PASS:
dormouse-botcollaborator permission ispermission: write,role_name: write,admin: false,maintain: falseviaGET /repos/diffplug/dormouse/collaborators/dormouse-bot/permission. (security-ci.md FAIL IF "dormouse-bot holds maintain or admin")PASS:
Merge accessruleset (id 16757376): targetbranch,conditions.ref_name.include: ["~DEFAULT_BRANCH"],rules: [{"type":"update"}]only,bypass_actors: [{actor_type: RepositoryRole, actor_id: 5, bypass_mode: exempt}]— sole bypass is admin role, blocks nothing beyond update.PASS:
Tag operationsruleset (id 16757382): targettag,conditions.ref_name.include: ["~ALL"],rules: [creation, update],bypass_actorssole entry RepositoryRole 5 (admin). Both rulesets match security-ci.md's admin-gating requirement exactly. (security-ci.md FAIL IF "either admin-gating ruleset is missing or weakened")PASS: Repo-level secrets (
GET /repos/.../actions/secrets): exactly one,CHROMATIC_PROJECT_TOKEN. Org-level secrets visible to repo (GET /repos/.../actions/organization-secrets): zero. Matches security-ci.md's required placement for both lines.PASS: Environment secret listings match the required placement exactly:
security-audit:AUDIT_PAT,CLAUDE_CODE_OAUTH_TOKEN(both, none at repo level)tend:CLAUDE_CODE_OAUTH_TOKEN,TEND_BOT_TOKENvscode-extension-publish:OVSX_PAT,VSCE_PATrelease-attest: 0 secrets, 0 variables (empty environment, confirmed viaGET /environments/release-attest/secretsand/variables)ANTHROPIC_API_KEYat repo or org level (confirmed above)(security-ci.md FAIL IF "secret inventory departs from this placement")
PASS:
VSCE_PATandOVSX_PATare referenced only in.github/workflows/release.ymllines 347 and 359, both inside thepublish-vscodejob (job starts line 310, bound toenvironment: vscode-extension-publish), and nowhere else under.github/workflows/**. (security-ci.md "VS Code Extension Releases" FAIL IF Leaky-bucket mechanism for soft-TODOs #2)PASS:
vscode-extension-publishenvironment via API:required_reviewersprotection rule present withprevent_self_review: true, reviewersnedtwigg+edgartwigg(nonempty),can_admins_bypass: false. (security-ci.md "VS Code Extension Releases" FAIL IF maybe: drop node-pty for a Rust backend #1)PASS:
release.ymlbuild-standalone/build-vscode jobs generate an ephemeral Tauri updater key at runtime (tauri signer generateinto$RUNNER_TEMP/tauri-ci-updater.key, line 70-81) and setTAURI_SIGNING_PRIVATE_KEYto that ephemeral path.grep -n "secrets\." release.ymlshows onlyGITHUB_TOKEN,VSCE_PAT,OVSX_PAT— no production desktop signing secret (TAURI_SIGNING_PRIVATE_KEY,EV_SIGN_PIN,APPLE_SIGN_PASS) is referenced fromsecrets.*anywhere in the workflow. (security-ci.md "VS Code Extension Releases" FAIL IF Fix terminal spawning #3)PASS:
scripts/sign-and-deploy.shverifies GitHub artifact attestations (gh attestation verify, lines 440-446, gated bycheck_gh_attestation_support), verifies SHA-256 manifests (check_sha256_manifest/sha256sum -c, lines 402-411), and uses PIV-backed Windows signing (jsign --storetype PIV, lines 739-762). (security-ci.md "Desktop Releases" FAIL IF maybe: drop node-pty for a Rust backend #1)PASS:
TAURI_SIGNING_PRIVATE_KEYis passed only via environment-variable prefix (line 824:TAURI_SIGNING_PRIVATE_KEY="$TAURI_SIGNING_PRIVATE_KEY" pnpm --dir ... exec tauri signer sign "$bundle"— the key itself never appears as a CLI argument).EV_SIGN_PINis passed to jsign as--storepass env:EV_SIGN_PIN(an env-var reference, not the literal value) at lines 747-749 and 760-762.APPLE_SIGN_PASSis passed via--password "$APPLE_SIGN_PASS"(argv) at line 677 — this matches the spec's own documented weakest-of-three/known-gap characterization, not a new finding. (security-ci.md "Desktop Releases" FAIL IF Leaky-bucket mechanism for soft-TODOs #2)PASS:
dormouse-botcollaborator permission (re-confirmed):permission: write,role_name: write;admin: false,maintain: false. (security-ci.md "Automated Maintainer" FAIL IF)PASS:
.github/workflows/workflow-audit.yaml'sSINCEis derived exclusively fromgh api .../actions/workflows/workflow-audit.yaml/runs?status=success&per_page=1 --jq '.workflow_runs[0].created_at'(a GitHub server-set timestamp of the previous successful run), falling back todate -u -d '25 hours ago'only when no prior successful run exists. Nothing pusher-controlled (e.g. committer date) feeds the lower bound. (security-ci.md FAIL IF "workflow-audit.yaml starts deriving its lower bound from anything the pusher controls")PASS:
workflow-audit.yaml'sWINDOW=(.github/workflows/ .config/tend.yaml .github/audit/ .vscode/)is the single source array;WINDOW_NON_WORKFLOW=("${WINDOW[@]:1}")is derived from it, and all four consumers (COMMITSgit log,own_changes(),is_renovate_pin_bump's refusal,is_tend_regen's refusal) referenceWINDOW/WINDOW_NON_WORKFLOWrather than a separately hand-maintained list. (security-audit.md FAIL IF ".github/audit/ or .vscode/ is outside any consumer of workflow-audit.yaml's diff window")PASS:
workflow-audit.yamllast successful runcreated_at: 2026-09-22T12:33:14Z; current time2026-09-23T09:22:41Z— approximately 20h49m elapsed, under 48 hours. Workflowstate: active. (security-ci.md FAIL IF "workflow-audit.yaml is missing, disabled, or has not produced a successful run in the last 48 hours")PASS:
.config/tend.yamlsecrets.allowedcontains exactlyCHROMATIC_PROJECT_TOKEN. (security-ci.md FAIL IF "CHROMATIC_PROJECT_TOKEN is missing from secrets.allowed in .config/tend.yaml")PASS:
.github/renovate.jsonpackageRulescontains{"matchManagers":["github-actions"],"matchFileNames":[".github/workflows/tend-*.yaml"],"enabled":false}(line 142-147), disabling Renovate's github-actions manager fortend-*.yaml. (security-ci.md FAIL IF "Renovate's github-actions manager can update .github/workflows/tend-*.yaml")PASS: Every
uses:line in all 9tend-*.yamlworkflows carries an explicit version tag (actions/checkout@v7,astral-sh/setup-uv@v10.2.0,max-sixty/tend/claude@0.2.15) — none unpinned (@main/no version).max-sixty/tend/claudeis pinned at0.2.15, above the0.1.19floor. (security-ci.md FAIL IF "any tend-.yaml pins max-sixty/tend below 0.1.19" and "any tend-.yaml workflow uses an unpinned action reference")PASS: Every agent-managed workflow's effective
GITHUB_TOKENpermissions (workflow- and job-level, grepped across alltend-*.yaml,workflow-audit.yaml,security-audit.yaml) are within{contents: write, pull-requests: write, issues: write, id-token: write, actions: read, contents: read}— nopackages,deployments,checks,statuses,discussions, orsecurity-eventswrite grants found anywhere. (security-ci.md FAIL IF "any job in an agent-managed workflow has effective GITHUB_TOKEN permissions beyond...")PASS:
GET /repos/diffplug/dormouse/actions/permissions/workflowreturns{"default_workflow_permissions":"read","can_approve_pull_request_reviews":false}. (security-ci.md FAIL IF ondefault_workflow_permissions)PASS:
GET /repos/diffplug/dormouse/private-vulnerability-reportingreturns{"enabled":true}. (security.md FAIL IF on private vulnerability reporting)PASS:
GET /repos/diffplug/dormouse/environments(7 environments) cross-checked againstGET .../deployment-branch-policiesfor each:hosted-preview→main,refs/pull/*/merge;hosted-production→main;hosted-release-tag→main;release-attest→v*(tag);security-audit→main,v*(tag);tend→main;vscode-extension-publish→v*(tag). Every one of these refs is admin-gated by theMerge access(targets~DEFAULT_BRANCH) orTag operations(targets~ALLtags) rulesets, both confirmed sole-bypass-actor admin (RepositoryRoleid 5).hosted-preview'srefs/pull/*/mergeentry is explicitly excepted by the spec. (security-ci.md FAIL IF "any GitHub environment except hosted-preview admits a ref that is not admin-gated")PASS:
vscode-extension-publish,hosted-preview,hosted-production,hosted-release-tagenvironments all havecan_admins_bypass: false;hosted-preview/hosted-production/hosted-release-tageach have arequired_reviewersprotection rule listingnedtwiggandedgartwigg(nonempty), matching "Ned or Edgar's review... administrator bypass disabled" (self-review explicitly allowed for Hosted, confirmedprevent_self_review: falsethere, consistent with spec text). (security-ci.md "Hosted Deployments" FAIL IF maybe: drop node-pty for a Rust backend #1)PASS:
release.ymlbuild-standaloneandbuild-vscodejobs both declareenvironment: {name: release-attest}(lines 19 and 182), matching the OIDC-token-bounding rationale in security-ci.md;release-attestenvironment has 0 secrets and 0 variables via API (confirmed above), keepingid-token: write/attestations: writethe only credential those jobs can reach. Note:release-attest's owncan_admins_bypassistrue(unlike the four environments above) — this is not named as a requirement anywhere in security-ci.md for this environment specifically (only vscode-extension-publish and the three Hosted environments have an explicit can_admins_bypass:false requirement), so not recorded as a FAIL IF violation; flagged separately below as a qualitative observation.INFO:
hosted-productionandhosted-release-tagenvironments currently have zero secrets configured via the GitHub API, even thoughhosted-production.yml'sdeployjob referencessecrets.DATABASE_URL,secrets.CLOUDFLARE_API_TOKEN,secrets.BACKUP_AGE_IDENTITY, and thetagjob referencessecrets.HOSTED_TAG_TOKEN. This is consistent withdocs/specs/security.md's stated status ("Hosted account code is implemented with production provisioning pending") — not a new finding — but it means theFAIL IF HOSTED_TAG_TOKEN appears outside hosted-release-tagand the "production credentials appear in hosted-preview" checks are currently vacuously true (the secrets don't exist anywhere yet, so they cannot appear anywhere) rather than affirmatively verified against real credential placement. Re-check this once Hosted production is actually provisioned.PASS (as far as checkable pre-provisioning):
HOSTED_TAG_TOKENdoes not appear in any GitHub secret listing at repo, org, or any environment scope today; it is referenced in workflow code only inside thetagjob ofhosted-production.yml, bound toenvironment: hosted-release-tag, and nowhere else under.github/workflows/**.PASS:
scripts/spec-lint.mjs(check 16) run locally:spec-lint: OK (35 specs, 69 files checked)— confirms everydocs/specs/security*.mdnon-rationale spec (security.md,security-ci.md,security-audit.md,security-hosted.md,security-local.md,security-remote.md,security-supply-chain.md) is claimed by exactly one.github/audit/*.mddomain scope, matching the Domains table in security-audit.md exactly. (security-audit.md FAIL IF "a docs/specs/security*.md spec is in no domain's scope, or in two, or a scope names a file that does not exist")PASS:
.github/workflows/security-audit.yaml'sclaude_argssets--model sonnetas the session floor and its inline--agentsJSON sets"model":"opus"for bothapplication-securityandhosted(not forsupply-chain/ci-and-secrets).scripts/security-audit-local.sh'srun_domain()setsmodel_args="--model sonnet"by default and overrides to--model opusonly forapplication-security|hosted. Both CI and local agree. (security-audit.md FAIL IF on model split)PASS:
.github/audit/contains all four domain files named by both the workflow's--agentsJSON andsecurity-audit-local.sh'sfor f in _preamble orchestrator supply-chain ci-and-secrets application-security hostedexistence loop:_preamble.md,orchestrator.md,supply-chain.md,ci-and-secrets.md,application-security.md,hosted.md— all present and read successfully. (security-audit.md FAIL IF on missing prompt file)PASS: Union of the four domains' qualitative "Qualitative pass" scope statements covers every top-level repo path (
ls -Aat root:.claude→ci-and-secrets,.config→ci-and-secrets,.github→ci-and-secrets,.vscode→ci-and-secrets,scripts→ci-and-secrets,website/public→ci-and-secrets explicit carve-out; rest ofwebsite/→supply-chain (explicit "all of website/ except website/public/"); rootpackage.json/pnpm-lock.yaml/pnpm-workspace.yaml→supply-chain ("the dependency graph, the lockfile");hosted→hosted,vendor→hosted;canopy,deploy,docs,dor,dor-lib-common,lib,relay,remote-lib-common,standalone,vscode-ext,.impeccable, and remaining root files (AGENTS.md,CLAUDE.md, etc.)→application-security catch-all by subtraction). No top-level path found with no owner. (security-audit.md FAIL IF on qualitative scope coverage)PASS:
.github/workflows/security-audit.yaml— all three release-gate pieces present:gh workflow run "$workflow" ...dispatch (line 282),gh run watch "$run_id" ... --exit-status(line 308), andpublish-vscode'sneeds: [..., security-audit]edge (lines 312-315). (security-audit.md "Schedule and gate" FAIL IF)PASS:
timeout-minutes: 40on theauditjob insecurity-audit.yaml, above the orchestrator's 32-minute wait deadline (.github/audit/orchestrator.mdDEADLINE_FILEsetsnow + 1920seconds = 32 min).BASH_DEFAULT_TIMEOUT_MS: "600000"(600s) is set above the wait loop's 540-second self-break in both_preamble.mdandorchestrator.md.PASS:
claude_argsincludes--allowed-tools "Read,Write,Edit,Bash,Grep,Glob,Task,Agent"and--disallowed-tools "Workflow"— Task/Agent allowed, Workflow denied, matching security-audit.md's Orchestration bullet.PASS:
environment: {name: security-audit}on theauditjob; confirmed above this environment's deployment-branch-policy admits onlymainandv*(both admin-gated).PASS: "Verify AUDIT_PAT is provisioned" step (lines 69-77) runs after checkout/setup-node/pnpm-install and before "Audit against the security specs"; it writes
audit-report.md+audit-status.txt+FAILand exits 1 ifAUDIT_PATis empty. (security-audit.md "Environment and AUDIT_PAT" FAIL IF)PASS: "Redact secrets from agent output" step is
if: always(), covers$RUNNER_TEMP/claude-execution-output.json,audit-report.md, and$AUDIT_FRAGMENTS(all four fragment filenames, sourced from the single job-levelAUDIT_FRAGMENTSenv var also used by the artifact-upload and reporting steps), and its trailing|| { rm -f ...; exit 1; }deletes every one of those sinks and fails the job if the Node redactor throws. (security-audit.md FAIL IF on the redact step)PASS: "Archive audit transcript" step is
if: always(), uploads unconditionally (retention-days: 14), matching "Every run uploads the audit-transcript artifact."PASS: "Surface result, file or close issue" step: (a) writes one
NOTESline per condition that holds via independentifblocks, never an if/elif chain over combinations; (b) both fragment-guard loops (MISSING_FRAGMENTScheck, and the sentinel/verdict-line check producingUNFINISHED/DISSENTING/INCONCLUSIVE_DOMAINS/UNREADABLE_VERDICTS) run unconditionally, never gated on$STATUS; (c) accepts only the exact stringVERDICT: PASSas passing (case statement), recognizes anyVERDICT: FAIL*prefix as dissent, treatsVERDICT: INCONCLUSIVEand any unrecognized first line as non-passing, and requires the literal last-non-blank-line<!-- END OF REPORT -->sentinel before treating a fragment as finished; (d) parsesaudit-status.txtaccepting only literalPASS/FAIL, defaulting anything else (including missing/empty) toMISSING; (e) escalation isFAIL > MISSING > PASS, in the single block at lines 420-425. All 52 assertions innode --test scripts/security-audit.test.mjs(reporting, redaction, local-runner, orchestrator-wait, merge, workflow-audit regeneration-boundary suites) pass. (security-audit.md "Outcomes and reporting" FAIL IFs, all clauses)PASS:
node --test scripts/sign-and-deploy.test.mjs— all 33 tests pass, including "Windows signatures pass the PIV PIN through the environment only".PASS:
node --test scripts/workflow-audit.test.mjs(bundled in the security-audit.test.mjs run above) — "accepts exact regeneration...", "never materializes an unrelated symlink...", "rejects symlink workflow inputs...", "does not let gitignore conceal unexpected generated workflows...", "reports a config change even when its workflows reproduce" all pass, backingis_tend_regen's materialization boundary. (security-ci.md FAIL IF "tend regeneration materializes anything except regular .config/tend.yaml and workflow YAML blobs")PASS:
.vscode/tasks.jsoncontains one task (build-dormouse-vscode,pnpm build:vscode) with norunOnkey anywhere in.vscode/— confirms security-ci.md's qualitative-pass claim "There is no such task today" is still accurate..claude/settings.jsonhas nohookskey; no root.mcp.jsonexists. No checkout-time-executing persistence mechanism found in either directory.Truncated to fit: the full body is 226302 characters. The untruncated
audit-report.mdis in this run'saudit-transcriptartifact (download).dormouse-bot commentedon Sep 23, 2026
This run went inconclusive on one clause, and the clause is already gone on #748's head — but the posted report could not say so, because the line that decided it was truncated away.
What made it INCONCLUSIVE.
audit-ci-secrets.mdreturned oneUNVERIFIABLE, againstdocs/specs/security-ci.md's FAIL IF on Hosted credential placement. GitHub-side placement passed in full; what the domain could not determine is the clause's second half — "inspect… Cloudflare/Neon token scope; names alone do not isolate resources" — which needs the liveCLOUDFLARE_API_TOKEN/NEON_API_KEYvalues the audit correctly never reads. It also recorded thatGET /actions/variablesand/actions/organization-variablesreturned403 Resource not accessible by personal access token, consistent withAUDIT_PAT's documentedAdministration + Secrets + Environmentsscope rather than drift from it, so that avenue was closed too.This is the third instance of the class #748 is about, and
15c11da2on that PR already deletes the token-scope sub-clause, leaving- **FAIL IF** Hosted credentials appear at repository/org scope, or production credentials appear in \hosted-preview`; inspect GitHub secret placement.` Tonight's audit should get past it once #748 lands. Nothing here needs a separate fix.What does need one. The comment above states
VERDICT: INCONCLUSIVEforaudit-ci-secrets.mdand tells the reader to read itsUNVERIFIABLEchecks — and the body contains none. The composed body was 226,302 characters;scripts/clamp-issue-body.mjskeeps the head and cuts at 32,000, so what posted was## Supply chain,## CI and secretscut mid-list, and nothing else:## Application security,## Hosted accountsand## Summaryare absent along with their verdicts. Everything quoted above comes from theaudit-transcriptartifact, not from what was posted.#764 fixes that path: each fragment's verdict line and its
UNVERIFIABLE/FAIL:/BLOCKER/WARNINGlines are lifted into the head ahead of the report, where truncation cannot reach them. Replayed against this run's four fragments it emits 8 lines and 1,571 characters — every verdict, the clause above, and threeWARNINGentries fromaudit-application.mdthat also never posted.Posted comment vs. the artifact, for this run
audit-report.mdin the artifact## Supply chain,## CI and secrets(cut mid-list)## Application security,## Hosted accounts,## SummaryUNVERIFIABLElinesaudit-ci-secrets.md, line 60)The three
WARNINGentries that did not post are all inaudit-application.md, which returned PASS: two spec-drift notes on the state-root writer enumeration and the default<tmpdir>/dormouse.loggap, plus a### WARNINGsection in its consolidated findings.Keep provisioning obligations out of the FAIL IF list so the audit ca…