Skip to content

[security-audit] FAIL on 2026-07-09 #236

Description

@github-actions

Audit failed at 2026-07-09T08:03Z. Run

Security Audit Report

Audited commit: 05bb639 (main). Repo: diffplug/dormouse.

FAIL IF results

Dependency Supply Chain

  1. PASSnode website/scripts/generate-deps.js produced no diff against a clean checkout. Ran it directly: git status --porcelain and git diff --stat were empty after the script wrote Wrote 26 dependencies..., Wrote 12 direct and 489 transitive Cargo dependencies..., Wrote 1 bundled runtime....
  2. PASSpackage.json:6-11 has devEngines.runtime.version: "24.18.0", an exact MAJOR.MINOR.PATCH.
  3. PASSstandalone/src-tauri/build.rs:42-43 calls read_pinned_node_version + verify_node_version before bundling, failing the build on mismatch (build.rs:149-215).
  4. PASSrelease.yml:39-41 (build-standalone) uses actions/setup-node@...v6.4.0 with node-version-file: package.json, which resolves devEngines.runtime.version per the workflow's own comment (release.yml:34-38). Indirect confirmation: recent CI runs (which run the same cargo check / node-version-file path in standalone-smoketest) are green (gh api .../actions/workflows/ci.yml/runs → 3/3 latest conclusion: success), which would be impossible if the pin resolution were broken, since build.rs hard-fails on mismatch.
  5. PASSpnpm-workspace.yaml:18 has minimumReleaseAge: 1440.
  6. PASS.github/renovate.json:3"enabledManagers": ["github-actions", "npm", "cargo"].
  7. PASS.github/renovate.json package rules include minimumReleaseAge for npm/cargo at patch (1 day), minor (3 days), major (14 days) update types.

GitHub Actions Policies

  1. PASSgrep -rn pull_request_target .github/workflows/ returns only .github/workflows/tend-review.yaml:11.
  2. PASS — Non-agent-managed workflows (ci.yml, chromatic.yml, release.yml) grant only contents: read at the top level; release.yml's build-standalone/build-vscode jobs add only id-token: write + attestations: write (release.yml:14-17, 157-160); the security-audit job adds only actions: write (release.yml:225-234), matching the documented exception. publish-vscode has no job-level permissions: override, so it inherits the top-level contents: read.

