From c53b8f4bea0b3f8039113d7ed40b8d054bd355e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Fiere?= Date: Mon, 29 Jun 2026 20:09:22 +0200 Subject: [PATCH] ci: cargo audit + SHA-pin all GHA action refs + enforcement lint (#1539) --- .github/scripts/lint-action-pinning.py | 70 ++++++++++++++ .github/workflows/backend-tests.yml | 16 ++-- .github/workflows/cargo-audit.yml | 66 +++++++++++++ .github/workflows/ci-base-image.yml | 10 +- .github/workflows/claude-code-review.yml | 10 +- .github/workflows/claude.yml | 4 +- .github/workflows/e2e-tests.yml | 54 +++++------ .github/workflows/frontend-tests.yml | 4 +- .github/workflows/lint-action-pinning.yml | 35 +++++++ .github/workflows/opencode-code-review.yml | 10 +- .github/workflows/pr-title.yml | 2 +- .github/workflows/preview-env.yml | 22 ++--- .github/workflows/release.yml | 106 ++++++++++----------- .github/workflows/universal-rebuild.yml | 10 +- apps/desktop/src-tauri/.cargo/audit.toml | 26 +++++ 15 files changed, 322 insertions(+), 123 deletions(-) create mode 100755 .github/scripts/lint-action-pinning.py create mode 100644 .github/workflows/cargo-audit.yml create mode 100644 .github/workflows/lint-action-pinning.yml create mode 100644 apps/desktop/src-tauri/.cargo/audit.toml diff --git a/.github/scripts/lint-action-pinning.py b/.github/scripts/lint-action-pinning.py new file mode 100755 index 0000000000..a481a19d94 --- /dev/null +++ b/.github/scripts/lint-action-pinning.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Verify every `uses:` ref in .github/workflows/ is pinned to a 40-char commit SHA. + +Exits 0 when all refs are pinned, 1 when any violation is found. +Emits GitHub Actions `::error::` annotations so failures appear inline in PRs. + +Usage (locally): python3 .github/scripts/lint-action-pinning.py +Usage (CI): same — no arguments needed. +""" + +import re +import sys +from pathlib import Path + +# A valid pinned ref is exactly 40 lowercase hex characters. +SHA_RE = re.compile(r"^[0-9a-f]{40}$") + +# Matches a `uses:` step line, capturing everything after `uses:` up to an +# optional trailing YAML comment. The outer group is intentionally lazy so +# trailing `# tag` comments are consumed by the optional group rather than +# included in the captured value. Expression-based refs like +# `${{ inputs.ref }}` contain spaces and are captured in full because +# the lazy match expands until the optional comment group can anchor at `$`. +USES_RE = re.compile(r"^\s*-?\s*uses:\s+(.+?)(?:\s+#.*)?$") + +workflows_dir = Path(__file__).parent.parent / "workflows" + +violations: list[str] = [] + +for path in sorted(p for ext in ("*.yml", "*.yaml") for p in workflows_dir.glob(ext)): + for lineno, line in enumerate(path.read_text().splitlines(), start=1): + m = USES_RE.match(line) + if not m: + continue + + # Split on the last `@` to separate the action name from its ref. + # rpartition returns ("", "", value) when `@` is absent. + uses_value = m.group(1).strip() + action, sep, ref = uses_value.rpartition("@") + if not sep: + # No `@` — local action (`./...`) without a pin; skip. + continue + + # Local reusable workflows: uses: ./.github/workflows/foo.yml@ref + # These reference local content with no external registry. + if action.startswith("./"): + continue + + # Docker container actions: uses: docker://image@sha256:digest + # The ref after `@` is a registry digest, not a commit SHA. + if action.startswith("docker://"): + continue + + if not SHA_RE.match(ref): + rel = path.relative_to(Path(__file__).parent.parent.parent) + violations.append((str(rel), lineno, line.strip())) + +if violations: + for file, lineno, text in violations: + # GitHub Actions annotation — shown inline on the PR diff. + print(f"::error file={file},line={lineno}::Unpinned action ref (use a 40-char commit SHA): {text}") + print( + f"\n{len(violations)} unpinned ref(s) found. " + "Pin each `uses:` to a commit SHA and keep the version tag as a comment:\n" + " uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6", + file=sys.stderr, + ) + sys.exit(1) + +print(f"✓ All {sum(1 for ext in ('*.yml', '*.yaml') for _ in workflows_dir.glob(ext))} workflow file(s) use SHA-pinned action refs.") diff --git a/.github/workflows/backend-tests.yml b/.github/workflows/backend-tests.yml index 477d38e339..df5036d893 100644 --- a/.github/workflows/backend-tests.yml +++ b/.github/workflows/backend-tests.yml @@ -49,7 +49,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 @@ -68,7 +68,7 @@ jobs: # so this condition is dormant). If homelab is reactivated, split this # job into a matrix where the homelab variant omits `container:`. if: ${{ contains(runner.name, 'homelab') }} - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/go/pkg/mod @@ -137,13 +137,13 @@ jobs: go test -v -race -coverprofile=coverage.out -covermode=atomic -json ./... 2>&1 | tee test-results.json - name: Generate test report - uses: robherley/go-test-action@v0 + uses: robherley/go-test-action@3856e4e57831b3b6d6d8d89f91747e5200c533f7 # v0 if: always() with: fromJSONFile: apps/backend/test-results.json - name: Upload test artifacts - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 if: always() with: name: test-results @@ -162,7 +162,7 @@ jobs: container: ghcr.io/kdlbs/kandev-ci:build-latest services: postgres: - image: postgres:16 + image: postgres:16@sha256:fe03a7605299a34ddf5e4f285dff78c3d7190a576b3c6b46f2fcff69f4bffd54 env: POSTGRES_USER: kandev POSTGRES_PASSWORD: kandev @@ -181,7 +181,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false @@ -226,10 +226,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version-file: apps/backend/go.mod cache-dependency-path: apps/backend/go.sum diff --git a/.github/workflows/cargo-audit.yml b/.github/workflows/cargo-audit.yml new file mode 100644 index 0000000000..0a002dcd58 --- /dev/null +++ b/.github/workflows/cargo-audit.yml @@ -0,0 +1,66 @@ +name: Cargo Audit + +on: + push: + branches: [main] + paths: + - "apps/desktop/src-tauri/**" + - ".github/workflows/cargo-audit.yml" + - "!**/*.md" + - "!**/*.mdx" + - "!**/*.markdown" + pull_request: + branches: [main] + paths: + - "apps/desktop/src-tauri/**" + - ".github/workflows/cargo-audit.yml" + - "!**/*.md" + - "!**/*.mdx" + - "!**/*.markdown" + schedule: + # Run weekly on Mondays at 06:00 UTC to catch newly published advisories + # even when no Cargo files have changed. + - cron: "0 6 * * 1" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Least-privilege: only reads source; no PR/issue writes or release ops. +permissions: + contents: read + +jobs: + audit: + name: Cargo Audit + runs-on: ubuntu-latest + defaults: + run: + working-directory: apps/desktop/src-tauri + + steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@67ef31d5b988238dd797d409d6f9574278e20537 # master + with: + toolchain: stable + + - name: Cache cargo registry + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: cargo-audit-${{ runner.os }}-${{ hashFiles('apps/desktop/src-tauri/Cargo.lock') }} + restore-keys: cargo-audit-${{ runner.os }}- + + - name: Install cargo-audit + run: cargo install cargo-audit --version 0.22.2 --locked + + - name: Run cargo audit + run: cargo audit diff --git a/.github/workflows/ci-base-image.yml b/.github/workflows/ci-base-image.yml index 33befb83e3..9683796d2d 100644 --- a/.github/workflows/ci-base-image.yml +++ b/.github/workflows/ci-base-image.yml @@ -31,10 +31,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Set up Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 # Push the built images to GHCR on any `push` to main (the default # publish path) or on manual `workflow_dispatch` (used to seed new tag @@ -45,7 +45,7 @@ jobs: if: | github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/main') - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -71,7 +71,7 @@ jobs: # Consumed by the `e2e` shards and `e2e-report`, where the smaller pull # is the speedup that pays for the split. - name: Build and push runtime - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: .github/docker/ci-base/Dockerfile @@ -89,7 +89,7 @@ jobs: # `cache-from` includes the runtime scope so the shared lower layers # only build once across both pushes. - name: Build and push build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: .github/docker/ci-base/Dockerfile diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 2785aa5f03..7da44288e6 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -41,13 +41,13 @@ jobs: steps: - name: Checkout PR head - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 - name: Run Claude Code Review (App token, inline comments) id: claude-review - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@78a7209fd893bd50d1b2b784da9b6f240a66373a # v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # No github_token: the action performs the OIDC -> installation-token @@ -90,7 +90,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | try { @@ -131,7 +131,7 @@ jobs: steps: - name: Checkout PR head - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: # pull_request_target defaults to the base ref; explicitly check out # the PR head through the base repository's pull ref. Keeping origin @@ -144,7 +144,7 @@ jobs: - name: Run Claude Code Review (GITHUB_TOKEN fallback) id: claude-review - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@78a7209fd893bd50d1b2b784da9b6f240a66373a # v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # Skip the Claude GitHub App's OIDC -> installation-token exchange, diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index d300267f18..a6f0866e15 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -26,13 +26,13 @@ jobs: actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 1 - name: Run Claude Code id: claude - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@78a7209fd893bd50d1b2b784da9b6f240a66373a # v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index dc5fff5cb9..8d71d3c2a0 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -53,7 +53,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false @@ -64,7 +64,7 @@ jobs: # /root/.local/share/pnpm/store. actions/cache resolves `~` against $HOME # so the same path string works for both the host and container variants. - name: Cache pnpm store - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} @@ -84,7 +84,7 @@ jobs: run: make build-backend-linux-helpers - name: Upload backend build - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: e2e-backend-build path: apps/backend/bin/ @@ -95,7 +95,7 @@ jobs: run: tar -cf apps/web/web-dist.tar -C apps/web dist - name: Upload web build - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: e2e-web-build path: apps/web/web-dist.tar @@ -138,7 +138,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false @@ -146,7 +146,7 @@ jobs: run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Cache pnpm store - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} @@ -157,7 +157,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Download backend build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: e2e-backend-build path: apps/backend/bin/ @@ -166,7 +166,7 @@ jobs: run: chmod +x apps/backend/bin/* - name: Download web build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: e2e-web-build path: apps/web/ @@ -186,7 +186,7 @@ jobs: - name: Upload blob report if: always() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: blob-report-${{ matrix.shard }} path: apps/web/e2e/blob-report/ @@ -194,7 +194,7 @@ jobs: - name: Upload test results if: failure() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: test-results-${{ matrix.shard }} path: apps/web/e2e/test-results/ @@ -230,7 +230,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false @@ -238,17 +238,17 @@ jobs: run: docker info - name: Set up pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" - name: Cache pnpm store - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} @@ -263,7 +263,7 @@ jobs: # need this because GitHub's `container:` integration handles auth # implicitly using GITHUB_TOKEN. - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -320,7 +320,7 @@ jobs: smoke_test - name: Download backend build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: e2e-backend-build path: apps/backend/bin/ @@ -329,7 +329,7 @@ jobs: run: chmod +x apps/backend/bin/* - name: Download web build - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: e2e-web-build path: apps/web/ @@ -353,7 +353,7 @@ jobs: # the sharded chromium/mobile-chrome runs. - name: Upload containers blob report if: always() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: blob-report-containers-${{ matrix.shard }} path: apps/web/e2e/blob-report/ @@ -361,7 +361,7 @@ jobs: - name: Upload containers E2E results if: failure() - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: containers-e2e-test-results-${{ matrix.shard }} path: apps/web/e2e/test-results/ @@ -377,17 +377,17 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false - name: Set up pnpm - uses: pnpm/action-setup@v5 + uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - name: Set up Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" cache: pnpm @@ -397,7 +397,7 @@ jobs: run: rustup toolchain install stable --profile minimal - name: Cache Rust build - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 with: workspaces: apps/desktop/src-tauri -> target key: desktop-e2e @@ -439,7 +439,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: persist-credentials: false @@ -447,7 +447,7 @@ jobs: run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Cache pnpm store - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} @@ -458,7 +458,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Download blob reports - uses: actions/download-artifact@v5 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: path: apps/web/e2e/blob-reports pattern: blob-report-* @@ -476,7 +476,7 @@ jobs: run: npx playwright merge-reports --config e2e/playwright.config.ts --reporter=html ./e2e/blob-reports - name: Upload merged report - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: playwright-report path: apps/web/playwright-report/ diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index b0d2bf5c61..9cc4fc0695 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -60,7 +60,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 @@ -70,7 +70,7 @@ jobs: run: git config --global --add safe.directory "$GITHUB_WORKSPACE" - name: Cache pnpm store - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.local/share/pnpm/store key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} diff --git a/.github/workflows/lint-action-pinning.yml b/.github/workflows/lint-action-pinning.yml new file mode 100644 index 0000000000..c8d0609e6f --- /dev/null +++ b/.github/workflows/lint-action-pinning.yml @@ -0,0 +1,35 @@ +name: Lint Action Pinning + +on: + push: + branches: [main] + paths: + - ".github/workflows/**" + - ".github/scripts/lint-action-pinning.py" + pull_request: + branches: [main] + paths: + - ".github/workflows/**" + - ".github/scripts/lint-action-pinning.py" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# Least-privilege: only reads source. +permissions: + contents: read + +jobs: + lint: + name: All action refs are SHA-pinned + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 + with: + persist-credentials: false + + - name: Verify action refs + run: python3 .github/scripts/lint-action-pinning.py diff --git a/.github/workflows/opencode-code-review.yml b/.github/workflows/opencode-code-review.yml index a4c642bcc5..42d42a0172 100644 --- a/.github/workflows/opencode-code-review.yml +++ b/.github/workflows/opencode-code-review.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout PR head - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 @@ -215,7 +215,7 @@ jobs: - name: Upload OpenCode review artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: opencode-review-same-repo-${{ github.event.pull_request.number }} path: .opencode-review/ @@ -234,7 +234,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | try { @@ -270,7 +270,7 @@ jobs: steps: - name: Checkout PR head - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: # pull_request_target checks out the base branch by default; explicitly # check out the contributor's head via the base repository PR ref. @@ -454,7 +454,7 @@ jobs: - name: Upload OpenCode review artifacts if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: opencode-review-fork-${{ github.event.pull_request.number }} path: .opencode-review/ diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml index 93a3cf0cd2..fecc6897f6 100644 --- a/.github/workflows/pr-title.yml +++ b/.github/workflows/pr-title.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Validate PR title follows Conventional Commits - uses: amannn/action-semantic-pull-request@v5 + uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.github/workflows/preview-env.yml b/.github/workflows/preview-env.yml index 866b5a47a8..cbc4c6db23 100644 --- a/.github/workflows/preview-env.yml +++ b/.github/workflows/preview-env.yml @@ -35,20 +35,20 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version-file: apps/backend/go.mod cache-dependency-path: apps/backend/go.sum - - uses: pnpm/action-setup@v5 + - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" @@ -94,7 +94,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: # pull_request_target checks out base by default; explicitly use PR head. # Safe: the preview CLI only reads files to build artifacts and posts comments. @@ -104,16 +104,16 @@ jobs: repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version-file: apps/backend/go.mod cache-dependency-path: apps/backend/go.sum - - uses: pnpm/action-setup@v5 + - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" @@ -148,7 +148,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/github-script@v7 + - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | try { @@ -181,9 +181,9 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version-file: apps/backend/go.mod cache-dependency-path: apps/backend/go.sum diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 69a6bc1c4d..e2431501e9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -70,16 +70,16 @@ jobs: tag: ${{ steps.compute.outputs.tag }} ref: ${{ steps.compute.outputs.ref }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} - - uses: pnpm/action-setup@v5 + - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" @@ -307,14 +307,14 @@ jobs: if: ${{ !inputs.dry_run }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.ref }} persist-credentials: false - - uses: pnpm/action-setup@v5 + - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" cache: pnpm @@ -327,7 +327,7 @@ jobs: VITE_KANDEV_API_PORT: "" VITE_KANDEV_DEBUG: "" run: pnpm -C apps --filter @kandev/web build - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: web-bundle path: apps/web/dist @@ -367,15 +367,15 @@ jobs: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.ref }} persist-credentials: false - - uses: actions/setup-go@v6 + - uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6 with: go-version-file: apps/backend/go.mod cache-dependency-path: apps/backend/go.sum - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: web-bundle path: apps/backend/internal/webapp/embedded/generated @@ -438,7 +438,7 @@ jobs: tar -czf "kandev-${{ matrix.platform }}.tar.gz" kandev shasum -a 256 "kandev-${{ matrix.platform }}.tar.gz" > "kandev-${{ matrix.platform }}.tar.gz.sha256" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: bundle-${{ matrix.platform }} path: | @@ -477,16 +477,16 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.ref }} persist-credentials: false - - uses: pnpm/action-setup@v5 + - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" cache: pnpm @@ -496,7 +496,7 @@ jobs: run: rustup toolchain install stable --profile minimal --target "${{ matrix.rust_target }}" - name: Cache Rust build - uses: Swatinem/rust-cache@v2 + uses: Swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 with: workspaces: apps/desktop/src-tauri -> target key: desktop-${{ matrix.platform }}-${{ matrix.rust_target }} @@ -517,7 +517,7 @@ jobs: - name: Install dependencies run: pnpm -C apps install --frozen-lockfile - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: name: bundle-${{ matrix.platform }} path: dist/desktop-runtime @@ -787,7 +787,7 @@ jobs: scripts/release/verify-desktop-assets.sh "$out_dir" "${{ matrix.platform }}" ls -lh "$out_dir" - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5 with: name: desktop-${{ matrix.platform }} path: dist/desktop-artifacts/* @@ -811,11 +811,11 @@ jobs: env: STAGING_IMAGE: ghcr.io/kdlbs/kandev:base-staging-${{ needs.prepare.outputs.tag }}-${{ github.run_id }}-amd64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.tag }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: bundle-linux-x64 path: dist/bundle @@ -829,10 +829,10 @@ jobs: tar -xzf dist/bundle/kandev-linux-x64.tar.gz --strip-components=1 -C ctx/bundle cp docker-entrypoint.sh ctx/ - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -845,13 +845,13 @@ jobs: # page link + provenance tooling rely on these annotations. - name: Docker metadata (labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/kdlbs/kandev - name: Build and push (amd64 staging tag) id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: ./ctx file: ./Dockerfile @@ -882,11 +882,11 @@ jobs: env: STAGING_IMAGE: ghcr.io/kdlbs/kandev:base-staging-${{ needs.prepare.outputs.tag }}-${{ github.run_id }}-arm64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ needs.prepare.outputs.tag }} - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: bundle-linux-arm64 path: dist/bundle @@ -898,10 +898,10 @@ jobs: tar -xzf dist/bundle/kandev-linux-arm64.tar.gz --strip-components=1 -C ctx/bundle cp docker-entrypoint.sh ctx/ - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -909,13 +909,13 @@ jobs: - name: Docker metadata (labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/kdlbs/kandev - name: Build and push (arm64 staging tag) id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: ./ctx file: ./Dockerfile @@ -939,7 +939,7 @@ jobs: steps: - name: Docker meta id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/kdlbs/kandev tags: | @@ -948,10 +948,10 @@ jobs: type=sha type=raw,value=latest - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -995,14 +995,14 @@ jobs: env: STAGING_IMAGE: ghcr.io/kdlbs/kandev:universal-staging-${{ needs.prepare.outputs.tag }}-${{ github.run_id }}-amd64 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.tag }} - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -1010,13 +1010,13 @@ jobs: - name: Docker metadata (labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/kdlbs/kandev - name: Build and push (universal amd64 staging tag) id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: Dockerfile.universal @@ -1047,14 +1047,14 @@ jobs: env: STAGING_IMAGE: ghcr.io/kdlbs/kandev:universal-staging-${{ needs.prepare.outputs.tag }}-${{ github.run_id }}-arm64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ needs.prepare.outputs.tag }} - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -1062,13 +1062,13 @@ jobs: - name: Docker metadata (labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ghcr.io/kdlbs/kandev - name: Build and push (universal arm64 staging tag) id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: Dockerfile.universal @@ -1097,10 +1097,10 @@ jobs: AMD64_REF: ghcr.io/kdlbs/kandev@${{ needs.docker-universal-amd64.outputs.digest }} ARM64_REF: ghcr.io/kdlbs/kandev@${{ needs.docker-universal-arm64.outputs.digest }} steps: - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -1150,13 +1150,13 @@ jobs: permissions: contents: write # softprops/action-gh-release creates the release steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.tag }} fetch-depth: 0 - name: Generate release notes - uses: orhun/git-cliff-action@v4 + uses: orhun/git-cliff-action@f50e11560dce63f7c33227798f90b924471a88b5 # v4 with: config: cliff.toml args: --latest --strip header @@ -1205,13 +1205,13 @@ jobs: } > "$tmp" mv "$tmp" RELEASE_NOTES.md - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: pattern: bundle-* path: dist/release-assets merge-multiple: true - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5 with: pattern: desktop-* path: dist/release-assets @@ -1237,7 +1237,7 @@ jobs: ls -lh dist/release-assets - name: Publish release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: ${{ needs.prepare.outputs.tag }} name: ${{ needs.prepare.outputs.tag }} @@ -1256,15 +1256,15 @@ jobs: id-token: write # Required for OIDC trusted publishing contents: read steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.tag }} - - uses: pnpm/action-setup@v5 + - uses: pnpm/action-setup@a8198c4bff370c8506180b035930dea56dbd5288 # v5 with: version: "9.15.9" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" registry-url: "https://registry.npmjs.org" @@ -1288,7 +1288,7 @@ jobs: if: ${{ !inputs.dry_run && !inputs.desktop_validation_only }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ needs.prepare.outputs.tag }} diff --git a/.github/workflows/universal-rebuild.yml b/.github/workflows/universal-rebuild.yml index b34e23a38d..bccd13acc6 100644 --- a/.github/workflows/universal-rebuild.yml +++ b/.github/workflows/universal-rebuild.yml @@ -46,16 +46,16 @@ jobs: env: STAGING_IMAGE: ghcr.io/kdlbs/kandev:universal-staging-weekly-${{ github.run_id }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 - name: Compute date tag id: date run: echo "tag=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT" - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Login to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -66,13 +66,15 @@ jobs: # current :latest is freshly fetched, not whatever's in BuildKit's # cache from a previous run. - name: Build and push (staging tag) - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: Dockerfile.universal push: true pull: true platforms: linux/amd64,linux/arm64 + # BASE_IMAGE is intentionally unpinned: the whole point of this + # weekly job is to pick up the current :latest on each run. build-args: | BASE_IMAGE=ghcr.io/kdlbs/kandev:latest tags: ${{ env.STAGING_IMAGE }} diff --git a/apps/desktop/src-tauri/.cargo/audit.toml b/apps/desktop/src-tauri/.cargo/audit.toml new file mode 100644 index 0000000000..459f717f23 --- /dev/null +++ b/apps/desktop/src-tauri/.cargo/audit.toml @@ -0,0 +1,26 @@ +# Temporary advisory ignores for cargo-audit. +# Each entry must have a tracking note. Remove the entry once the fix lands. + +[advisories] +ignore = [ + # tauri 2.8.4 — IPC origin confusion on Windows (CVE-2026-42184, CVSS 8.8). + # Remote page in WebView can invoke local-only Tauri commands (incl. fs access). + # Fix: upgrade tauri to >= 2.11.1. + "GHSA-7gmj-67g7-phm9", + + # time 0.3.41 — stack exhaustion via malformed RFC 2822 input (CVE-2026-25727, CVSS 6.9). + # Limited attack surface in a local desktop app; no known reachable path via Tauri shell. + # Fix: upgrade time to >= 0.3.47. + "RUSTSEC-2026-0009", + + # rand 0.7.3 — memory unsoundness; UB only when all three hold simultaneously: + # `log` feature enabled + custom logger installed + rand::rng() called. + # That combination is not present in the kandev desktop shell. + # Fix: upgrade rand to >= 0.8.6 (semver-breaking; requires coordinating dependents). + "RUSTSEC-2026-0097", + + # glib 0.18.5 — unsound VariantStrIter::impl_get, Linux-only via webkit2gtk chain. + # No known exploit in the wild; will resolve when Tauri migrates to webkit2gtk-4.1 + # which pulls glib >= 0.20.0. + "RUSTSEC-2024-0429", +]