Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[security-audit] FAIL on 2026-10-03

Aperta
#908 4 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
35/100
Tipo di issue
Bug
Chiarezza
Da chiarire
Stato di attività
Attiva
Stack tecnologico
node.js, typescript

Direzione di ricerca

Start with the failing audit-application.md section and its linked run transcript, then inspect scripts/loopback-lint.mjs, scripts/loopback-lint-selftest.mjs, scripts/e2e-lint.mjs, and the referenced security and CLI specs. Re-run the security audit and its available lint or test commands; done means the reported FAIL and associated warnings are resolved and the audit passes.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

security-audit-failure

Audit failed at 2026-10-02T10:37Z. Run · Transcript

  • A domain returned FAIL. audit-application.md opened with VERDICT: FAIL — read that domain's section first. A domain's own verdict outranks the merged one.
Lines that decided this verdict

Lifted out of the fragments so truncation cannot cut them. Each domain's full section follows for as far as the body reaches.

  • audit-supply-chain.md: VERDICT: PASS
  • audit-ci-secrets.md: VERDICT: PASS
  • audit-application.md: VERDICT: FAIL
  • audit-hosted.md: VERDICT: PASS
  • audit-application.md: - WARNING: scripts/loopback-lint.mjs checks 2 (stale ALLOWED entry, :205-213) and 3 (zero non-test listeners matched, :216-222) have no self-test case — scripts/loopback-lint-selftest.mjs only plants bind-form, .mts-extension, and test-file fixtures; AGENTS.md says a lint rule without its self-test case "is not enforced". Code side is behind: add mutations (e.g. a bogus ALLOWED path; a LISTEN_RE that matches nothing).
  • audit-application.md: - FAIL: docs/specs/security-remote.md#Trust boundary — each rule names a line in security-remote.md or security-hosted.md -> "Rendezvous boundary" — evidence: two rules ('Hosted's RelayRoom never names, parses, decodes, logs, or stores a frame', 'The shared frame layer copies ct field by field and reads it nowhere', scripts/e2e-lint.mjs:476,489) cite docs/specs/security-hosted.md:62 under '## Relay boundary', not 'Rendezvous boundary'. Spec side is wrong (the FAIL IF text is stale; AGENTS.
  • audit-application.md: - WARNING: docs/specs/security-remote.md#Trust boundary (e2e-lint FAIL IF) says each rule names a line in security-remote.md or security-hosted.md -> "Rendezvous boundary"; two RULES (scripts/e2e-lint.mjs:476,489) cite security-hosted.md '## Relay boundary' (line 62). Spec text is stale; update it to name both hosted headings.
  • audit-application.md: - UNVERIFIABLE: security-remote.md#Credentials at rest / setup password — relay unit tests (config, setup-password-store, token-bucket, burrows, cors) not executed — reason: relay/dist not built in this checkout (node --test failed at import); verdicts above are from source reading
  • audit-application.md: - WARNING: One-time phone page keeps-nothing rule is only textual: the shipped /connect/ bundle (built with lib/vite.one-time.config.ts to /tmp/ot-dist) contains lib/src/lib/local-json-store.ts readers/writers pulled in transitively (alert-settings.ts, watched-commands.ts, session-activity-store.ts, alert-manager.ts, and even lib/src/lib/platform/vscode-adapter.ts via PocketWall/remote-wall/terminal-registry); the minified bundle runs localStorage.getItem('dormouse:alert-settings') at module ini
  • audit-application.md: - WARNING: Spec drift / inconsistent hardening on bare-name spawns: lib/src/host/git-cli.ts:14 calls spawnAndCapture('git', ...) with the bare name, which cross-spawn resolves through which — searching the cwd before PATH on Windows. That is the exact hazard docs/specs/dor-cli.md -> "Spawning External Binaries" (lines 107-122) names, yet that rule lists only dor agent-browser, dor playwright and the browser hosts. dor-tools-builtin/src/folder-viewer.ts:54 already resolves git via
  • audit-application.md: - WARNING: security-remote.md#Trust boundary: the e2e-lint FAIL IF is violated as written. Two RULES entries, at scripts/e2e-lint.mjs:476 and :489, cite docs/specs/security-hosted.md -> "Relay boundary" (line 44). The FAIL IF (docs/specs/security-remote.md:61) allows only "Rendezvous boundary". The spec text is the stale side: it should name both headings, as the lint header (scripts/e2e-lint.mjs:6) already does. Both rules are enforced and self-tested.
  • audit-application.md: - WARNING: scripts/loopback-lint.mjs has two checks with no self-test case: the stale-allowlist check (:205-213) and the zero-listeners check (:216-222). By the AGENTS.md rule they are not enforced. Fix the code side.
  • audit-application.md: - WARNING: The one-time phone bundle pulls in local-json-store readers and writers transitively (alert-settings and others). The "keeps nothing" rule is checked only by text, on a narrow file set. Either add a bundle-level assert or narrow the spec.
  • audit-application.md: - WARNING: lib/src/host/git-cli.ts:14 spawns git by its bare name. On Windows that searches the current directory before PATH. docs/specs/dor-cli.md -> "Spawning External Binaries" covers only the browser tools. Not attacker-reachable today.
  • audit-hosted.md: - WARNING: Bearer-gated relay routes reach Postgres without a per-address limit. requireSession/requireBurrow/readAsBurrow check only the bearer's shape (isRelayBearer) before a SELECT ... WHERE "tokenHash" = $1 (hosted/server/relay-auth.ts:142-153). Anyone can make up a 32-byte base64url bearer and send unlimited requests to GET /api/burrows, POST /api/burrow/setup-token, reauth/*, setup/retire and the push routes; each costs one connection and one indexed read. Nothing confid

