From fab5e783a09ad772e01366e2d23da1c4f58f8887 Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-MacBook-Air.local>
Date: Fri, 3 Apr 2026 11:33:56 -0700
Subject: [PATCH 1/6] docs: add Dependabot security-only update standards

Define org-wide policy for dependency management: security-only updates
for application packages, weekly version updates for GitHub Actions,
mandatory labels, auto-merge workflow, and vulnerability audit CI check.

Includes templates for npm, Go, Rust, Python, Terraform, and full-stack
repositories.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 standards/dependabot-policy.md               | 152 +++++++++++++++++++
 standards/dependabot/backend-go.yml          |  23 +++
 standards/dependabot/backend-python.yml      |  23 +++
 standards/dependabot/backend-rust.yml        |  23 +++
 standards/dependabot/frontend.yml            |  23 +++
 standards/dependabot/fullstack.yml           |  48 ++++++
 standards/dependabot/infra-terraform.yml     |  23 +++
 standards/workflows/dependabot-automerge.yml |  57 +++++++
 standards/workflows/dependency-audit.yml     | 136 +++++++++++++++++
 9 files changed, 508 insertions(+)
 create mode 100644 standards/dependabot-policy.md
 create mode 100644 standards/dependabot/backend-go.yml
 create mode 100644 standards/dependabot/backend-python.yml
 create mode 100644 standards/dependabot/backend-rust.yml
 create mode 100644 standards/dependabot/frontend.yml
 create mode 100644 standards/dependabot/fullstack.yml
 create mode 100644 standards/dependabot/infra-terraform.yml
 create mode 100644 standards/workflows/dependabot-automerge.yml
 create mode 100644 standards/workflows/dependency-audit.yml

