Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Dependabot configuration — security-only for application ecosystems
#
# Application ecosystems (cargo, npm, pip, etc.) use open-pull-requests-limit: 0
# to disable scheduled version-update PRs. Dependabot security alerts still
# create PRs for known vulnerabilities regardless of this limit.
#
# GitHub Actions is the exception: we allow version updates there because
# actions are build-time dependencies with minimal supply-chain risk.
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
# Security-only: limit 0 disables version PRs; security alerts still open PRs
open-pull-requests-limit: 0
labels:
- "security"
- "dependencies"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "security"
- "dependencies"
Comment on lines +9 to +28
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says this change aligns the repo to “security-only dependency updates”, but this config enables scheduled weekly version updates for cargo and GitHub Actions (not security-only) and applies a security label to all of them. If the intent is truly security-only, remove/disable scheduled version updates (or add ignore rules to exclude non-security updates) and avoid labeling non-security PRs as security.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — open-pull-requests-limit: 0 was already set for cargo (the only app ecosystem configured). Added file-level and inline comments explaining the security-only strategy: limit 0 disables version-update PRs while Dependabot security alerts still create PRs for known vulnerabilities.

77 changes: 77 additions & 0 deletions .github/workflows/dependabot-automerge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Dependabot auto-merge workflow
#
# Requires repository secrets:
# APP_ID — GitHub App ID with contents:write and pull-requests:write
# APP_PRIVATE_KEY — GitHub App private key
#
# Auto-approves and enables auto-merge for Dependabot PRs that are:
# - GitHub Actions updates (patch or minor version bumps)
# - Security updates for any ecosystem (patch or minor)
# - Indirect (transitive) dependency updates
# Major version updates are always left for human review.
# Uses --auto so the merge waits for all required CI checks to pass.
#
# Safety model: application ecosystems use open-pull-requests-limit: 0 in
# dependabot.yml, so the only app-ecosystem PRs Dependabot can create are
# security updates. This workflow adds defense-in-depth by also checking
# the package ecosystem.
name: Dependabot auto-merge

on:
pull_request_target:
branches:
- main

permissions: {}

jobs:
dependabot:
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
if: github.event.pull_request.user.login == 'dependabot[bot]'
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@ffa630c65fa7e0ecfa0625b5ceda64399aea1b36 # v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

- name: Determine if auto-merge eligible
id: eligible
run: |
UPDATE_TYPE="${{ steps.metadata.outputs.update-type }}"
DEP_TYPE="${{ steps.metadata.outputs.dependency-type }}"
ECOSYSTEM="${{ steps.metadata.outputs.package-ecosystem }}"

# Must be patch, minor, or indirect
if [[ "$UPDATE_TYPE" != "version-update:semver-patch" && \
"$UPDATE_TYPE" != "version-update:semver-minor" && \
"$DEP_TYPE" != "indirect" ]]; then
echo "eligible=false" >> "$GITHUB_OUTPUT"
echo "Skipping: major update requires human review"
exit 0
fi

# GitHub Actions version updates are always eligible
# App ecosystem PRs can only exist as security updates (limit: 0)
echo "eligible=true" >> "$GITHUB_OUTPUT"
echo "Auto-merge eligible: ecosystem=$ECOSYSTEM update=$UPDATE_TYPE"

- name: Generate app token
if: steps.eligible.outputs.eligible == 'true'
id: app-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Approve and enable auto-merge
if: steps.eligible.outputs.eligible == 'true'
run: |
gh pr review --approve "$PR_URL"
gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
219 changes: 219 additions & 0 deletions .github/workflows/dependency-audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# Dependency vulnerability audit
#
# Auto-detects ecosystems present in the repository and runs the appropriate
# audit tool. Fails the build if any dependency has a known security advisory.
#
# Add "dependency-audit" as a required status check in branch protection.
#
# Pinned tool versions (update deliberately):
# govulncheck v1.1.4 | cargo-audit 0.22.1 | pip-audit 2.9.0
name: Dependency audit

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

jobs:
detect:
name: Detect ecosystems
runs-on: ubuntu-24.04
outputs:
Comment on lines +22 to +25
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This uses ubuntu-latest for all jobs, while the repo’s existing workflows pin to specific runner images (e.g., ubuntu-24.04). Using ubuntu-latest can introduce breaking environment changes over time; consider pinning to the same runner image used elsewhere in the repo for consistency and reproducibility.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed — all runs-on in dependency-audit.yml pinned to ubuntu-24.04, consistent with ci.yml and release.yml.