Security audit

Supply chain

VERDICT: PASS

FAIL IF results
  • PASS: generate-deps.js against clean tree after install produced no diff in website/src/data/ (git status clean; 70 npm, 12 direct + 478 transitive cargo, 1 runtime).
  • PASS: .github/workflows/ci.yml lines 24-46 run pnpm install --frozen-lockfile then node website/scripts/generate-deps.js and exit 1 on git diff --quiet -- website/src/data/.
  • PASS: roots (generate-deps.js:23-30) = dor, dormouse, dormouse-standalone, dormouse-lib, dormouse-sidecar, relay; exclusions (line 33) = canopy, dormouse-website, dormouse-hosted. All 13 pnpm-workspace.yaml packages classified; remote-lib-common, dor-lib-common, dor-tools-builtin, dor-tools-lib are link: edges from roots (pnpm-lock.yaml). Tauri resources only "../sidecar/**/*"; no excluded package is a dependency of a root.
  • PASS: package.json devEngines.runtime.version is exactly 24.18.0.
  • PASS: standalone/src-tauri/build.rs:43 calls verify_node_version, which runs --version and errors on mismatch (lines 207-224); only skip is host != target (line 197), and release.yml standalone matrix (lines 28-36) is all host-native (ubuntu x86_64, macos aarch64, windows x86_64).
  • PASS: release.yml build-standalone uses setup-node with node-version-file: package.json (lines 47-49); root package.json has no volta or engines.node field.
  • PASS: pnpm-workspace.yaml has minimumReleaseAge: 1440.
  • PASS: minimumReleaseAgeExclude is only pgstencil and @pgstencil/*; Renovate's only null minimumReleaseAge rule is the pgstencil / @pgstencil/** rule.
  • PASS: renovate.json enabledManagers includes npm and cargo; packageRules set minimumReleaseAge for npm+cargo (patch 1 day, minor 3 days, major 14 days).
  • PASS: renovate.json vulnerabilityAlerts block sets minimumReleaseAge "1 day" explicitly.
  • PASS: secret_scanning enabled and secret_scanning_push_protection enabled (AUDIT_PAT read); vulnerability-alerts returned 204.
Qualitative findings
  • INFO: only lockfile entry outside the npm registry is @diffplug/xterm-addon-webgl-sdf (GitHub release tarball with integrity hash), imported by canopy only, which is an excluded Storybook-only workspace. node@runtime:24.18.0 is the pinned runtime, with integrity.
  • INFO: install-script packages are governed by allowBuilds in pnpm-workspace.yaml (node-pty, esbuild, sharp, workerd, @swc/core enabled); node-pty is shipped and disclosed. No undisclosed shipped package found.
  • INFO: dependabot_security_updates is disabled; the spec only requires alerts, so not a violation.
  • INFO: no newly added runtime dependency since the last audit could be identified from a diff; the disclosure snapshot is current.

CI and secrets

VERDICT: PASS

FAIL IF results
  • PASS security.md PVR enabled: API {"enabled":true}.
  • PASS security-ci pull_request_target not in non-tend workflows: only a comment at hosted-preview.yml:76.
  • PASS non-agent workflows effective write perms: argos/ci/hosted-*/release default contents: read; release.yml build jobs add only id-token/attestations write (lines 21-24,184-187); security-audit job actions: write (line 252-259). Repo default perms read.
  • PASS agent-managed workflows effective perms: tend-* jobs only contents/pull-requests/issues write + actions read; security-audit.yaml and workflow-audit.yaml workflow-level only contents/issues/id-token/actions/pull-requests (read where applicable); no job-level overrides.
  • PASS action SHA pinning in non-tend workflows: grep for uses: lacking 40-hex SHA found none.
  • PASS tend-* pins: all actions/checkout@v7, [email protected], max-sixty/tend/[email protected] (tag pins, none unpinned); 0.3.5 >= 0.1.19.
  • PASS Merge access ruleset (16757376): ~DEFAULT_BRANCH, rules exactly update/deletion/creation, sole bypass RepositoryRole 5, active.
  • PASS Tag operations ruleset (16757382): ~ALL tags, rules creation+update, sole bypass RepositoryRole 5, active.
  • PASS .config/tend.yaml merge: restricted (line 2); every tend-*.yaml merge: restricted only.
  • PASS dormouse-bot permission: permission=write, role_name=write (no maintain/admin).
  • PASS environments admitted refs: vscode-extension-publish v*; release-attest v*; security-audit main + v*; tend main; hosted-production/hosted-release-tag main; hosted-preview (exempt) main + refs/pull/*/merge. All custom_branch_policies, none null/protected_branches.
  • PASS secret inventory: repo = ARGOS_TOKEN, CHROMATIC_PROJECT_TOKEN only; org secrets empty; 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 empty secrets and no variables; no ANTHROPIC_API_KEY at any level.
  • PASS secrets.allowed in .config/tend.yaml lists CHROMATIC_PROJECT_TOKEN and ARGOS_TOKEN.
  • PASS workflow-audit.yaml active; last successful run 2026-10-01T14:24:49Z (<48h; now 2026-10-02 10:31Z).
  • PASS Renovate cannot update tend-.yaml: packageRules entry matchFileNames tend-.yaml enabled:false (renovate.json).
  • PASS workflow-audit lower bound: SINCE from previous successful run created_at (workflow-audit.yaml:86-92), fallback 25h only when none exists.
  • PASS default_workflow_permissions=read, can_approve_pull_request_reviews=false.
  • PASS Hosted environments (hosted-preview, hosted-production, hosted-release-tag): custom branch policies main (preview also refs/pull/*/merge), 2 required reviewers (nedtwigg, edgartwigg), can_admins_bypass false. Secrets only in Hosted environments, none at repo/org; production creds (DATABASE_URL, BACKUP_AGE_IDENTITY) not in preview.
  • PASS HOSTED_TAG_TOKEN only in hosted-release-tag; only tag job in hosted-production.yml uses that environment.
  • PASS hosted preview: deploy requires same-repo head, needs verify; cleanup checks out refs/heads/main; production tag needs: deploy (live verification step).
  • PASS vscode-extension-publish: 2 reviewers, prevent_self_review true, can_admins_bypass false.
  • PASS release.yml publish-vscode has environment vscode-extension-publish; VSCE_PAT/OVSX_PAT referenced only at release.yml:347,359 inside it.
  • PASS release.yml no production signing secrets (only GITHUB_TOKEN); ephemeral Tauri key generated (lines 70-81).
  • PASS sign-and-deploy.sh: gh attestation verify (455), sha256 manifest check (417-464), jsign --storetype PIV (763).
  • PASS TAURI_SIGNING_PRIVATE_KEY via env only (839); EV_SIGN_PIN --storepass env:EV_SIGN_PIN (764,777).
  • PASS security-audit.md workflow gate: security-audit.yaml active; release.yml dispatches via gh workflow run (282), gh run watch --exit-status (308), publish-vscode needs: security-audit (312-315).
  • PASS spec scope ownership: each of the 6 security*.md specs is in exactly one prompt's Scope; all named files exist; spec-lint OK.
  • PASS fan-out: security-audit.yaml --agents has four dedicated domains; application-security and hosted model: opus, floor --model sonnet (149-152); security-audit-local.sh same split (69-70).
  • PASS prompt files: all five prompts + _preamble exist in .github/audit/; local script uses AUDIT_DIR=.github/audit.
  • PASS qualitative scope coverage: application-security takes the remainder by subtraction; dotfile dirs named in ci-and-secrets prompt.
  • PASS workflow-audit WINDOW: single array (.github/workflows/ .config/tend.yaml .github/audit/ .vscode/) feeds commit list, own_changes, is_clean_merge, classifiers via ${WINDOW[@]:1}.
  • PASS orchestration: orchestrator.md has until-loop, persisted $RUNNER_TEMP/audit-deadline (1920s), sentinel-based finished(), requires audit-report.md; BASH_DEFAULT_TIMEOUT_MS=600000 > 540s break; timeout-minutes 40 > 32.
  • PASS reporting/redaction/AUDIT_PAT pre-check: redactor step covers transcript, audit-report.md and AUDIT_FRAGMENTS and rm -f's on throw (211-255); fragment guards unconditional, exact VERDICT: PASS, VERDICT: FAIL* dissent, sentinel check; AUDIT_PAT pre-check present (69-77). scripts/security-audit.test.mjs 49/49 pass.
  • PASS regen boundary: workflow-audit.yaml accepts only mode 100644/100755 blobs (269-273); workflow-audit.test.mjs 27/27 pass; sign-and-deploy.test.mjs 37/37 pass.
