Skip to content

[security-audit] FAIL on 2026-07-20 #272

Description

@github-actions

Audit failed at 2026-07-20T07:35Z. Run

Security Audit Report

Audited against SECURITY.md as of commit 5b42136 (2026-07-20). $AUDIT_PAT (fine-grained,
read-only Administration + Secrets + Environments) was available and used for all admin-scoped
checks below.

FAIL IF results

Dependency Supply Chain

  1. PASS — Ran node website/scripts/generate-deps.js from a clean checkout (git status was
    clean before and after). git diff --stat against website/src/data/dependencies-{npm,cargo,runtime}.json
    produced no output — no drift.
  2. PASSpackage.json:6-12 has devEngines.runtime.version: "24.18.0" — exact MAJOR.MINOR.PATCH.
  3. PASSstandalone/src-tauri/build.rs:42-43,149-227 (read_pinned_node_version +
    verify_node_version) reads devEngines.runtime.version and fails the build (Err(...)) if the
    bundled binary's node --version doesn't match.
  4. PASS.github/workflows/release.yml:39-41 (build-standalone job):
    actions/setup-node@... with node-version-file: package.json, which resolves
    devEngines.runtime.version.
  5. PASSpnpm-workspace.yaml:23 has minimumReleaseAge: 1440.
  6. PASS.github/renovate.json:3enabledManagers: ["github-actions", "npm", "cargo"].
  7. PASS.github/renovate.json:68-82minimumReleaseAge package rules for npm/cargo at
    patch (1 day), minor (3 days), major (14 days).

GitHub Actions Policies

  1. PASSgrep -rn pull_request_target .github/workflows matched only
    .github/workflows/tend-review.yaml:11.
  2. PASS — Non-agent-managed workflows (ci.yml, chromatic.yml, release.yml) checked:
    • ci.yml:8-9, chromatic.yml:13-14: contents: read only, no writes.
    • release.yml build-standalone/build-vscode jobs (lines 14-17, 162-165):
      contents: read, id-token: write, attestations: write — exactly the allowed release
      provenance set.
    • release.yml security-audit job (lines 230-239): contents: read, actions: write — the
      explicitly documented exception.
    • release.yml publish-vscode job: no job-level permissions: block, inherits the workflow's
      top-level contents: read (no write).

Automated Maintainer (tend)

  1. PASS — Ruleset Merge access (id 16757376, GH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/rulesets/16757376): target: branch, conditions.ref_name.include: ["~DEFAULT_BRANCH"], rules: [{"type":"update"}], bypass_actors: [{"actor_id":5, "actor_type":"RepositoryRole","bypass_mode":"exempt"}] — sole bypass actor is admin (role id 5).
  2. PASS — Ruleset Tag operations (id 16757382): target: tag, conditions.ref_name.include: ["~ALL"], rules: [{"type":"creation"},{"type":"update"}], same sole admin-only bypass actor.
  3. PASSGH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/collaborators/dormouse-bot/permission
    {"permission":"write","role_name":"write"} — exactly push/write, not higher.
  4. PASSGH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/actions/secrets → repo-level
    secrets are CHROMATIC_PROJECT_TOKEN, CLAUDE_CODE_OAUTH_TOKEN, TEND_BOT_TOKEN only. Neither
    OVSX_PAT nor VSCE_PAT present at repo level.
  5. PASS — Environments (gh api repos/diffplug/dormouse/environments): only security-audit
    and vscode-extension-publish exist.
    • security-audit deployment-branch-policies: main (branch) + v* (tag) — both admin-gated
      (Merge access / Tag operations).
    • vscode-extension-publish deployment-branch-policies: v* (tag) only — admin-gated
      (Tag operations).
  6. PASSAUDIT_PAT is present in the security-audit environment secrets
    (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. PASS.github/workflows/workflow-audit.yaml exists, is not disabled (on: schedule +
    workflow_dispatch), and its most recent run
    (gh api repos/diffplug/dormouse/actions/workflows/workflow-audit.yaml/runs) completed
    success at 2026-07-19T09:13:32Z, ~22h before this audit (date -u2026-07-20T07:32:25Z),
    well inside 48h.
  9. PASS — All tend-*.yaml action refs are tag-pinned (actions/checkout@v7,
    max-sixty/tend/claude@0.1.11) — none use @main or an unpinned ref. Tag pins are explicitly
    accepted in this file class.
  10. FAIL.github/workflows/tend-mention.yaml's verify job (lines 34-205) declares no
    permissions: block (only the handle job, line 213, has one; grep confirms no top-level
    permissions: key exists in the file either). The repo's default Actions token permission is
    write (GH_TOKEN=$AUDIT_PAT gh api repos/diffplug/dormouse/actions/permissions/workflow
    {"default_workflow_permissions":"write","can_approve_pull_request_reviews":true}). That default
    grants the job's ambient GITHUB_TOKEN write access to every scope (contents, issues,
    pull-requests, actions, checks, deployments, packages, security-events, statuses, …), which
    exceeds the allowed set (contents: write, pull-requests: write, issues: write,
    id-token: write, actions: read, or any read). See Qualitative findings for exploitability
    discussion.

VS Code Extension Releases

  1. PASSrelease.yml:294-296publish-vscode job has environment: {name: vscode-extension-publish}.
  2. PASSgrep -rn 'VSCE_PAT|OVSX_PAT' .github/workflows matched only
    release.yml:325,337, both inside the publish-vscode job (the only job with the
    vscode-extension-publish environment).
  3. PASS — No production desktop signing secret names (APPLE_SIGN_PASS, EV_SIGN_PIN,
    TAURI_SIGNING_PRIVATE_KEY from a repo/org secret) appear in release.yml; grep -n 'secrets\.' release.yml returns only GITHUB_TOKEN, VSCE_PAT, OVSX_PAT.
  4. PASSrelease.yml:63-74 ("Generate ephemeral Tauri updater key") runs tauri signer generate --ci --write-keys ... --force for every CI build; no persistent/production updater key
    is injected.

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
    before any signing proceeds.
  2. PASSscripts/sign-and-deploy.sh:401-403 calls check_sha256_manifest (sha256sum -c /
    shasum -a 256 -c) after attestation verification.
  3. PASSscripts/sign-and-deploy.sh:683-718 (sign_windows) uses jsign --storetype PIV.

CI Validation Contract

  1. PASS.github/workflows/security-audit.yaml exists, is enabled (schedule +
    workflow_dispatch), and release.yml's publish-vscode job has needs: [..., security-audit]
    (line 290-293), with the security-audit job (227-286) dispatching and gating on this workflow.
  2. PASSsecurity-audit.yaml still requires the qualitative pass (prompt lines 90-93),
    treats the FAIL IF list as non-exhaustive (lines 83-85), keeps the failure-reporting step that
    opens/updates a security-audit-failure issue and exits non-zero (lines 123-172), and keeps the
    AUDIT_PAT pre-check that fails the job if the secret is absent (lines 47-55).

Qualitative findings

  • BLOCKER.github/workflows/tend-mention.yaml, verify job (no permissions: block,
    lines 34-205). This job runs on issues (edited), issue_comment (created/edited),
    pull_request_review (submitted), and pull_request_review_comment (edited) — all
    attacker-influenceable triggers per SECURITY.md's own "Prompt-injection through user-supplied
    content" threat model. Because the repo's default_workflow_permissions is write, this job's
    ambient GITHUB_TOKEN carries full write scope across contents/issues/PRs/actions/checks/
    deployments/packages/security-events/statuses. In the current file, every authenticated step in
    this job explicitly overrides GITHUB_TOKEN with secrets.TEND_BOT_TOKEN for its own gh calls,
    so the ambient token is not consumed today — but it still exists as a live, over-scoped credential
    in the runner for the job's duration, contradicts the least-privilege pattern every sibling job in
    this repo follows (all other jobs, including handle in the same file, declare an explicit
    permissions: block), and would silently regrant broad write access to any future step added to
    this job (or to any action/dependency it pulls in) without anyone touching a permissions: line.
    Fix: add an explicit permissions: {contents: read} (or the minimal read set the job actually
    needs) to the verify job, matching the pattern used everywhere else in this repo. Note this file
    is machine-generated by tend (# Do not edit this file directly); the fix belongs upstream in
    the tend-ci-runner plugin's job template, or the regeneration needs a local override mechanism.
  • INFO.github/workflows/chromatic.yml:39 pins chromaui/action by full commit SHA
    (94713c544284a14195de3b50ef24301579f1877e) but trails it with a stale # latest comment instead
    of a version number. Not a security issue (the ref is a real SHA pin, satisfying the "pinned by
    commit hash" rule) — just a misleading comment for future readers/Renovate diffs.
  • INFO — Reviewed all run: steps across every workflow for classic GitHub Actions script
    injection (untrusted ${{ github.event.* }} interpolated directly into a shell string). Found
    none: tend-mention.yaml and all other workflows correctly route event-derived strings (comment
    bodies, issue titles, URLs) through env: maps and reference them as shell variables
    ($COMMENT_BODY, etc.), which is the safe pattern. No action needed.
  • INFOvscode-extension-publish environment's required_reviewers protection rule lists two
    named human reviewers (nedtwigg, edgartwigg) with prevent_self_review: true, matching
    SECURITY.md's description that the publish approval "can't be the same account which triggered the
    publish." Confirmed via gh api repos/diffplug/dormouse/environments.
  • INFOpnpm-workspace.yaml's allowBuilds disables native postinstall scripts for
    @vscode/vsce-sign and keytar while allowing them for @swc/core, esbuild, node-pty,
    sharp — a deliberate, narrow allowlist consistent with the "small dependency surface" posture in
    SECURITY.md §Dependency Supply Chain. No action needed.

Summary

FAIL. All 23 previously-passing FAIL IF checks that could be mechanically verified with the
provided tokens remain PASS, including every ruleset, environment, secret-placement, and signing
check that requires $AUDIT_PAT. However, one FAIL IF is newly violated: the verify job in
.github/workflows/tend-mention.yaml — a job triggered by attacker-influenceable events (issue and
PR comments) — has no explicit permissions: block, and the repository's default_workflow_permissions
is write, so that job's GITHUB_TOKEN silently carries full write scope across every permission
category instead of the bounded set SECURITY.md requires for agent-managed workflows. No currently-
written step in the job spends that ambient token (all authenticated calls use the separately-scoped
TEND_BOT_TOKEN instead), so there is no active exploit path today, but the drift from the repo's
own declared policy is real, mechanically confirmed, and sits exactly on the threat surface
SECURITY.md calls out for this bot. This alone is sufficient to fail the audit per the "FAIL IF any
FAIL IF is violated" rule in the CI Validation Contract.

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