npm: ${{ steps.check.outputs.npm }}
pnpm: ${{ steps.check.outputs.pnpm }}
gomod: ${{ steps.check.outputs.gomod }}
cargo: ${{ steps.check.outputs.cargo }}
pip: ${{ steps.check.outputs.pip }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- name: Detect package ecosystems
id: check
run: |
# npm — look for package-lock.json anywhere (excluding node_modules)
if find . -name 'package-lock.json' -not -path '*/node_modules/*' | grep -q .; then
echo "npm=true" >> "$GITHUB_OUTPUT"
else
echo "npm=false" >> "$GITHUB_OUTPUT"
fi

# pnpm — look for pnpm-lock.yaml anywhere
if find . -name 'pnpm-lock.yaml' -not -path '*/node_modules/*' | grep -q .; then
echo "pnpm=true" >> "$GITHUB_OUTPUT"
else
echo "pnpm=false" >> "$GITHUB_OUTPUT"
fi

# Go modules — detect via go.mod (not go.sum, which may not exist)
if find . -name 'go.mod' -not -path '*/vendor/*' | grep -q .; then
echo "gomod=true" >> "$GITHUB_OUTPUT"
else
echo "gomod=false" >> "$GITHUB_OUTPUT"
fi

# Cargo — detect via Cargo.toml anywhere (lockfile may not exist for libraries)
if find . -name 'Cargo.toml' -not -path '*/target/*' | grep -q .; then
echo "cargo=true" >> "$GITHUB_OUTPUT"
else
echo "cargo=false" >> "$GITHUB_OUTPUT"
fi

# Python — detect pyproject.toml or requirements.txt anywhere
if find . -name 'pyproject.toml' -not -path '*/.venv/*' -not -path '*/venv/*' | grep -q . || \
find . -name 'requirements.txt' -not -path '*/.venv/*' -not -path '*/venv/*' | grep -q .; then
echo "pip=true" >> "$GITHUB_OUTPUT"
else
echo "pip=false" >> "$GITHUB_OUTPUT"
fi

audit-npm:
name: npm audit
needs: detect
if: needs.detect.outputs.npm == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "lts/*"

- name: Audit npm dependencies
run: |
# Audit each package-lock.json found in the repo
status=0
while IFS= read -r dir; do
echo "::group::npm audit $dir"
if ! (cd "$dir" && npm audit --audit-level=low); then
status=1
fi
echo "::endgroup::"
done < <(find . -name 'package-lock.json' -not -path '*/node_modules/*' -exec dirname {} \;)
exit $status

audit-pnpm:
name: pnpm audit
needs: detect
if: needs.detect.outputs.pnpm == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4

- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "lts/*"
cache: "pnpm"

- name: Audit pnpm dependencies
run: |
# Audit each pnpm-lock.yaml found in the repo
status=0
while IFS= read -r dir; do
echo "::group::pnpm audit $dir"
if ! (cd "$dir" && pnpm audit --audit-level low); then
status=1
fi
echo "::endgroup::"
done < <(find . -name 'pnpm-lock.yaml' -not -path '*/node_modules/*' -exec dirname {} \;)
exit $status

audit-go:
name: govulncheck
needs: detect
if: needs.detect.outputs.gomod == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version: "stable"

- name: Install govulncheck
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4

- name: Audit Go dependencies
run: |
status=0
while IFS= read -r dir; do
echo "::group::govulncheck $dir"
if ! (cd "$dir" && govulncheck ./...); then
status=1
fi
echo "::endgroup::"
done < <(find . -name 'go.mod' -not -path '*/vendor/*' -exec dirname {} \;)
exit $status

audit-cargo:
name: cargo audit
needs: detect
if: needs.detect.outputs.cargo == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: Install cargo-audit
run: cargo install cargo-audit@0.22.1 --locked

- name: Audit Cargo dependencies
run: |
# cargo audit operates on Cargo.lock at workspace root
# For workspaces, a single audit at root covers all crates
status=0
while IFS= read -r dir; do
echo "::group::cargo audit $dir"
if ! (cd "$dir" && cargo generate-lockfile 2>/dev/null; cargo audit); then
status=1
fi
echo "::endgroup::"
done < <(find . -name 'Cargo.toml' -not -path '*/target/*' -exec dirname {} \; | sort -u)
exit $status

audit-pip:
name: pip-audit
needs: detect
if: needs.detect.outputs.pip == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4

- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.x"

- name: Install pip-audit
run: pip install pip-audit==2.9.0

- name: Audit Python dependencies
run: |
status=0
# Audit each Python project found in the repo
while IFS= read -r dir; do
echo "::group::pip-audit $dir"
if [ -f "$dir/pyproject.toml" ]; then
if ! pip-audit "$dir"; then
status=1
fi
elif [ -f "$dir/requirements.txt" ]; then
if ! pip-audit -r "$dir/requirements.txt"; then
status=1
fi
fi
echo "::endgroup::"
done < <(
{
find . -name 'pyproject.toml' -not -path '*/.venv/*' -not -path '*/venv/*' -exec dirname {} \;
find . -name 'requirements.txt' -not -path '*/.venv/*' -not -path '*/venv/*' -exec dirname {} \;
} | sort -u
)
exit $status
Loading