Qualitative findings
  • INFO: stray sibling-domain fragments (audit-application.md, audit-hosted.md, audit-supply-chain.md) sit at the repo root during the run; expected, no action.
  • INFO: .vscode has no runOn: folderOpen task; .claude/settings.json allowlist is read-only/test commands; no .mcp.json; website/public/standalone-latest.json URLs point at github.com/diffplug/dormouse releases with Tauri signatures.
  • INFO: Hosted environments allow self-review (prevent_self_review false), matching security-ci.md ("self-review is allowed").
  • INFO: release-attest and tend environments have can_admins_bypass true; not covered by a FAIL IF (only vscode-extension-publish and Hosted environments are), no change from spec.

Application security

VERDICT: FAIL

FAIL IF results

Checks were split across seven delegates (A: local terminal/browser/socket/persisted; B: loopback/viewer/network policy; C: remote trust boundary/relay origin; D: credentials/setup password/cross-origin; E: network posture/what crosses; F: direct path/one-time/revocation; G: catch-all sweep). Their results are merged below verbatim.

Delegate A
Delegate A (security-local.md: terminal output, browser panes, dor socket, persisted state)
  • PASS: security-local.md#Terminal output — TerminalProtocolParser consumes OSC 52 / OSC 50 — evidence: lib/src/lib/terminal-protocol.ts:113 OSC_CONSUMED_IDS has '50','52'; :324 parseOsc52 (always returns array); :925-928 isKnownUnsupportedIterm2Osc consumes 50
  • PASS: security-local.md#Terminal output — every parse site runs before pty:data leaves it — evidence: standalone/sidecar/main.js:76 if (event !== 'data') send(...) (raw data never sent, host.onPtyEvent parses); lib/src/host/remote/sidecar-entry.ts:186 onChunk -> pty:data after createOwnerPtyStream; vscode-ext/src/message-router.ts:487-489 posts only onProcessedPtyData visibleData; replay parsed by parseReplay (lib/src/lib/platform/replay-parse.ts:16) in vscode/tauri/browser-sidecar adapters
  • PASS: security-local.md#Terminal output — OSC 52 reaches clipboard only as user-chosen copy-editor program format — evidence: terminal-protocol.ts:936-943 emits clipboardOffer only; all adapters route it to offerProgramCopy (vscode-adapter.ts:131, standalone/src/tauri-adapter.ts:166, fake-adapter.ts:485); clipboard written only by copySelection (copy-selection.ts:17-21 via editorRendering(copyEditor, programCopy))
  • PASS: security-local.md#Terminal output — OSC 52 bounded and control-stripped (only \n and \t kept) — evidence: terminal-protocol.ts:940 data.length > CLIPBOARD_OFFER_LIMIT(16000) drops; lib/src/lib/osc-sanitize.ts:43-45 strips [\x00-\x08\x0b-\x1f\x7f-\x9f]
  • PASS: security-local.md#Terminal output — OSC 52 dropped in a pane with no shadowed drag — evidence: lib/src/lib/mouse-selection.ts:195-198 returns unless selection.owner === 'program'
  • PASS: security-local.md#Terminal output — retained values bounded and control-stripped — evidence: titles/bodies sanitizeText(TITLE_LIMIT/BODY_LIMIT) terminal-protocol.ts:345,379,459,913; OSC 99 pending capped (OSC99_MAX_PENDING_IDS=64, TTL, appendLimited :446-448); cwd boundedCwdValue (terminal-state.ts:784-789, MAX_CWD_LENGTH=4096) applied :252,261,265,279,697
  • PASS: security-local.md#Terminal output — COMMAND_LINE_LIMIT binds after unescape with 4x pre-bound — evidence: terminal-protocol.ts:734-741 truncateText(encoded, COMMAND_LINE_LIMIT*encodedWidth) then sanitizeCommandLine(decode(..), COMMAND_LINE_LIMIT); 633/133 \xNN width 4 (:721), cmdline_url width 12 (:720) — spec mentions only the 4x form (minor drift, not a weakness)
  • PASS: security-local.md#Terminal output — OSC 8 activation never reaches openExternal without the dialog — evidence: lib/src/lib/terminal-lifecycle.ts:144-149 linkHandler -> activateTerminalLink; lib/src/lib/terminal-link-activation.ts:35-37,51 non-preview or refused paths call requestExternalLinkConfirmation only; sole openExternal on that path is ExternalLinkModalHost.tsx:25-27 confirm()
  • PASS: security-local.md#Terminal output — dialog renders no open action for a deceptive verdict (host also refuses) — evidence: lib/src/components/ExternalLinkModal.tsx:110-128 deceptive branch offers Close + Copy only; ExternalLinkModalHost.tsx:25 verdict !== 'deceptive' gate
  • PASS: security-local.md#Terminal output — preview path only when display text is a whole-component suffix of decoded target — evidence: lib/src/lib/external-links.ts:113-118 (control chars refused; path === text || path.endsWith('/'+text))
  • PASS: security-local.md#Terminal output — host opens file: URLs only for empty/localhost/this-machine host — evidence: lib/src/host/tool-input.ts:45-56 localFileUrlPath -> namesThisHost(url.hostname) else ToolFileError; controls re-checked after decode and after realpath (:19-35)
  • PASS: security-local.md#Terminal output — OSC 367 open never dispatched from replay — evidence: lib/src/lib/tool-events.ts:23-36 recordToolEvents (used by parseReplay, replay-parse.ts:18) has no toolOpen branch; dispatchToolOpens only via applyLiveToolEvents (vscode-adapter.ts:127, tauri-adapter.ts:158, browser-sidecar-adapter.ts:333, fake-adapter.ts:479)
  • PASS: security-local.md#Terminal output — OSC 367 open only from a Tool running its designated command — evidence: lib/src/components/wall/use-dor-control.ts:1122-1128 requires isToolParams(meta) and currentCommand.rawCommandLine === tool.command
  • PASS: security-local.md#Terminal output — OSC 367 open path absolute and control-free — evidence: dor-tools-lib/src/osc.ts:124-135 validToolOpenPath (bounded, leading / or drive, no [\u0000-\u001f\u007f-\u009f])
  • PASS: security-local.md#Terminal output — control-socket request cannot set oscOpen — evidence: lib/src/lib/platform/dor-control-dispatch.ts:35,45-53 detail built field-by-field from payload (requestId/surfaceId/method/params), oscOpen only from the 3rd arg; only lib/src/lib/tool-open-requests.ts:33-40 passes {oscOpen:true} (wire callers vscode-adapter.ts:154, tauri-adapter.ts:233, browser-sidecar-adapter.ts:360 pass none)
  • PASS: security-local.md#Terminal output — error-viewer argv carries no control character — evidence: use-dor-control.ts:1392-1393 openFile is the validated OSC path; message .replace(/[\x00-\x1f\x7f-\x9f]+/g,' ')
  • PASS: security-local.md#Browser panes — shim targets only its proxy origin and the chain's innermost origin — evidence: lib/src/host/iframe-proxy-rewrite.ts:113 send() posts to location.origin and TARGET only (never '*'); TARGET = grant.embedderOrigins[0] (lib/src/host/iframe-proxy.ts:268,519)
  • PASS: security-local.md#Browser panes — shim relays no nested location, foreign-origin, or unregistered message — evidence: iframe-proxy-rewrite.ts relay listener requires e.origin===location.origin and forwards only leader/pointerdown/open-window(string url); 'location' not relayed; theme listener requires e.source===P && e.origin===TARGET
  • PASS: security-local.md#Browser panes — proxy uses a chain only if validated in full — evidence: iframe-proxy-rewrite.ts:67-77 normalizeEmbedderOrigins returns null on any non-string / non-serialized-origin entry, empty, or >8; iframe-proxy.ts:133-137 null chain => pass-through, no shim
  • PASS: security-local.md#Browser panes — VSCodeAdapter listeners act only after isHostMessage — evidence: lib/src/lib/platform/vscode-adapter.ts:100-103 and :215 check isHostMessage before reading type; lib/src/lib/vscode-message-token.ts:46-50 fails closed on null token
  • PASS: security-local.md#Browser panes — token minted per serve, attached only by WebviewChannel.post — evidence: vscode-ext/src/webview-html.ts:155-165 randomBytes(24) per getWebviewHtml; vscode-ext/src/webview-messaging.ts:34-41 serveWebview stamps it in post(); all other posts go through channel.post (webview-view-provider.ts:23-24; grep shows no other webview.postMessage)
  • PASS: security-local.md#The dor control socket — ensureControlDir requires real dir, not symlink, owned by uid, exactly 0700 — evidence: standalone/sidecar/dor-control-server.js:88-96 (lstat-based isDirectory && !isSymbolicLink && uid===uid && (mode&0o777)===0o700); chmod only when already ours (:80-87)
  • PASS: security-local.md#The dor control socket — resolveControlSocketPath refuses when the predicate fails — evidence: dor-control-server.js:118-119 if (!safeDir) return null (win32 named pipe branch :113-116 has no dir, by design); VS Code reuses the same module (vscode-ext/src/pty-host.js:7)
  • PASS: security-local.md#The dor control socket — raw token never reaches a socket — evidence: server writes challenge nonce + HMAC welcome only (dor-control-server.js:236,251); client writes hello{nonce,proof} then request{requestId,surfaceId,method,params,timeoutMs} (dor/src/control-client.ts:314-334), no token field
  • PASS: security-local.md#The dor control socket — both sides compare proofs only via SHA-256-then-timingSafeEqual — evidence: dor-control-server.js:29-34,247; dor/src/control-client.ts:82-86,323; constructions identical (HMAC-SHA256(token, ${domain} ${nonce}) hex at server :19-20, client :78-79)
  • PASS: security-local.md#Persisted state — write_file_atomically restricts dir 0700 and file 0600 on unix before bytes — evidence: standalone/src-tauri/src/lib.rs:1815-1825 ensure_parent_with restrict(dir,0o700); :1849-1853 restrict(&tmp,0o600) before write_all; unix arm :1683-1687
  • PASS: security-local.md#Persisted state — Windows arm applies a protected DACL with exactly one current-user ACE — evidence: lib.rs:1695-1797 single EXPLICIT_ACCESS_W (FILE_ALL_ACCESS, TokenUser SID) via SetEntriesInAclW(NewAcl=None) + SetNamedSecurityInfoW(DACL|PROTECTED_DACL); test restrict_to_owner_leaves_one_owner_only_ace at :4827
  • PASS: security-local.md#Persisted state — every lib.rs writer under the state root goes through it — evidence: grep of fs::write/File::create/OpenOptions in standalone/src-tauri/src/*.rs: non-test writers are write_file_atomically callers (:1880 session, :1994 legacy-transcript scrub, :2248 geometry, :2488 arrivals.json) plus the log (:886,:902; known gap in security-local.md 'standalone log'), the dor-node.exe cache copy (:3566, cache dir not state root), and clipboard_win.rs:174 (temp_dir drop file)
  • PASS: security-local.md#Persisted state — named pin tests exist — evidence: lib.rs:5200 session_permission_failures_preserve_previous_snapshot_without_writing_bytes, :5228 session_write_tightens_directory_and_existing_temp_file
Qualitative (security-local.md delegate A: terminal output, browser panes, dor socket, persisted state)
  • INFO: Iframe-proxy framing — replacement is exactly frame-ancestors 'self' <validated chain> (lib/src/host/iframe-proxy-rewrite.ts:84-86); upstream CSP kept only with x-dormouse-preserve-csp and then intersected (iframe-proxy.ts:404-415); null chain keeps X-Frame-Options/CSP (replaceFraming=false, :393) and passThrough injects no shim (:274); serveErrorPage likewise uninstrumented with no chain (:515-526). Shim message targets are location.origin + TARGET, never '*'. No defect found.
  • INFO: OSC id routing is by literal decimal string (terminal-protocol.ts:873-881), so OSC 052;... / OSC 050;... are forwarded to xterm.js, which parses the id numerically as 52/50. Harmless today: no ClipboardAddon or OSC 50/52 handler is registered (grep of lib/src, standalone/src, vscode-ext/src, website/src). Would become a bypass of the 'consume OSC 52' rule if a clipboard addon were ever loaded; consider normalizing leading zeros in oscDispositionAt.
  • INFO: OSC 367 open gate compares currentCommand.rawCommandLine (shell-reported via OSC 633 E / 133 C) with tool.command (use-dor-control.ts:1124-1125). A later process in the same Tool pane, after the designated command exits, can forge that report and pass the gate. Confined to a pane already designated as a Tool; noting because the spec phrases the check as 'running its designated command'.
  • INFO: validToolOpenPath accepts any leading '/' (dor-tools-lib/src/osc.ts:124-127), so on Windows a running Tool can name //server/share/x, which resolveLocalToolTarget realpaths (lib/src/host/tool-input.ts:29), touching SMB. Limited to a running trusted Tool; the OSC 8 path is safe because fileURLToPath rejects host-less UNC URLs.
  • INFO: Sweep of lib/src/host, dor/src, dor-lib-common/src, standalone/sidecar for command construction: all spawns are argv-based (spawnAndCapture/cross-spawn, execFile). No shell:true. runGit runs only rev-parse/config --get (no index read, so no fsmonitor) with dir in argv and no attacker cwd (lib/src/host/git-cli.ts:13-14, git-info.ts:70-72). Browser CLIs resolve the binary to an absolute path before spawning (dor/src/commands/browser-cli.ts:365-418). standalone/sidecar/clipboard-ops.js:167 builds AppleScript by escaping only '"' in the output path, not '\'. The path is host-generated, so this is not exploitable. Escaping backslashes too would be safer.
  • INFO: Terminal context directory actions: openDirectory checks for an absolute, NUL-free, existing directory, then realpaths it and passes it as a single argv to explorer.exe/open/xdg-open (standalone/sidecar/pty-core.js:1626-1632, 1179-1193). This matches the security-local.md rule. Unverified residual: explorer.exe parses its own command line, with commas acting as separators, so a canonical directory path containing commas could be read as more than one argument. Not exploited or tested here.
