Files
Dragan Spiridonov 6dcb919aee feat(deps): dependency-security system + fix grpc-js HIGH (ADR-115)
Closes the gap that let @grpc/grpc-js@1.14.3 (CVE-2026-48068/48069, HIGH)
sit undetected: every existing dependency check only fires on dep-file
changes, so a CVE disclosed against a frozen pin was invisible.

Adds:
- scripts/dependency-audit.mjs    in-repo + consumer-POV audit, severity gate
- scripts/dependency-update.mjs   security-first in-range updater with a guard
                                  that refuses to drop optional @ruvector
                                  platform deps (the Dependabot lockfile bug)
- dependency-audit.yml            daily consumer-POV scan -> tracking issue
- dependency-update.yml           weekly in-range security PR (gh CLI)
- docs/guides/dependency-security-policy.md + ADR-115
- tests/unit/scripts/dependency-security.test.ts (11 hermetic tests)
- npm scripts: deps:audit[:consumer], deps:update[:all]

Also bumps @grpc/grpc-js 1.14.3 -> 1.14.4 in-range (HIGH 1 -> 0); optional
platform deps preserved (90 -> 90).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:18:21 +00:00

91 lines
3.5 KiB
YAML

# =============================================================================
# Dependency Security Audit — SCHEDULED (ADR-115)
# =============================================================================
#
# The gap this closes: every other dependency check in the repo
# (consumer-audit.yml, the npm-publish gate) is triggered by CHANGES to
# package.json / package-lock.json. A CVE freshly disclosed against an
# already-pinned, unchanged dependency therefore stays invisible until someone
# happens to edit a dep file. That is exactly how @grpc/grpc-js@1.14.3 sat with
# a HIGH (CVE-2026-48068) while our own CI stayed green — a cold-outreach vendor
# noticed before we did.
#
# This workflow runs the authoritative CONSUMER-POV audit (pack tarball, clean
# install, audit what a real `npm install agentic-qe` user gets — root overrides
# do NOT apply downstream) on a daily schedule. On any HIGH/CRITICAL it opens or
# updates a single tracking issue so the finding surfaces on its own.
# =============================================================================
name: Dependency Security Audit
on:
schedule:
# Daily 07:00 UTC — off-peak, after most upstreams publish their nightly.
- cron: '0 7 * * *'
workflow_dispatch:
# Least-privilege: read the repo, write only the tracking issue.
permissions:
contents: read
issues: write
concurrency:
group: dependency-audit-scheduled
cancel-in-progress: true
env:
NODE_VERSION: '24.13.0'
ISSUE_TITLE: 'Dependency audit: HIGH/CRITICAL CVE reachable by consumers'
jobs:
audit:
name: Consumer-POV scheduled audit
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build (tarball must match what publish ships)
run: npm run build
- name: Consumer-POV audit (HIGH/CRITICAL gate)
id: audit
continue-on-error: true
run: node scripts/dependency-audit.mjs --consumer --level=high
- name: Open or update tracking issue
if: steps.audit.outcome == 'failure'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
BODY_FILE=reports/dependency-audit.md
[ -f "$BODY_FILE" ] || echo "Audit failed but no report produced — see workflow logs." > "$BODY_FILE"
printf '\n\n_Auto-filed by `%s` run [%s](%s/%s/actions/runs/%s)._\n' \
"dependency-audit.yml" "${GITHUB_RUN_ID}" "${GITHUB_SERVER_URL}" "${GITHUB_REPOSITORY}" "${GITHUB_RUN_ID}" >> "$BODY_FILE"
EXISTING=$(gh issue list --state open --search "in:title ${ISSUE_TITLE}" --json number --jq '.[0].number // empty')
if [ -n "$EXISTING" ]; then
echo "Updating existing issue #${EXISTING}"
gh issue comment "$EXISTING" --body-file "$BODY_FILE"
else
echo "Opening new tracking issue"
gh issue create --title "$ISSUE_TITLE" --body-file "$BODY_FILE" --label dependencies,security || \
gh issue create --title "$ISSUE_TITLE" --body-file "$BODY_FILE"
fi
- name: Fail the run when blocking findings exist
if: steps.audit.outcome == 'failure'
run: |
echo "::error::Consumer-reachable HIGH/CRITICAL CVE found — tracking issue filed. Run scripts/dependency-update.mjs or bump manually."
exit 1