You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 its UNVERIFIABLE checks. Those checks do not count as passing.
The audit wrote no verdict.audit-status.txt was absent, empty, or not PASS/FAIL, so the run ended without deciding. Start at the report's own markers: UNVERIFIABLE is 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.js run against a clean tree after pnpm install --frozen-lockfile produces no diff in website/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) runs pnpm install --frozen-lockfile (line 25) then node website/scripts/generate-deps.js, and exit 1s on git 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 11 pnpm-workspace.yaml packages 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 via grep for their package names as dependency values), and 2 workspace-edge-only packages (dor-lib-common, remote-lib-common) reachable transitively from roots. dor and dormouse-lib are correctly listed as independent roots even though dor's files reach disk via scripts/stage-dor-cli.mjs (docs/specs/dor-cli.md -> "Bundling And PATH") rather than an npm dependency edge — confirmed neither standalone/package.json nor vscode-ext/package.json lists dor as a dependency. Confirmed standalone/src-tauri/tauri.conf.json's bundle.resources: ["../sidecar/**/*"] is the sidecar's route onto disk. Disclosure -> FAIL IF Fix terminal spawning #3 (root-completeness).
PASS: standalone/src-tauri/build.rs's bundle_node_runtime() unconditionally calls read_pinned_node_version + verify_node_version before every bundle (line 42-43), which runs <node> --version and fails the build on any mismatch (lines 207-224); the one deliberate skip is host != target (lines 197-205). Checked .github/workflows/release.yml's build-standalone matrix (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) and build-vscode (lines 191-193) both install Node via actions/setup-node with node-version-file: package.json; root package.json carries no volta.node/engines.node alternate pin. Bundled runtime -> FAIL IF Fix terminal spawning #3.
PASS: .github/renovate.json line 3 enabledManagers includes both npm and cargo; lines 122-135 (packageRules) set minimumReleaseAge for patch (1 day), minor (3 days), and major (14 days) update types matching both npm and cargo managers. Cooldown and alerts -> FAIL IF Leaky-bucket mechanism for soft-TODOs #2.
PASS: .github/renovate.json has a vulnerabilityAlerts block (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_analysis reports secret_scanning.status: enabled and secret_scanning_push_protection.status: enabled; gh api repos/diffplug/dormouse/vulnerability-alerts -i returns HTTP/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 overrides in pnpm-workspace.yaml (pgstencil: file:vendor/pgstencil-0.1.0.tgz, @pgstencil/auth: file:vendor/pgstencil-auth-0.1.0.tgz), pinned by sha256 in vendor/build.json. Traced with pnpm why: both resolve only into dormouse-hosted (hosted/package.jsondependencies), which is correctly excluded from disclosure per docs/specs/security-supply-chain.md -> "Disclosure" (dormouse-hosted — "no installed desktop or selfhost artifact imports it") and per docs/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 own dependencies (not devDependencies) pull in testcontainers -> 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 and hosted potentially 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 per AGENTS.md -> canopy. Traced with pnpm why: depended on only by canopy, which website/scripts/generate-deps.js's excludedWorkspacePackages and docs/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-pty is the only production (shipped) dependency in the graph carrying allowBuilds: true in pnpm-workspace.yaml (native addon, required for the sidecar/vscode-ext PTY) and it is correctly present in the current disclosure. Traced every other allowBuilds-listed package (sharp, esbuild, @swc/core, @vscode/vsce-sign, keytar, cpu-features, protobufjs, ssh2, workerd) with pnpm why: all resolve exclusively through devDependencies or through the excluded dormouse-hosted/dormouse-website workspaces (storybook, vsce packaging, wrangler/miniflare, testcontainers), never through a product root's dependencies/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 added picomatch to lib/package.json (root dormouse-lib) and PR Move an authorized Pocket session off the Relay onto a direct WebRTC channel #613 added detect-libc + node-datachannel + five @node-datachannel/* platform optionals to standalone/sidecar/package.json (root dormouse-sidecar) — and both PRs included the regenerated website/src/data/dependencies-npm.json in the same PR; all of picomatch, detect-libc, node-datachannel, and @node-datachannel/darwin-arm64 are 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 updated website/src/data/dependencies-npm.json in 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 to dormouse-hosted devDependencies 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 log shows only the tip commit), so no local git diff/git log comparison against "the last audit" was possible; the dependency-history review above relies entirely on gh pr list/gh pr diff against 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_target appears as an on: trigger only in .github/workflows/tend-review.yaml (an exempt tend-*.yaml file); grepped every file under .github/workflows/*.y*ml for the string, the only other hit is a comment in hosted-preview.yml line 75, not a trigger.
PASS — security-ci.md "Admin-gating rulesets": GET repos/diffplug/dormouse/rulesets returns 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. Neither maintain nor admin present.
PASS — security-ci.md environment ref-admission: enumerated GET .../environments (7 total) and each one's deployment-branch-policies. hosted-preview (excepted by the rule) admits main + refs/pull/*/merge. Every other environment admits only refs covered by the two admin-only rulesets above: release-attest→v* tag; security-audit→main branch + v* tag; tend→main branch; hosted-production→main branch; hosted-release-tag→main branch; vscode-extension-publish→v* tag.
PASS — security-ci.md secret placement inventory. Enumerated actions/secrets (repo level: exactly CHROMATIC_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"). No ANTHROPIC_API_KEY anywhere. 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 in ci.yml (contents:read only), chromatic.yml (contents:read only), hosted-preview.yml (workflow contents:read; job changes adds pull-requests:read; no job grants write), hosted-production.yml (contents:read throughout, no job overrides), and release.yml (build-standalone/build-vscode jobs: contents:read + the explicitly allowed id-token:write/attestations:write; security-audit job: contents:read + the explicitly allowed actions: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 every tend-*.yaml job-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-*.yaml file's generated header and its max-sixty/tend/claude@ pin both read 0.2.13, above the 0.1.19 floor.
PASS — security-ci.md unpinned-action bound inside tend-*.yaml: every uses: line in the nine tend-*.yaml files 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_target restricted to tend-*.yaml: confirmed above, only tend-review.yaml (exempt) uses it.
PASS — security-ci.md CHROMATIC_PROJECT_TOKEN in secrets.allowed: .config/tend.yaml → secrets.allowed: [CHROMATIC_PROJECT_TOKEN].
PASS — security-ci.md workflow-audit.yaml liveness: state: "active"; latest successful run 2026-09-21T14:00:40Z, ~19h before the current audit time (2026-09-22T09:22:50Z UTC) — within 48h.
PASS — security-ci.md workflow-audit.yaml gap-resistant window: read the workflow source — SINCE is drawn from gh 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 /environments shows hosted-preview, hosted-production, hosted-release-tag all have can_admins_bypass:false and nonempty required_reviewers (nedtwigg, edgartwigg); branch policies restrict each to main (hosted-preview also admits refs/pull/*/merge, expected for PR previews per the workflow's own fork/ready checks).
PASS — security-ci.md HOSTED_TAG_TOKEN placement: grep -rn HOSTED_TAG_TOKEN .github/ shows exactly one reference, hosted-production.yml's tag job, which runs in the hosted-release-tag environment; 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's deploy and cleanup jobs both gate on github.event.pull_request.head.repo.full_name == github.repository (fork exclusion); deploy has needs: verify; cleanup checks out ref: refs/heads/main, never the PR ref; hosted-production.yml's tag job has needs: deploy, and deploy'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-publish environment has nonempty required reviewers, prevent_self_review:true, can_admins_bypass:false (from the environments API dump above). release.yml's publish-vscode job carries environment: {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.yml desktop-signing-in-CI bound: grep for secrets.TAURI_SIGNING_PRIVATE_KEY/secrets.APPLE_SIGN_PASS/secrets.EV_SIGN_PIN across .github/workflows/ returns nothing; release.yml's "Generate ephemeral Tauri updater key" step calls tauri signer generate --ci and writes the key to a $RUNNER_TEMP file, never a repo secret.
PASS — security-ci.md sign-and-deploy.sh three-part bound: check_gh_attestation_support + gh attestation verify (attestations), check_sha256_manifest (SHA-256 manifests), and jsign --storetype PIV (PIV-backed Windows signing) are all present in scripts/sign-and-deploy.sh.
PASS — security-ci.md signing-credential argv bound: TAURI_SIGNING_PRIVATE_KEY is passed only via an env-var prefix (TAURI_SIGNING_PRIVATE_KEY="$TAURI_SIGNING_PRIVATE_KEY" ... tauri signer sign), never on the tauri signer sign argv; EV_SIGN_PIN reaches jsign only via --storepass env:EV_SIGN_PIN (a reference), never a literal value on argv. (APPLE_SIGN_PASS remains 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.yaml exists, is not disabled, and is triggered by schedule+workflow_dispatch; release.yml's security-audit job does gh workflow run security-audit.yaml then gh run watch "$run_id" --exit-status; publish-vscode declares needs: [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 every docs/specs/security*.md is claimed by exactly one domain's **Scope block and that every named scope file exists.
PASS — security-audit.md dedicated application-security subagent: .github/audit/orchestrator.md §1 spawns exactly three Task-tool subagents (supply-chain, ci-and-secrets, application-security) concurrently in one message; application-security.md is a separate file, not merged into either mechanical domain's prompt.
PASS — security-audit.md model floor: .github/workflows/security-audit.yaml's claude_args sets --model sonnet as the session default and overrides application-security to "model":"opus" inside --agents; scripts/security-audit-local.sh's run_domain() sets model_args="--model sonnet" and overrides to "--model opus" only for application-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 file security-audit.yaml's --agents and the orchestrator prompt name; scripts/security-audit-local.sh loops over the same five basenames and errors out if any is missing.
PASS — security-audit.md domain qualitative-scope coverage: application-security.md states it is "the catch-all domain... defined by subtraction, not by a list" over what supply-chain.md and ci-and-secrets.md explicitly claim, and instructs ls -A recomputation 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.md and running node --test scripts/security-audit.test.mjs (41/41 passing) and node --test scripts/workflow-audit.test.mjs (6/6 passing): non-turn-ending Bash until loop with a persisted $RUNNER_TEMP/audit-deadline, self-breaking under the Bash cap (BASH_DEFAULT_TIMEOUT_MS: "600000" in security-audit.yaml, above the loop's 540s break); orchestrator prompt forbids ending the turn before audit-report.md exists; 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 exact VERDICT: PASS is accepted as passing, VERDICT: FAIL (with any suffix) is recognized as dissent and forces STATUS=FAIL, VERDICT: INCONCLUSIVE and any unrecognized first line are both tracked as non-passing; only literal PASS/FAIL file contents are honored for FILE_STATUS, anything else becomes MISSING.
PASS — security-audit.md redaction fail-closed bound: the Redact secrets from agent output step's Node script covers audit-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_PAT provisioning gate: the Verify AUDIT_PAT is provisioned step runs after checkout/setup-node/install and before the audit step, exits 1 and writes FAIL to audit-status.txt when 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.json and .vscode/launch.json were inspected for runOn: "folderOpen" or similar checkout-time execution (the risk .github/audit/ci-and-secrets.md calls out by name) — neither file defines any such trigger; tasks.json has one manual build-dormouse-vscode task with no runOptions.
INFO: .claude/settings.json grants a small, benign Bash(...) allowlist (test/build/screenshot commands) and defines no hooks block, so nothing there executes automatically on session start or checkout.
INFO: eight untracked frag-*.md scratch files and one dormouse.yml sit in the repo root during this run. dormouse.yml is a tracked, legitimate dor tool config unrelated to the audit. The frag-*.md files are untracked working-directory artifacts (matching the application-security domain's per-topic scratch-file naming) from the concurrently running application-security subagent sharing this checkout — not a repository or workflow issue, and outside this domain's scope to characterize further.
INFO: release-attest and tend environments have can_admins_bypass:true (unlike the Hosted and VS Code publish environments, which require false). Neither docs/specs/security-ci.md nor docs/specs/security-audit.md states a bypass requirement for these two environments specifically, and release-attest holds no secrets while tend sends its GITHUB_TOKEN-adjacent secrets through admin-gated main-only deployment anyway — so this is not a FAIL IF violation, only a documentation gap worth naming since the spec is explicit about bypass being false everywhere 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] — isKnownUnsupportedIterm2Osc still consumes OSC 52/50/1337 and every pty:data parse site runs TerminalProtocolParser first — lib/src/lib/terminal-protocol.ts
PASS — terminal-output/bounds [9] — TITLE_LIMIT/BODY_LIMIT/COMMAND_LINE_LIMIT/sanitizeText present and applied, COMMAND_LINE_LIMIT binds after the \xNN unescape with a 4x pre-bound; MAX_CWD_LENGTH/boundedCwdValue in lib/src/lib/terminal-state.ts; OSC-incomplete cap 16,384 at terminal-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; classifyDisplayMatch deceptive verdict renders no open action (ExternalLinkModal.tsx), and ExternalLinkModalHost.tsx:24-27 rejects a deceptive confirmation host-side
PASS — browser/shim-target [6] — iframeShim/normalizeEmbedderOrigins in lib/src/host/iframe-proxy-rewrite.ts target only the proxy origin plus the validated innermost origin, relay no nested location, no foreign-origin and no unregistered message, and the proxy uses no unvalidated chain
PASS — browser/vscode-token [3] — isHostMessage runs before any dispatch in lib/src/lib/platform/vscode-adapter.ts:154-158; token minted per serve and attached only by WebviewChannel.post in vscode-ext/src/webview-messaging.ts
PASS — dor/control-dir [5] — ensureControlDir requires real directory, non-symlink, this uid, exactly 0700 before bind (standalone/sidecar/dor-control-server.js:88-96); resolveControlSocketPath yields null otherwise, 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-timingSafeEqual in proofMatches (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.mjs green; every non-test listener it prints inspected, plus a manual sweep for createServer / .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) and hosted/server/dev.ts:26,37 (allowedDevRequest on request and upgrade)
PASS — iframe/origin-rewrite [3] — Origin rewritten to the upstream's own only for own-origin callers, on handleRequestandhandleUpgrade; a foreign Origin is forwarded untouched, not blocked
PASS — iframe/cookies [4] — Cookie and Set-Cookie stripped on HTTP and on WebSocket handshakes, refused upgrades included
PASS — iframe/host-check [2] — Host must 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 Origin does not refresh a grant's idle timer; an absent Origin does
PASS — stream-relay [5] — grant single-use, TTL-bounded, pinned to one target port, Origin dropped rather than rewritten
PASS — browser-dev/gates [6] — all four gates present (per-run token, loopback Host, application/json on 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-rebinding Host check
PASS — file-viewer [20] — fresh 256-bit URL capability compared by SHA-256 then timingSafeEqual including malformed lengths, case-insensitive loopback Host, absent-or-same-listener Origin, GET/HEAD only, allowsFileViewerRequest gating 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_owner in standalone/src-tauri/src/lib.rs restrict directory and file on every platform arm (0700/0600 unix; 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 in vscode-ext/src/, so NOTEPAD_ARCHIVE_KEY cannot 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] — #onConnectionTransport consumes its own challenge and verifies with verifyPresenceProof against a binding built from the Burrow's own burrowId, connection id, challenge and handshake hash, then requires one active BurrowAclRecord carrying 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 :1136 so it happens exactly once per ceremony, matched against the immutable pairingId
PASS — expected-code-never-leaves [3] — PairingQueueItem is the four-field projection at lib/src/host/remote/service.ts:705-710 and the type at service-protocol.ts:90-97; no invitation private half crosses
PASS — pending-maps-bounded [6] — MAX_PENDING_PAIRINGS on both the runtime map and the mirrored service queue with oldest-first eviction, MAX_PENDING_CONNECTION_HANDSHAKES, MAX_TOKENS_PER_BURROW, MAX_CLIENT_ID_LENGTH applied 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).
Audit reached no usable verdict at 2026-09-22T09:33Z. 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
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..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.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).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.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.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.pnpm-workspace.yamlline 40 setsminimumReleaseAge: 1440. Cooldown and alerts -> FAIL IF maybe: drop node-pty for a Rust backend #1..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..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.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
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.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.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.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.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
gh api repos/diffplug/dormouse/private-vulnerability-reporting→{"enabled":true}.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.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.GET repos/diffplug/dormouse/collaborators/dormouse-bot/permission→permission:"write",role_name:"write",permissions.admin:false,permissions.maintain:false. Neithermaintainnoradminpresent.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.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.default_workflow_permissions:gh api repos/diffplug/dormouse/actions/permissions/workflow→{"default_workflow_permissions":"read","can_approve_pull_request_reviews":false}.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.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.tend-*.yamlfile's generated header and itsmax-sixty/tend/claude@pin both read0.2.13, above the0.1.19floor.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).pull_request_targetrestricted totend-*.yaml: confirmed above, onlytend-review.yaml(exempt) uses it.tend-*.yaml:.config/tend.yaml's... correction,.github/renovate.json'spackageRulesincludes{"matchManagers":["github-actions"],"matchFileNames":[".github/workflows/tend-*.yaml"],"enabled":false}.CHROMATIC_PROJECT_TOKENinsecrets.allowed:.config/tend.yaml→secrets.allowed: [CHROMATIC_PROJECT_TOKEN].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.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..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.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).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").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.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.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.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.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.).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].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.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..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..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.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..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.".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.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.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.Qualitative findings
No BLOCKER or WARNING findings in the
ci-and-secretsdomain (.github/,.config/,.claude/,.vscode/,scripts/,website/public/, and code touching secrets)..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..claude/settings.jsongrants a small, benignBash(...)allowlist (test/build/screenshot commands) and defines nohooksblock, so nothing there executes automatically on session start or checkout.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.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 IFin scope was decomposed to its individual clauses and eachclause 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-clauseevidence 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)isKnownUnsupportedIterm2Oscstill consumes OSC 52/50/1337 and everypty:dataparse site runsTerminalProtocolParserfirst —lib/src/lib/terminal-protocol.tsTITLE_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-448linkHandler(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-sideiframeShim/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 chainisHostMessageruns 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.tsensureControlDirrequires 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)timingSafeEqualinproofMatches(dor-control-server.js:19-34,dor/src/control-client.ts:70-78); 24 CSPRNG bytes per host processsecurity-local.md— Loopback listeners, file viewer, persisted state, Dor Tools (74 clauses, all PASS)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)Originrewritten to the upstream's own only for own-origin callers, onhandleRequestandhandleUpgrade; a foreignOriginis forwarded untouched, not blockedCookieandSet-Cookiestripped on HTTP and on WebSocket handshakes, refused upgrades includedHostmust name the grant's own port on both pathsframe-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)Origindoes not refresh a grant's idle timer; an absentOrigindoesOrigindropped rather than rewrittenHost,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-rebindingHostchecktimingSafeEqualincluding 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 imageswrite_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 filesetKeysForSync(call anywhere invscode-ext/src/, soNOTEPAD_ARCHIVE_KEYcannot be opted instandalone/sidecar/pty-core.js,vscode-ext/src/message-router.ts)security-remote.md— Trust boundary (70 clauses, all PASS)#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 themBurrowAcl.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 immutablepairingIdPairingQueueItemis the four-field projection atlib/src/host/remote/service.ts:705-710and the type atservice-protocol.ts:90-97; no invitation private half crossesMAX_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.mdis in this run'saudit-transcriptartifact (download).