Delegate B
Delegate B: Loopback Listeners, Local-file viewer, Network policy
  • INFO: loopback-lint run — node scripts/loopback-lint.mjs -> "loopback-lint: OK (2 allowlisted)", 8 non-test listeners: dor-tools-builtin/src/viewer-server.ts:94, hosted/server/dev.ts:46, lib/src/host/browser-viewer.ts:112, lib/src/host/iframe-proxy.ts:197, scripts/direct-interop/run.mjs:405, scripts/dor-tool-qc/server.mjs:25, standalone/scripts/dev-agent-browser.mjs:279, standalone/scripts/dev-run.mjs:27; node scripts/loopback-lint-selftest.mjs -> OK (13 load-bearing checks)
  • PASS: docs/specs/security-local.md#Loopback Listeners — iframe proxy never rewrites Origin for a non-own caller (handleRequest) — evidence: lib/src/host/iframe-proxy.ts:226 if (isOwnOrigin(req.headers.origin, grant.port)) headers.origin = grant.upstream.origin; in upstreamRequestHeaders, used at :247
  • PASS: docs/specs/security-local.md#Loopback Listeners — same for handleUpgrade — evidence: lib/src/host/iframe-proxy.ts:461 handleUpgrade calls upstreamRequestHeaders
  • PASS: docs/specs/security-local.md#Loopback Listeners — iframe proxy strips Cookie upstream on HTTP and WS — evidence: lib/src/host/iframe-proxy.ts:227 delete headers.cookie in shared upstreamRequestHeaders
  • PASS: docs/specs/security-local.md#Loopback Listeners — strips Set-Cookie downstream on HTTP, 101 handshakes, and refused upgrades — evidence: iframe-proxy.ts:399 (sanitizeResponseHeaders), :477 (101 rawHeaders loop), :488-492 refused upgrade goes through passThrough->sanitizeResponseHeaders
  • PASS: docs/specs/security-local.md#Loopback Listeners — Host names own grant port on both paths — evidence: iframe-proxy.ts:232 and :456 isLoopbackHost(req.headers.host, grant.port) refuse (421 / socket.destroy) before lastUsed
  • PASS: docs/specs/security-local.md#Loopback Listeners — framing headers replaced with exactly frame-ancestors 'self' ; no usable chain preserves headers and injects nothing — evidence: iframe-proxy.ts:400-413 (drop only when embedderOrigins!==null, then append frameAncestorsCsp), :260-267 embedder===undefined -> passThrough (no shim); iframe-proxy-rewrite.ts:67-86 normalizeEmbedderOrigins strict SERIALIZED_ORIGIN_RE, frameAncestorsCsp; preserved upstream CSP only intersects
  • PASS: docs/specs/security-local.md#Loopback Listeners — shim posts only to its own proxy origin and the chain's innermost origin — evidence: iframe-proxy-rewrite.ts:119 P.postMessage(m,location.origin)+P.postMessage(m,TARGET), TARGET=embedderOrigins[0] (iframe-proxy.ts:258, :518)
  • PASS: docs/specs/security-local.md#Loopback Listeners — foreign Origin does not refresh idle timer; absent Origin does — evidence: iframe-proxy.ts:245 and :460 if (!isForeignOrigin(req.headers.origin, grant.port)) grant.lastUsed = Date.now();
  • PASS: docs/specs/security-local.md#Loopback Listeners — no loopback listener grants an unrecognized caller extra privilege (all 8 non-test listeners inspected) — evidence: viewer-server.ts:80-94 (see Local-file viewer lines); hosted/server/dev.ts:31,44 allowedDevRequest (exact Host+Origin, sec-fetch-site!=cross-site) on HTTP and upgrade; lib/src/host/browser-viewer.ts:104 Host+single-use grant, HTTP 403; iframe-proxy.ts admits-all/vouches-none; scripts/direct-interop/run.mjs:353,365 isAuthorized (token/Host/JSON) + isOwnOrigin for POST; scripts/dor-tool-qc/server.mjs allowlisted unshipped fixture; standalone/scripts/dev-agent-browser.mjs:231 isAuthorized before routing; standalone/scripts/dev-run.mjs:24-51 cors:false, allowedHosts:[]. Independent grep for createServer/.listen(/serve(/WebSocketServer over tracked non-test JS/TS found no extra loopback listener (others: relay/ out of scope, Unix socket/pipe in standalone/sidecar/dor-control-server.js:351 and vscode-ext/src/peer-link.ts:749)
  • PASS: docs/specs/security-local.md#Loopback Listeners — lint scans all tracked JS/TS and fails an unguarded new non-test listener; every BIND_FORMS form has a self-test fixture — evidence: scripts/loopback-lint.mjs:127-150 BIND_FORMS (6 labels) / SOURCE_EXT; scripts/loopback-lint-selftest.mjs FIXTURES cover all 6 labels plus .mts and test-file cases; run -> "loopback-lint-selftest: OK (13 load-bearing checks)"
  • PASS: docs/specs/security-local.md#Loopback Listeners — browser viewer upgrades only with own loopback Host and a single-use 60s grant for one view — evidence: lib/src/host/browser-viewer.ts:104 token && isLoopbackHost(req.headers.host, port) ? grants.consume(token); lib/src/host/browser-stream-guard.ts:12-20 randomBytes(32), expires now+60_000, delete on consume
  • PASS: docs/specs/security-local.md#Loopback Listeners — provider receives only rebuilt webview messages — evidence: browser-viewer.ts:332 parseViewerInput(raw) and :476-525 constructs fresh objects per type, default null

Truncated to fit: the full body is 182443 characters. The untruncated audit-report.md is in this run's audit-transcript artifact (download).

Lingua principale
TypeScript
Stelle
5
Fork
1
Merge medio
11h 50m
PR unite (30g)
353

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di diffplug/dormouse

Tutte le issue di diffplug/dormouse

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.