Automated Maintainer (tend)

  1. PASS — Ruleset Merge access (id 16757376): target: branch, conditions.ref_name.include: ["~DEFAULT_BRANCH"], rules: [{"type":"update"}] only, bypass_actors: [{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"exempt"}] (role 5 = Admin), sole bypass actor.
  2. PASS — Ruleset Tag operations (id 16757382): target: tag, conditions.ref_name.include: ["~ALL"], rules: [{"type":"creation"},{"type":"update"}], same admin-only sole bypass actor.
  3. PASSGH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/collaborators/dormouse-bot/permission{"permission":"write", ...}. Not higher than push/write.
  4. PASSGH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/actions/secrets → only CHROMATIC_PROJECT_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, TEND_BOT_TOKEN. No OVSX_PAT/VSCE_PAT at repo level.
  5. PASS — Environment deployment-branch-policies: vscode-extension-publish[{"name":"v*","type":"tag"}] only (admin-gated by Tag operations). security-audit[{"name":"main","type":"branch"},{"name":"v*","type":"tag"}], both admin-gated (Merge access / Tag operations).
  6. PASSAUDIT_PAT is present in the security-audit environment (gh api .../environments/security-audit/secrets{"total_count":1,"secrets":[{"name":"AUDIT_PAT",...}]}) and absent from the repo-level secret list (check 13's output).
  7. PASS.config/tend.yaml:4-5secrets: allowed: [CHROMATIC_PROJECT_TOKEN].
  8. PASSworkflow-audit.yaml state: "active"; latest successful run created_at: 2026-07-08T09:32:13Z, current time 2026-07-09T07:56:30Z → ~22.4h old, within 48h.
  9. PASS — All 8 tend-*.yaml files pin actions/checkout@v7 and max-sixty/tend/claude@0.1.9 — version-tag pins, which the spec explicitly permits for this file class (none use @main or an unpinned ref).
  10. FAILtend-mention.yaml's verify job (lines 33-165) declares no permissions: block at either workflow or job level (only the handle job at line 169 does). The repo's default Actions permission is write (GH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/actions/permissions/workflow{"default_workflow_permissions":"write", ...}), which grants the job's ambient GITHUB_TOKEN write access to scopes beyond the allowed set (e.g. checks, deployments, packages, security-events, statuses write) — exceeding contents:write, pull-requests:write, issues:write, id-token:write, actions:read, read-*. In practice every gh api call in this job explicitly overrides GITHUB_TOKEN to secrets.TEND_BOT_TOKEN (lines 138, 160), so the ambient elevated token is not currently exercised — but the grant itself violates the letter of the rule. See qualitative findings for the root cause.

VS Code Extension Releases

  1. PASSrelease.yml:290-291publish-vscode job has environment: name: vscode-extension-publish.
  2. PASSgrep -rn "VSCE_PAT\|OVSX_PAT" .github/workflows/ shows both only inside publish-vscode (release.yml:320, 332), which runs under the vscode-extension-publish environment.
  3. PASS — No production desktop signing secrets (APPLE_SIGN_PASS, EV_SIGN_PIN, non-ephemeral TAURI_SIGNING_PRIVATE_KEY) appear anywhere in release.yml; the only Tauri key material in CI is the ephemeral one generated at release.yml:63-74.
  4. PASSrelease.yml:63-74 ("Generate ephemeral Tauri updater key") runs tauri signer generate --ci --write-keys ... --force and exports it as TAURI_SIGNING_PRIVATE_KEY for the unsigned CI build.

Desktop Releases

  1. PASSscripts/sign-and-deploy.sh:392-399 (verify_downloaded_artifact) calls gh attestation verify with --cert-identity/--cert-oidc-issuer/--source-ref/--source-digest.
  2. PASSscripts/sign-and-deploy.sh:359-370, 402 (check_sha256_manifest) runs sha256sum -c / shasum -a 256 -c against the manifest.
  3. PASSscripts/sign-and-deploy.sh:695-714 (sign_windows) uses jsign --storetype PIV ....

CI Validation Contract

  1. PASSsecurity-audit.yaml exists, state: "active" (checked via gh api .../actions/workflows/security-audit.yaml), and is dispatched from release.yml's security-audit job (release.yml:236-281), which publish-vscode depends on (needs: [build-standalone, build-vscode, security-audit], release.yml:285-288).
  2. PASS — The audit prompt still requires the qualitative pass (security-audit.yaml:90-93: "Then do a qualitative pass over..."), no FAIL IF is marked ignorable, the failure-reporting step that opens/updates a security-audit-failure issue and exit 1s is intact (security-audit.yaml:123-172), and the AUDIT_PAT pre-check (security-audit.yaml:47-55) is present and unbypassed (exit 1 on missing secret).

Qualitative findings

  • BLOCKER — repo-wide default_workflow_permissions is write, not read. GH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/actions/permissions/workflow returns {"default_workflow_permissions":"write","can_approve_pull_request_reviews":true}. This is the root cause of FAIL-IF Improve alert icons (and rename from alarm to alert) #19: any workflow or job — present or future, human-authored or tend-authored — that omits an explicit permissions: block silently receives broad write scopes (contents, issues, pull-requests, checks, deployments, packages, security-events, statuses) rather than the least-privilege default the rest of SECURITY.md's ruleset/environment model is built around. Every currently-reviewed non-agent-managed workflow (ci.yml, chromatic.yml, release.yml) happens to declare an explicit top-level permissions: contents: read, so today's actual exposure is limited to the one tend-mention.yaml job identified above — but the repo setting itself is the hole, not just that one job. Recommended fix: flip the org/repo setting to read and add an explicit permissions: block to every job (including tend-mention.yaml's verify job) that needs anything more.
  • WARNING — ANTHROPIC_API_KEY is referenced by every tend-*.yaml workflow but is absent from SECURITY.md's threat enumeration. grep -rn ANTHROPIC_API_KEY .github/workflows/ shows it passed as anthropic_api_key: to max-sixty/tend/claude@0.1.9 in all 8 tend workflows, yet SECURITY.md's "Automated Maintainer" section enumerates exactly three reachable secrets (TEND_BOT_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, CHROMATIC_PROJECT_TOKEN) as the complete accepted blast radius. It is not a repo-level secret (confirmed via check Themes for standalone and web playground #13's listing), so if it resolves to a real value it must be an org-level secret — org-secret listing (gh api orgs/diffplug/actions/secrets) returned 403 Resource not accessible by personal access token under AUDIT_PAT, which is expected and not a scope drift (SECURITY.md only provisions AUDIT_PAT with repo-scoped Administration/Secrets/Environments, never org-level access) but does make this UNVERIFIABLE whether the secret is populated or dead. Either way, SECURITY.md's tend threat model should be updated to account for it, or the reference should be removed if unused.
  • INFO — qualitative code-level scan (spawn/subprocess/crypto/auth) found nothing. A targeted review of dor-lib-common/ (spawnAndCapture uses cross-spawn with an argument array, no shell: true, no string interpolation), server-lib-common/src/security/ (randomness via crypto.getRandomValues/randomBytes, timing-safe comparisons throughout, correct passkey/ACL verification order, key hash checked against stored ACL value rather than trusting the wire), and server/src/ (session/bearer gating before WS upgrade, no user-controlled path reaching static file serving) turned up no exploitable issues. No hardcoded credentials (ghp_, github_pat_, sk-ant-, AWS keys, PEM private keys) or curl | sh-style patterns were found anywhere in the tree.

Summary

Overall: FAIL. One concrete FAIL IF violation (#19) was confirmed: the verify job in tend-mention.yaml has no explicit permissions: block and therefore inherits the repository's default_workflow_permissions: write setting, granting it write scopes beyond what SECURITY.md permits for agent-managed workflows. That repo-level default itself is flagged BLOCKER in the qualitative section as the systemic root cause — it is a live gap in the least-privilege model the rest of the document (rulesets, environment scoping, per-workflow permission caps) is built to enforce, even though no currently-reviewed workflow exploits it beyond the one job. All 27 other FAIL IF checks passed with direct evidence, and the deeper qualitative code scan (subprocess spawning, crypto/auth primitives, server request handling, hardcoded-secret patterns) found no additional vulnerabilities. Remediation is narrow and low-risk: set the repo's default workflow permission to read and add an explicit least-privilege permissions: block to the tend-mention.yaml verify job (and, since it's a tend-generated file, either patch it upstream in the tend generator or accept the override will be reverted on next regeneration).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions