diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d98bf74 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +# Dependabot security updates only. Version updates stay manual: open-pull-requests-limit: 0 +# turns them off, while security updates still open PRs for every new alert. +# cooldown mirrors pnpm's minimumReleaseAge (24 h). GitHub applies it to version updates only, +# so it does not delay security PRs: check the fixed version's age before merging one. +version: 2 +updates: + - package-ecosystem: npm + directories: + - "/" + schedule: + interval: weekly + open-pull-requests-limit: 0 + cooldown: + default-days: 1 + groups: + security: + applies-to: security-updates + patterns: + - "*" + + - package-ecosystem: github-actions + directories: + - "/" + schedule: + interval: weekly + open-pull-requests-limit: 0 + cooldown: + default-days: 1 + groups: + security: + applies-to: security-updates + patterns: + - "*" diff --git a/.github/workflows/dependency-audit.yml b/.github/workflows/dependency-audit.yml new file mode 100644 index 0000000..11e9619 --- /dev/null +++ b/.github/workflows/dependency-audit.yml @@ -0,0 +1,85 @@ +name: Dependency audit + +# Weekly audit of every lockfile in the repo. It is not a pull_request check, so a new +# advisory never blocks unrelated PRs: it opens one issue (label `dependency-audit`), +# comments on it while findings remain, and closes it once the audit comes back clean. +on: + schedule: + - cron: "0 9 * * 1" + workflow_dispatch: + +permissions: {} + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +jobs: + audit: + name: Audit dependencies + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + issues: write + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: 🛠️ Setup mise + uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 + with: + install: true + cache: true + + # Each audit command exits non-zero when it finds advisories (or cannot reach the + # registry); both end up in the issue so a person looks at them. + - name: 🔍 Audit lockfiles + id: audit + run: | + report="$RUNNER_TEMP/audit.md" + : > "$report" + found=false + audit() { + local label="$1" + shift + local out + if ! out="$("$@" 2>&1)"; then + found=true + { + echo "### \`$label\`" + echo + echo '```' + printf '%s\n' "${out:0:15000}" + echo '```' + echo + } >> "$report" + fi + } + # REPO-SPECIFIC: one line per lockfile. + audit "pnpm-lock.yaml" pnpm audit + echo "found=$found" >> "$GITHUB_OUTPUT" + + - name: 📝 Open, update or close the audit issue + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + FOUND: ${{ steps.audit.outputs.found }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + label="dependency-audit" + gh label create "$label" --color B60205 --description "Opened by the weekly dependency audit" --force > /dev/null + number="$(gh issue list --label "$label" --state open --limit 1 --json number --jq '.[0].number // empty')" + if [ "$FOUND" = "true" ]; then + body="$RUNNER_TEMP/body.md" + { head -c 60000 "$RUNNER_TEMP/audit.md"; echo; echo "Run: $RUN_URL"; } > "$body" + if [ -n "$number" ]; then + gh issue comment "$number" --body-file "$body" + else + gh issue create --title "Dependency audit found known vulnerabilities" --label "$label" --body-file "$body" + fi + elif [ -n "$number" ]; then + gh issue close "$number" --comment "The weekly dependency audit came back clean: $RUN_URL" + fi diff --git a/.github/zizmor.yml b/.github/zizmor.yml index f936e61..0837e49 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -5,3 +5,7 @@ rules: # First-party withvariablelab/gh-actions is tracked at @main on purpose. "withvariablelab/*": ref-pin "*": hash-pin + dependabot-cooldown: + config: + # Matches pnpm's minimumReleaseAge (1440 minutes) in pnpm-workspace.yaml. + days: 1