diff --git a/standards/dependabot-policy.md b/standards/dependabot-policy.md
new file mode 100644
index 00000000..b3c42305
--- /dev/null
+++ b/standards/dependabot-policy.md
@@ -0,0 +1,152 @@
+# Dependabot Policy: Security-Only Updates
+
+## Rationale
+
+New package versions pose risk to stability and security. Keeping known-good versions
+and upgrading only when vulnerabilities are found and fixed provides a better
+security posture than chasing every minor/patch release.
+
+## Policy
+
+1. **Security updates only** for application dependencies (npm, Go modules, pip, Cargo).
+   Dependabot opens PRs only when a vulnerability advisory exists for a dependency.
+2. **Version updates weekly** for GitHub Actions, since pinned action versions do not
+   affect application stability and staying current reduces CI attack surface.
+3. **Labels** `security` and `dependencies` on every Dependabot PR for filtering and audit.
+4. **Auto-merge** security patches and minor updates after CI passes, using a GitHub App
+   token to satisfy branch protection (CODEOWNERS review bypass for bot PRs).
+5. **Vulnerability audit CI check** runs on every PR and push to `main`, failing the
+   build if any dependency has a known advisory. This is a required status check.
+
+## Configuration Files
+
+Each repository must have:
+
+| File | Purpose |
+|------|---------|
+| `.github/dependabot.yml` | Dependabot config scoped to the repo's ecosystems |
+| `.github/workflows/dependabot-automerge.yml` | Auto-approve + squash-merge security PRs |
+| `.github/workflows/dependency-audit.yml` | CI check — fail on known vulnerabilities |
+
+## Dependabot Templates
+
+Use the template matching your repository type. All templates share these settings:
+
+```yaml
+# Common settings applied to every package ecosystem entry
+schedule:
+  interval: "weekly"
+open-pull-requests-limit: 10
+labels:
+  - "security"
+  - "dependencies"
+```
+
+### Frontend (npm)
+
+For repos with `package.json` at root or in subdirectories.
+
+- Ecosystem: `npm`
+- Directory: `/` (or path to each `package.json`)
+- Security updates only (no `versioning-strategy` override needed — Dependabot
+  security updates use the lockfile by default)
+
+See [`dependabot/frontend.yml`](dependabot/frontend.yml)
+
+### Backend — Go
+
+For repos with `go.mod`.
+
+- Ecosystem: `gomod`
+- Directory: path to `go.mod`
+
+See [`dependabot/backend-go.yml`](dependabot/backend-go.yml)
+
+### Backend — Rust
+
+For repos with `Cargo.toml`.
+
+- Ecosystem: `cargo`
+- Directory: `/`
+
+See [`dependabot/backend-rust.yml`](dependabot/backend-rust.yml)
+
+### Backend — Python
+
+For repos with `pyproject.toml` or `requirements.txt`.
+
+- Ecosystem: `pip`
+- Directory: `/`
+
+See [`dependabot/backend-python.yml`](dependabot/backend-python.yml)
+
+### Infrastructure — Terraform
+
+For repos with Terraform modules.
+
+- Ecosystem: `terraform`
+- Directory: path to Terraform root module
+
+See [`dependabot/infra-terraform.yml`](dependabot/infra-terraform.yml)
+
+### GitHub Actions (all repos)
+
+Every repository must include the `github-actions` ecosystem entry.
+GitHub Actions use **version updates** (not security-only) on a weekly schedule
+since pinned action SHAs do not affect application runtime stability.
+
+```yaml
+- package-ecosystem: "github-actions"
+  directory: "/"
+  schedule:
+    interval: "weekly"
+  open-pull-requests-limit: 10
+  labels:
+    - "security"
+    - "dependencies"
+```
+
+### Full-Stack Example
+
+A full-stack repo (e.g., npm + Go + Terraform + GitHub Actions) combines the
+relevant ecosystem entries into a single `dependabot.yml`. See
+[`dependabot/fullstack.yml`](dependabot/fullstack.yml) for a complete example.
+
+## Auto-Merge Workflow
+
+See [`workflows/dependabot-automerge.yml`](workflows/dependabot-automerge.yml).
+
+Behavior:
+- Triggers on `pull_request_target` from `dependabot[bot]`
+- Fetches Dependabot metadata to determine update type
+- For **patch** and **minor** security updates (and indirect dependency updates):
+  approves the PR and squash-merges using a GitHub App token
+- **Major** updates are left for human review
+
+## Vulnerability Audit CI Check
+
+See [`workflows/dependency-audit.yml`](workflows/dependency-audit.yml).
+
+This reusable workflow detects the ecosystems present in the repo and runs the
+appropriate audit tool:
+
+| Ecosystem | Tool | Command |
+|-----------|------|---------|
+| npm | `npm audit` | `npm audit --audit-level=moderate` |
+| Go | `govulncheck` | `govulncheck ./...` |
+| Rust | `cargo-audit` | `cargo audit` |
+| Python | `pip-audit` | `pip-audit` |
+
+The workflow fails if any known vulnerability is found, blocking the PR from merging.
+
+## Applying to a Repository
+
+1. Copy the appropriate `dependabot.yml` template to `.github/dependabot.yml`,
+   adjusting `directory` paths as needed.
+2. Copy `workflows/dependabot-automerge.yml` to `.github/workflows/`.
+3. Copy `workflows/dependency-audit.yml` to `.github/workflows/`.
+4. Ensure the repository has the GitHub App secrets (`APP_ID`, `APP_PRIVATE_KEY`)
+   configured for auto-merge.
+5. Create the `security` and `dependencies` labels in the repository if they
+   don't already exist.
+6. Add `dependency-audit` as a required status check in branch protection rules.
diff --git a/standards/dependabot/backend-go.yml b/standards/dependabot/backend-go.yml
new file mode 100644
index 00000000..a7cdca70
--- /dev/null
+++ b/standards/dependabot/backend-go.yml
@@ -0,0 +1,23 @@
+# Dependabot configuration for Go backend repositories
+# Copy to .github/dependabot.yml and adjust directory paths as needed
+version: 2
+updates:
+  # Go modules — security updates only
+  - package-ecosystem: "gomod"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # GitHub Actions — version updates (keep actions current)
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
diff --git a/standards/dependabot/backend-python.yml b/standards/dependabot/backend-python.yml
new file mode 100644
index 00000000..b8f3a829
--- /dev/null
+++ b/standards/dependabot/backend-python.yml
@@ -0,0 +1,23 @@
+# Dependabot configuration for Python backend repositories
+# Copy to .github/dependabot.yml and adjust directory paths as needed
+version: 2
+updates:
+  # pip — security updates only
+  - package-ecosystem: "pip"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # GitHub Actions — version updates (keep actions current)
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
diff --git a/standards/dependabot/backend-rust.yml b/standards/dependabot/backend-rust.yml
new file mode 100644
index 00000000..92d283f5
--- /dev/null
+++ b/standards/dependabot/backend-rust.yml
@@ -0,0 +1,23 @@
+# Dependabot configuration for Rust backend repositories
+# Copy to .github/dependabot.yml and adjust directory paths as needed
+version: 2
+updates:
+  # Cargo — security updates only
+  - package-ecosystem: "cargo"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # GitHub Actions — version updates (keep actions current)
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
diff --git a/standards/dependabot/frontend.yml b/standards/dependabot/frontend.yml
new file mode 100644
index 00000000..96bb698e
--- /dev/null
+++ b/standards/dependabot/frontend.yml
@@ -0,0 +1,23 @@
+# Dependabot configuration for frontend (npm) repositories
+# Copy to .github/dependabot.yml and adjust directory paths as needed
+version: 2
+updates:
+  # npm — security updates only
+  - package-ecosystem: "npm"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # GitHub Actions — version updates (keep actions current)
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
diff --git a/standards/dependabot/fullstack.yml b/standards/dependabot/fullstack.yml
new file mode 100644
index 00000000..f2b6c90a
--- /dev/null
+++ b/standards/dependabot/fullstack.yml
@@ -0,0 +1,48 @@
+# Dependabot configuration for full-stack repositories (npm + Go + Terraform)
+# Copy to .github/dependabot.yml and adjust directory paths as needed
+#
+# Example layout:
+#   apps/mobile/package.json  (or markets-app/package.json)
+#   apps/api/go.mod           (or markets-api/go.mod)
+#   infra/terraform/main.tf
+version: 2
+updates:
+  # npm — security updates only
+  - package-ecosystem: "npm"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # Go modules — security updates only
+  - package-ecosystem: "gomod"
+    directory: "/apps/api"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # Terraform — security updates only
+  - package-ecosystem: "terraform"
+    directory: "/infra/terraform"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # GitHub Actions — version updates (keep actions current)
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
diff --git a/standards/dependabot/infra-terraform.yml b/standards/dependabot/infra-terraform.yml
new file mode 100644
index 00000000..1aa50b9d
--- /dev/null
+++ b/standards/dependabot/infra-terraform.yml
@@ -0,0 +1,23 @@
+# Dependabot configuration for Terraform infrastructure repositories
+# Copy to .github/dependabot.yml and adjust directory paths as needed
+version: 2
+updates:
+  # Terraform — security updates only
+  - package-ecosystem: "terraform"
+    directory: "/infra/terraform"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
+
+  # GitHub Actions — version updates (keep actions current)
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+    labels:
+      - "security"
+      - "dependencies"
diff --git a/standards/workflows/dependabot-automerge.yml b/standards/workflows/dependabot-automerge.yml
new file mode 100644
index 00000000..4561a0ec
--- /dev/null
+++ b/standards/workflows/dependabot-automerge.yml
@@ -0,0 +1,57 @@
+# Dependabot auto-merge workflow
+# Copy to .github/workflows/dependabot-automerge.yml
+#
+# 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 squash-merges Dependabot PRs for:
+#   - Patch version updates
+#   - Minor version updates
+#   - Indirect (transitive) dependency updates
+# Major version updates are left for human review.
+name: Dependabot auto-merge
+
+on:
+  pull_request_target:
+    branches:
+      - main
+
+permissions: {}
+
+jobs:
+  dependabot:
+    runs-on: ubuntu-latest
+    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: Generate app token
+        if: >-
+          steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
+          steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
+          steps.metadata.outputs.dependency-type == 'indirect'
+        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 auto-merge
+        if: >-
+          steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
+          steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
+          steps.metadata.outputs.dependency-type == 'indirect'
+        run: |
+          gh pr review --approve "$PR_URL"
+          gh pr merge --squash --admin "$PR_URL"
+        env:
+          PR_URL: ${{ github.event.pull_request.html_url }}
+          GH_TOKEN: ${{ steps.app-token.outputs.token }}
diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml
new file mode 100644
index 00000000..d84556e5
--- /dev/null
+++ b/standards/workflows/dependency-audit.yml
@@ -0,0 +1,136 @@
+# Dependency vulnerability audit
+# Copy to .github/workflows/dependency-audit.yml
+#
+# 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.
+name: Dependency audit
+
+on:
+  pull_request:
+    branches: [main]
+  push:
+    branches: [main]
+
+permissions:
+  contents: read
+
+jobs:
+  detect:
+    name: Detect ecosystems
+    runs-on: ubuntu-latest
+    outputs:
+      npm: ${{ steps.check.outputs.npm }}
+      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
+          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
+
+          # Go modules
+          if find . -name 'go.sum' | grep -q .; then
+            echo "gomod=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "gomod=false" >> "$GITHUB_OUTPUT"
+          fi
+
+          # Cargo
+          if [ -f "Cargo.lock" ]; then
+            echo "cargo=true" >> "$GITHUB_OUTPUT"
+          else
+            echo "cargo=false" >> "$GITHUB_OUTPUT"
+          fi
+
+          # Python
+          if [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; 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-latest
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+
+      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+        with:
+          node-version: "lts/*"
+
+      - name: Audit npm dependencies
+        run: npm audit --audit-level=moderate
+
+  audit-go:
+    name: govulncheck
+    needs: detect
+    if: needs.detect.outputs.gomod == 'true'
+    runs-on: ubuntu-latest
+    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@latest
+
+      - name: Audit Go dependencies
+        run: |
+          # Run govulncheck in each module directory
+          find . -name 'go.mod' -exec dirname {} \; | while read -r dir; do
+            echo "::group::govulncheck $dir"
+            (cd "$dir" && govulncheck ./...)
+            echo "::endgroup::"
+          done
+
+  audit-cargo:
+    name: cargo audit
+    needs: detect
+    if: needs.detect.outputs.cargo == 'true'
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+
+      - name: Install cargo-audit
+        run: cargo install cargo-audit
+
+      - name: Audit Cargo dependencies
+        run: cargo audit
+
+  audit-pip:
+    name: pip-audit
+    needs: detect
+    if: needs.detect.outputs.pip == 'true'
+    runs-on: ubuntu-latest
+    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
+
+      - name: Audit Python dependencies
+        run: |
+          if [ -f "pyproject.toml" ]; then
+            pip-audit --require-hashes=false
+          elif [ -f "requirements.txt" ]; then
+            pip-audit -r requirements.txt
+          fi

From 5532e5b687bbdea0614a16e90acc7a19229b7c2b Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-MacBook-Air.local>
Date: Fri, 3 Apr 2026 11:41:08 -0700
Subject: [PATCH 2/6] fix: address Copilot review feedback on Dependabot
 standards

- Use open-pull-requests-limit: 0 for app ecosystems to suppress version
  update PRs (security updates bypass this limit)
- Switch auto-merge from --admin to --auto --squash to respect CI checks
- Fix dependency-audit: detect go.mod not go.sum, detect Cargo.toml not
  Cargo.lock, add Rust toolchain setup, handle monorepo npm lockfiles,
  pin tool versions (govulncheck v1.1.4, cargo-audit 0.21.1,
  pip-audit 2.9.0), install Python deps before auditing
- Use npm audit --audit-level=low to fail on any advisory
- Update policy doc wording for clarity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 standards/dependabot-policy.md               | 47 ++++++++++++++-----
 standards/dependabot/backend-go.yml          |  7 ++-
 standards/dependabot/backend-python.yml      |  7 ++-
 standards/dependabot/backend-rust.yml        |  7 ++-
 standards/dependabot/frontend.yml            |  7 ++-
 standards/dependabot/fullstack.yml           | 15 +++---
 standards/dependabot/infra-terraform.yml     |  7 ++-
 standards/workflows/dependabot-automerge.yml |  7 +--
 standards/workflows/dependency-audit.yml     | 49 +++++++++++++-------
 9 files changed, 106 insertions(+), 47 deletions(-)

diff --git a/standards/dependabot-policy.md b/standards/dependabot-policy.md
index b3c42305..e3a09bf7 100644
--- a/standards/dependabot-policy.md
+++ b/standards/dependabot-policy.md
@@ -10,14 +10,26 @@ security posture than chasing every minor/patch release.
 
 1. **Security updates only** for application dependencies (npm, Go modules, pip, Cargo).
    Dependabot opens PRs only when a vulnerability advisory exists for a dependency.
+   Version update PRs are suppressed by setting `open-pull-requests-limit: 0` for
+   application ecosystems — Dependabot security updates bypass this limit.
 2. **Version updates weekly** for GitHub Actions, since pinned action versions do not
    affect application stability and staying current reduces CI attack surface.
 3. **Labels** `security` and `dependencies` on every Dependabot PR for filtering and audit.
-4. **Auto-merge** security patches and minor updates after CI passes, using a GitHub App
-   token to satisfy branch protection (CODEOWNERS review bypass for bot PRs).
+4. **Auto-merge** security patches and minor updates after all CI checks pass, using a
+   GitHub App token to satisfy branch protection (CODEOWNERS review bypass for bot PRs).
+   Uses `gh pr merge --auto` to wait for required checks before merging.
 5. **Vulnerability audit CI check** runs on every PR and push to `main`, failing the
    build if any dependency has a known advisory. This is a required status check.
 
+## Prerequisites
+
+- **Dependabot security updates** must be enabled at the org or repo level
+  (Settings > Code security > Dependabot security updates).
+- The `dependabot.yml` entries below configure which ecosystems and directories
+  Dependabot monitors. Setting `open-pull-requests-limit: 0` for application
+  ecosystems suppresses routine version-update PRs while still allowing
+  security-alert-triggered PRs to be created.
+
 ## Configuration Files
 
 Each repository must have:
@@ -30,13 +42,25 @@ Each repository must have:
 
 ## Dependabot Templates
 
-Use the template matching your repository type. All templates share these settings:
+Use the template matching your repository type.
+
+**Application ecosystems** (npm, gomod, cargo, pip, terraform) use:
+
+```yaml
+schedule:
+  interval: "weekly"
+open-pull-requests-limit: 0   # suppress version updates; security PRs bypass this
+labels:
+  - "security"
+  - "dependencies"
+```
+
+**GitHub Actions** uses:
 
 ```yaml
-# Common settings applied to every package ecosystem entry
 schedule:
   interval: "weekly"
-open-pull-requests-limit: 10
+open-pull-requests-limit: 10  # allow version updates for CI actions
 labels:
   - "security"
   - "dependencies"
@@ -48,8 +72,6 @@ For repos with `package.json` at root or in subdirectories.
 
 - Ecosystem: `npm`
 - Directory: `/` (or path to each `package.json`)
-- Security updates only (no `versioning-strategy` override needed — Dependabot
-  security updates use the lockfile by default)
 
 See [`dependabot/frontend.yml`](dependabot/frontend.yml)
 
@@ -119,21 +141,22 @@ See [`workflows/dependabot-automerge.yml`](workflows/dependabot-automerge.yml).
 Behavior:
 - Triggers on `pull_request_target` from `dependabot[bot]`
 - Fetches Dependabot metadata to determine update type
-- For **patch** and **minor** security updates (and indirect dependency updates):
-  approves the PR and squash-merges using a GitHub App token
+- For **patch** and **minor** updates (and indirect dependency updates):
+  approves the PR and enables auto-merge (waits for all required CI checks)
 - **Major** updates are left for human review
+- Uses `gh pr merge --auto --squash` so the merge only happens after CI passes
 
 ## Vulnerability Audit CI Check
 
 See [`workflows/dependency-audit.yml`](workflows/dependency-audit.yml).
 
-This reusable workflow detects the ecosystems present in the repo and runs the
+This workflow template detects the ecosystems present in the repo and runs the
 appropriate audit tool:
 
 | Ecosystem | Tool | Command |
 |-----------|------|---------|
-| npm | `npm audit` | `npm audit --audit-level=moderate` |
-| Go | `govulncheck` | `govulncheck ./...` |
+| npm | `npm audit` | `npm audit --audit-level=low` (fails on any advisory) |
+| Go | `govulncheck` | `govulncheck ./...` (per module) |
 | Rust | `cargo-audit` | `cargo audit` |
 | Python | `pip-audit` | `pip-audit` |
 
diff --git a/standards/dependabot/backend-go.yml b/standards/dependabot/backend-go.yml
index a7cdca70..985d6721 100644
--- a/standards/dependabot/backend-go.yml
+++ b/standards/dependabot/backend-go.yml
@@ -1,13 +1,16 @@
 # Dependabot configuration for Go backend repositories
 # Copy to .github/dependabot.yml and adjust directory paths as needed
+#
+# gomod uses open-pull-requests-limit: 0 to suppress version update PRs.
+# Dependabot security updates bypass this limit, so security PRs still open.
 version: 2
 updates:
-  # Go modules — security updates only
+  # Go modules — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "gomod"
     directory: "/"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
diff --git a/standards/dependabot/backend-python.yml b/standards/dependabot/backend-python.yml
index b8f3a829..dc8ff3f3 100644
--- a/standards/dependabot/backend-python.yml
+++ b/standards/dependabot/backend-python.yml
@@ -1,13 +1,16 @@
 # Dependabot configuration for Python backend repositories
 # Copy to .github/dependabot.yml and adjust directory paths as needed
+#
+# pip uses open-pull-requests-limit: 0 to suppress version update PRs.
+# Dependabot security updates bypass this limit, so security PRs still open.
 version: 2
 updates:
-  # pip — security updates only
+  # pip — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "pip"
     directory: "/"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
diff --git a/standards/dependabot/backend-rust.yml b/standards/dependabot/backend-rust.yml
index 92d283f5..5570ac84 100644
--- a/standards/dependabot/backend-rust.yml
+++ b/standards/dependabot/backend-rust.yml
@@ -1,13 +1,16 @@
 # Dependabot configuration for Rust backend repositories
 # Copy to .github/dependabot.yml and adjust directory paths as needed
+#
+# cargo uses open-pull-requests-limit: 0 to suppress version update PRs.
+# Dependabot security updates bypass this limit, so security PRs still open.
 version: 2
 updates:
-  # Cargo — security updates only
+  # Cargo — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "cargo"
     directory: "/"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
diff --git a/standards/dependabot/frontend.yml b/standards/dependabot/frontend.yml
index 96bb698e..7398cb44 100644
--- a/standards/dependabot/frontend.yml
+++ b/standards/dependabot/frontend.yml
@@ -1,13 +1,16 @@
 # Dependabot configuration for frontend (npm) repositories
 # Copy to .github/dependabot.yml and adjust directory paths as needed
+#
+# npm uses open-pull-requests-limit: 0 to suppress version update PRs.
+# Dependabot security updates bypass this limit, so security PRs still open.
 version: 2
 updates:
-  # npm — security updates only
+  # npm — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "npm"
     directory: "/"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
diff --git a/standards/dependabot/fullstack.yml b/standards/dependabot/fullstack.yml
index f2b6c90a..fb1a0458 100644
--- a/standards/dependabot/fullstack.yml
+++ b/standards/dependabot/fullstack.yml
@@ -1,38 +1,41 @@
 # Dependabot configuration for full-stack repositories (npm + Go + Terraform)
 # Copy to .github/dependabot.yml and adjust directory paths as needed
 #
+# Application ecosystems use open-pull-requests-limit: 0 to suppress version
+# update PRs. Dependabot security updates bypass this limit.
+#
 # Example layout:
 #   apps/mobile/package.json  (or markets-app/package.json)
 #   apps/api/go.mod           (or markets-api/go.mod)
 #   infra/terraform/main.tf
 version: 2
 updates:
-  # npm — security updates only
+  # npm — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "npm"
     directory: "/"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
 
-  # Go modules — security updates only
+  # Go modules — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "gomod"
     directory: "/apps/api"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
 
-  # Terraform — security updates only
+  # Terraform — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "terraform"
     directory: "/infra/terraform"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
diff --git a/standards/dependabot/infra-terraform.yml b/standards/dependabot/infra-terraform.yml
index 1aa50b9d..e6e27c4d 100644
--- a/standards/dependabot/infra-terraform.yml
+++ b/standards/dependabot/infra-terraform.yml
@@ -1,13 +1,16 @@
 # Dependabot configuration for Terraform infrastructure repositories
 # Copy to .github/dependabot.yml and adjust directory paths as needed
+#
+# terraform uses open-pull-requests-limit: 0 to suppress version update PRs.
+# Dependabot security updates bypass this limit, so security PRs still open.
 version: 2
 updates:
-  # Terraform — security updates only
+  # Terraform — security updates only (limit 0 suppresses version updates)
   - package-ecosystem: "terraform"
     directory: "/infra/terraform"
     schedule:
       interval: "weekly"
-    open-pull-requests-limit: 10
+    open-pull-requests-limit: 0
     labels:
       - "security"
       - "dependencies"
diff --git a/standards/workflows/dependabot-automerge.yml b/standards/workflows/dependabot-automerge.yml
index 4561a0ec..85114d36 100644
--- a/standards/workflows/dependabot-automerge.yml
+++ b/standards/workflows/dependabot-automerge.yml
@@ -5,11 +5,12 @@
 #   APP_ID         — GitHub App ID with contents:write and pull-requests:write
 #   APP_PRIVATE_KEY — GitHub App private key
 #
-# Auto-approves and squash-merges Dependabot PRs for:
+# Auto-approves and enables auto-merge for Dependabot PRs:
 #   - Patch version updates
 #   - Minor version updates
 #   - Indirect (transitive) dependency updates
 # Major version updates are left for human review.
+# Uses --auto so the merge waits for all required CI checks to pass.
 name: Dependabot auto-merge
 
 on:
@@ -44,14 +45,14 @@ jobs:
           app-id: ${{ secrets.APP_ID }}
           private-key: ${{ secrets.APP_PRIVATE_KEY }}
 
-      - name: Approve and auto-merge
+      - name: Approve and enable auto-merge
         if: >-
           steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
           steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
           steps.metadata.outputs.dependency-type == 'indirect'
         run: |
           gh pr review --approve "$PR_URL"
-          gh pr merge --squash --admin "$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 }}
diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml
index d84556e5..ab82e9f5 100644
--- a/standards/workflows/dependency-audit.yml
+++ b/standards/workflows/dependency-audit.yml
@@ -31,22 +31,22 @@ jobs:
       - name: Detect package ecosystems
         id: check
         run: |
-          # npm
+          # 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
 
-          # Go modules
-          if find . -name 'go.sum' | grep -q .; then
+          # 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
-          if [ -f "Cargo.lock" ]; then
+          # Cargo — detect via Cargo.toml (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"
@@ -72,7 +72,17 @@ jobs:
           node-version: "lts/*"
 
       - name: Audit npm dependencies
-        run: npm audit --audit-level=moderate
+        run: |
+          # Audit each package-lock.json found in the repo
+          failed=0
+          find . -name 'package-lock.json' -not -path '*/node_modules/*' -exec dirname {} \; | while read -r dir; do
+            echo "::group::npm audit $dir"
+            if ! (cd "$dir" && npm audit --audit-level=low); then
+              failed=1
+            fi
+            echo "::endgroup::"
+          done
+          exit $failed
 
   audit-go:
     name: govulncheck
@@ -87,16 +97,20 @@ jobs:
           go-version: "stable"
 
       - name: Install govulncheck
-        run: go install golang.org/x/vuln/cmd/govulncheck@latest
+        run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4
 
       - name: Audit Go dependencies
         run: |
           # Run govulncheck in each module directory
-          find . -name 'go.mod' -exec dirname {} \; | while read -r dir; do
+          failed=0
+          find . -name 'go.mod' -not -path '*/vendor/*' -exec dirname {} \; | while read -r dir; do
             echo "::group::govulncheck $dir"
-            (cd "$dir" && govulncheck ./...)
+            if ! (cd "$dir" && govulncheck ./...); then
+              failed=1
+            fi
             echo "::endgroup::"
           done
+          exit $failed
 
   audit-cargo:
     name: cargo audit
@@ -106,8 +120,10 @@ jobs:
     steps:
       - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
 
+      - uses: dtolnay/rust-toolchain@stable
+
       - name: Install cargo-audit
-        run: cargo install cargo-audit
+        run: cargo install cargo-audit@0.21.1 --locked
 
       - name: Audit Cargo dependencies
         run: cargo audit
@@ -124,13 +140,14 @@ jobs:
         with:
           python-version: "3.x"
 
-      - name: Install pip-audit
-        run: pip install pip-audit
-
-      - name: Audit Python dependencies
+      - name: Install dependencies and pip-audit
         run: |
+          pip install pip-audit==2.9.0
           if [ -f "pyproject.toml" ]; then
-            pip-audit --require-hashes=false
+            pip install -e ".[dev]" 2>/dev/null || pip install -e . 2>/dev/null || true
           elif [ -f "requirements.txt" ]; then
-            pip-audit -r requirements.txt
+            pip install -r requirements.txt
           fi
+
+      - name: Audit Python dependencies
+        run: pip-audit

From 9ea71bafdd78b166078ab58fb2ee4bada9199955 Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-MacBook-Air.local>
Date: Fri, 3 Apr 2026 11:43:10 -0700
Subject: [PATCH 3/6] fix: address CodeRabbit review feedback
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

- Auto-merge: restructure eligibility check with explicit step, add
  defense-in-depth comment explaining security model
- dependency-audit: make Cargo and Python detection recursive (matching
  npm/Go pattern), iterate over workspace dirs for cargo audit, use
  pip-audit with project path instead of auditing runner env, use
  process substitution to preserve exit status across subshells
- Policy doc: fix wording (Copy → Add), update audit table for monorepo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 standards/dependabot-policy.md               | 12 +--
 standards/workflows/dependabot-automerge.yml | 44 ++++++++---
 standards/workflows/dependency-audit.yml     | 77 ++++++++++++++------
 3 files changed, 91 insertions(+), 42 deletions(-)

diff --git a/standards/dependabot-policy.md b/standards/dependabot-policy.md
index e3a09bf7..6abbc2ab 100644
--- a/standards/dependabot-policy.md
+++ b/standards/dependabot-policy.md
@@ -155,10 +155,10 @@ appropriate audit tool:
 
 | Ecosystem | Tool | Command |
 |-----------|------|---------|
-| npm | `npm audit` | `npm audit --audit-level=low` (fails on any advisory) |
-| Go | `govulncheck` | `govulncheck ./...` (per module) |
-| Rust | `cargo-audit` | `cargo audit` |
-| Python | `pip-audit` | `pip-audit` |
+| npm | `npm audit` | `npm audit --audit-level=low` per lockfile (fails on any advisory) |
+| Go | `govulncheck` | `govulncheck ./...` per `go.mod` directory |
+| Rust | `cargo-audit` | `cargo audit` per `Cargo.toml` workspace |
+| Python | `pip-audit` | `pip-audit .` per `pyproject.toml` / `-r requirements.txt` |
 
 The workflow fails if any known vulnerability is found, blocking the PR from merging.
 
@@ -166,8 +166,8 @@ The workflow fails if any known vulnerability is found, blocking the PR from mer
 
 1. Copy the appropriate `dependabot.yml` template to `.github/dependabot.yml`,
    adjusting `directory` paths as needed.
-2. Copy `workflows/dependabot-automerge.yml` to `.github/workflows/`.
-3. Copy `workflows/dependency-audit.yml` to `.github/workflows/`.
+2. Add `workflows/dependabot-automerge.yml` to `.github/workflows/`.
+3. Add `workflows/dependency-audit.yml` to `.github/workflows/`.
 4. Ensure the repository has the GitHub App secrets (`APP_ID`, `APP_PRIVATE_KEY`)
    configured for auto-merge.
 5. Create the `security` and `dependencies` labels in the repository if they
diff --git a/standards/workflows/dependabot-automerge.yml b/standards/workflows/dependabot-automerge.yml
index 85114d36..6080c9a4 100644
--- a/standards/workflows/dependabot-automerge.yml
+++ b/standards/workflows/dependabot-automerge.yml
@@ -5,12 +5,17 @@
 #   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:
-#   - Patch version updates
-#   - Minor version updates
+# 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 left for human review.
+# 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:
@@ -34,11 +39,29 @@ jobs:
         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.metadata.outputs.update-type == 'version-update:semver-patch' ||
-          steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
-          steps.metadata.outputs.dependency-type == 'indirect'
+        if: steps.eligible.outputs.eligible == 'true'
         id: app-token
         uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3
         with:
@@ -46,10 +69,7 @@ jobs:
           private-key: ${{ secrets.APP_PRIVATE_KEY }}
 
       - name: Approve and enable auto-merge
-        if: >-
-          steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
-          steps.metadata.outputs.update-type == 'version-update:semver-minor' ||
-          steps.metadata.outputs.dependency-type == 'indirect'
+        if: steps.eligible.outputs.eligible == 'true'
         run: |
           gh pr review --approve "$PR_URL"
           gh pr merge --auto --squash "$PR_URL"
diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml
index ab82e9f5..36a8f6ad 100644
--- a/standards/workflows/dependency-audit.yml
+++ b/standards/workflows/dependency-audit.yml
@@ -5,6 +5,9 @@
 # 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.21.1 | pip-audit 2.9.0
 name: Dependency audit
 
 on:
@@ -45,15 +48,16 @@ jobs:
             echo "gomod=false" >> "$GITHUB_OUTPUT"
           fi
 
-          # Cargo — detect via Cargo.toml (lockfile may not exist for libraries)
+          # 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
-          if [ -f "pyproject.toml" ] || [ -f "requirements.txt" ]; then
+          # 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"
@@ -74,15 +78,15 @@ jobs:
       - name: Audit npm dependencies
         run: |
           # Audit each package-lock.json found in the repo
-          failed=0
-          find . -name 'package-lock.json' -not -path '*/node_modules/*' -exec dirname {} \; | while read -r dir; do
+          status=0
+          while IFS= read -r dir; do
             echo "::group::npm audit $dir"
             if ! (cd "$dir" && npm audit --audit-level=low); then
-              failed=1
+              status=1
             fi
             echo "::endgroup::"
-          done
-          exit $failed
+          done < <(find . -name 'package-lock.json' -not -path '*/node_modules/*' -exec dirname {} \;)
+          exit $status
 
   audit-go:
     name: govulncheck
@@ -101,16 +105,15 @@ jobs:
 
       - name: Audit Go dependencies
         run: |
-          # Run govulncheck in each module directory
-          failed=0
-          find . -name 'go.mod' -not -path '*/vendor/*' -exec dirname {} \; | while read -r dir; do
+          status=0
+          while IFS= read -r dir; do
             echo "::group::govulncheck $dir"
             if ! (cd "$dir" && govulncheck ./...); then
-              failed=1
+              status=1
             fi
             echo "::endgroup::"
-          done
-          exit $failed
+          done < <(find . -name 'go.mod' -not -path '*/vendor/*' -exec dirname {} \;)
+          exit $status
 
   audit-cargo:
     name: cargo audit
@@ -126,7 +129,18 @@ jobs:
         run: cargo install cargo-audit@0.21.1 --locked
 
       - name: Audit Cargo dependencies
-        run: cargo audit
+        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
@@ -140,14 +154,29 @@ jobs:
         with:
           python-version: "3.x"
 
-      - name: Install dependencies and pip-audit
-        run: |
-          pip install pip-audit==2.9.0
-          if [ -f "pyproject.toml" ]; then
-            pip install -e ".[dev]" 2>/dev/null || pip install -e . 2>/dev/null || true
-          elif [ -f "requirements.txt" ]; then
-            pip install -r requirements.txt
-          fi
+      - name: Install pip-audit
+        run: pip install pip-audit==2.9.0
 
       - name: Audit Python dependencies
-        run: pip-audit
+        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

From ad067cfb3c4de49f5bb14c8d11141f6102475671 Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-MacBook-Air.local>
Date: Fri, 3 Apr 2026 11:47:28 -0700
Subject: [PATCH 4/6] feat: add pnpm audit support to dependency-audit workflow

Repos like broodly use pnpm instead of npm. The workflow now detects
pnpm-lock.yaml and runs pnpm audit alongside npm audit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 standards/dependabot-policy.md           |  3 +-
 standards/workflows/dependency-audit.yml | 36 ++++++++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)

diff --git a/standards/dependabot-policy.md b/standards/dependabot-policy.md
index 6abbc2ab..702889bc 100644
--- a/standards/dependabot-policy.md
+++ b/standards/dependabot-policy.md
@@ -155,7 +155,8 @@ appropriate audit tool:
 
 | Ecosystem | Tool | Command |
 |-----------|------|---------|
-| npm | `npm audit` | `npm audit --audit-level=low` per lockfile (fails on any advisory) |
+| npm | `npm audit` | `npm audit --audit-level=low` per `package-lock.json` (fails on any advisory) |
+| pnpm | `pnpm audit` | `pnpm audit --audit-level low` per `pnpm-lock.yaml` |
 | Go | `govulncheck` | `govulncheck ./...` per `go.mod` directory |
 | Rust | `cargo-audit` | `cargo audit` per `Cargo.toml` workspace |
 | Python | `pip-audit` | `pip-audit .` per `pyproject.toml` / `-r requirements.txt` |
diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml
index 36a8f6ad..4e7c732f 100644
--- a/standards/workflows/dependency-audit.yml
+++ b/standards/workflows/dependency-audit.yml
@@ -25,6 +25,7 @@ jobs:
     runs-on: ubuntu-latest
     outputs:
       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 }}
@@ -41,6 +42,13 @@ jobs:
             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"
@@ -88,6 +96,34 @@ jobs:
           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-latest
+    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

From 16b46cb4b771b9525e86ce3f2d9cf411dda1e4df Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-MacBook-Air.local>
Date: Fri, 3 Apr 2026 11:51:27 -0700
Subject: [PATCH 5/6] fix: bump cargo-audit to 0.22.1 for CVSS 4.0 support

cargo-audit 0.21.1 fails parsing RustSec advisories using CVSS 4.0
scores (e.g., RUSTSEC-2025-0138). Version 0.22.1 adds support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 standards/workflows/dependency-audit.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml
index 4e7c732f..3f29337a 100644
--- a/standards/workflows/dependency-audit.yml
+++ b/standards/workflows/dependency-audit.yml
@@ -7,7 +7,7 @@
 # Add "dependency-audit" as a required status check in branch protection.
 #
 # Pinned tool versions (update deliberately):
-#   govulncheck v1.1.4 | cargo-audit 0.21.1 | pip-audit 2.9.0
+#   govulncheck v1.1.4 | cargo-audit 0.22.1 | pip-audit 2.9.0
 name: Dependency audit
 
 on:

From 8ca31e47184d9420b26ae41a82cb3af35571a315 Mon Sep 17 00:00:00 2001
From: DJ <dj@Rachels-MacBook-Air.local>
Date: Fri, 3 Apr 2026 20:11:56 -0700
Subject: [PATCH 6/6] fix: remove pnpm cache from audit job (causes path
 validation error)

The setup-node cache: "pnpm" option fails during post-job cleanup
when the pnpm store path doesn't exist. Caching isn't needed for
a one-off audit run.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 standards/workflows/dependency-audit.yml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/standards/workflows/dependency-audit.yml b/standards/workflows/dependency-audit.yml
index 3f29337a..5a1c53bc 100644
--- a/standards/workflows/dependency-audit.yml
+++ b/standards/workflows/dependency-audit.yml
@@ -109,7 +109,6 @@ jobs:
       - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
           node-version: "lts/*"
-          cache: "pnpm"
 
       - name: Audit pnpm dependencies
         run: |
@@ -162,7 +161,7 @@ jobs:
       - uses: dtolnay/rust-toolchain@stable
 
       - name: Install cargo-audit
-        run: cargo install cargo-audit@0.21.1 --locked
+        run: cargo install cargo-audit@0.22.1 --locked
 
       - name: Audit Cargo dependencies
         run: |
