Skip to content

feat(plugin-webhooks): the webhooks service serves the redeliver door from a Request, and the veto no longer waits for realtime (webhooks segment 3 of #22564) #6040

feat(plugin-webhooks): the webhooks service serves the redeliver door from a Request, and the veto no longer waits for realtime (webhooks segment 3 of #22564)

feat(plugin-webhooks): the webhooks service serves the redeliver door from a Request, and the veto no longer waits for realtime (webhooks segment 3 of #22564) #6040

Workflow file for this run

name: Validate Dependencies
on:
pull_request:
paths:
- '**/package.json'
- 'pnpm-lock.yaml'
- '.changeset/config.json'
- 'pnpm-workspace.yaml'
- 'scripts/check-changeset-fixed.mjs'
- 'scripts/check-override-consistency.mjs'
# The consumer-resolution half of the same question (#16186) — an edit to
# the gate must be exercised on the PR that makes it.
- 'scripts/check-vendor-export-contract.mjs'
# The OSV exemption ledger and its check: a PR that touches either must
# run this workflow, or an exemption could be added without the gate
# that governs it ever running on the PR that adds it.
- 'osv-scanner.toml'
- 'scripts/check-osv-exemptions.mjs'
# The pull_request verdict of the OSV step (#22082): an edit to it must
# be exercised on the PR that makes it.
- 'scripts/osv-base-relative.mjs'
# Re-run when the workflow itself changes, so edits to these gates are
# exercised on the PR that introduces them.
- '.github/workflows/validate-deps.yml'
schedule:
# Run daily at 03:00 UTC — narrows the discovery window for a new OSV
# advisory from up to six days (weekly) to one, so the scheduled scan
# is more likely to surface a red before an unrelated PR's blocking
# trigger collides with it (#14645).
- cron: '0 3 * * *'
workflow_dispatch:
jobs:
validate:
name: Validate Package Dependencies
runs-on: ubuntu-latest
# Measured over the 100 most recent completed runs: p50 1.3 min, max
# 1.9 min (#22082). 15 leaves room for a cold pnpm store and two OSV
# scans on a pull request, and stops a hung step long before GitHub's
# 360-minute default would.
timeout-minutes: 15
permissions:
contents: read
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22'
- name: Setup pnpm
uses: ./.github/actions/setup-pnpm
- name: Get pnpm store directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v6
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-v3-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-v3-
- name: Verify lockfile is up to date
run: |
pnpm install --frozen-lockfile --prefer-offline
- name: Verify Changesets "fixed" group covers every public package
run: node scripts/check-changeset-fixed.mjs
# pnpm overrides pin what THIS workspace resolves, but they do not ship
# with published packages — downstream installs see only the declared
# ranges. If an override target isn't reachable from a publishable
# package's declared range, we test one dependency graph and publish
# another (the 15.1.0 quickstart shipped exactly that: plugin-auth
# declared better-auth ^1.6.23 while CI ran the 1.7.0-rc.1 override,
# and every fresh project 500'd on auth).
#
# The same run also prints two informational censuses (#6046): overrides
# nothing in the dependency tree consumes, and selectors whose upper
# bound excludes their own target (#4961 / #5032). Both are REPORTS and
# never fail the job — an unconsumed override is a legitimate posture
# (#5835 ruling A). `--self-test` proves the check in both directions,
# so run the `check:override-consistency` chain rather than the bare
# script: a self-test nothing invokes is a phantom check.
- name: Verify overrides are reflected in published manifests
run: pnpm check:override-consistency
# The half check-override-consistency structurally cannot cover (#16186).
# It asks whether the override TARGET is reachable from the declared
# range; `^1.7.2` and `^1.7.2` agreed perfectly while both floated onto
# `@better-auth/core@1.7.3`, a PATCH that deleted the export plugin-auth
# imports statically. Three published releases could not load on a fresh
# install and nothing here went red, because pnpm-lock.yaml held 1.7.2.
#
# `--resolve` deliberately ignores the lockfile: for every version the
# DECLARED range admits on the registry -- the way a downstream project
# resolves -- it installs that version outside this workspace and checks
# the export surface. It fails if ANY admitted version is missing a symbol
# we import. With the ranges pinned exact that is one small install per
# vendor; under a caret it is one per published version, which is the cost
# of the risk being taken.
#
# It lives HERE rather than in the lint farm because it needs the network,
# which this job already has, and because the daily schedule is what turns
# "a vendor published something today" into a red in this repo instead of
# in a customer's install. An unreachable registry exits 3
# (PREREQUISITE NOT MET), never a green.
- name: Verify the declared ranges cannot resolve past our import surface
run: pnpm check:vendor-export-contract-resolve
# Fail the workflow if known vulnerabilities are found — enforces
# security compliance before merging.
#
# Previously this ran `pnpm audit --audit-level=high`, but npm retired the
# audit endpoint (HTTP 410, "This endpoint is being retired. Use the bulk
# advisory endpoint instead.") and pnpm has not migrated, so `pnpm audit`
# now fails unconditionally on every branch (see issue #2974). OSV-Scanner
# reads pnpm-lock.yaml directly against the OSV database and exits non-zero
# when any advisory matches, restoring a working blocking gate.
#
# Note: OSV-Scanner blocks on any severity, not just high/critical. When
# the advisory has a fixed version you take the fix. The ONLY escape
# hatch, for an advisory with no fix available, is an `[[IgnoredVulns]]`
# entry in `osv-scanner.toml` at the repo root — never lowering the gate.
#
# That escape hatch is governed by three conventions (#4965), stated in
# full in the header of osv-scanner.toml: `ignoreUntil` mandatory
# (default 30 days, ceiling 90), `reason` mandatory with an advisory link
# plus a sentence on why it cannot be fixed, and exemptions land in their
# own `osv-exemption`-labelled PR. The scanner enforces none of that — it
# reads a missing `ignoreUntil` as "ignore forever", silently — so the
# step below runs first and fails on any exemption that is missing,
# quoted, expired, over the ceiling, or unexplained. `--self-test` proves
# the check in both directions.
- name: Verify OSV exemptions carry an expiry and a reason
run: |
node scripts/check-osv-exemptions.mjs --self-test
node scripts/check-osv-exemptions.mjs
# Two verdicts, one scanner pin (#22082, ruling A).
#
# `schedule` and `workflow_dispatch` judge `main` ABSOLUTELY: every
# advisory the lockfile matches fails the step. That daily red is the
# signal that files the finding card, and this step is unchanged by the
# pull_request verdict below.
#
# A pull request is judged BASE-RELATIVELY: it answers for the advisories
# it introduces, not for the ones `main` already carries -- those are
# `main`'s to fix, and a pull request that changes no dependency cannot
# fix them. The steps below scan the pull request's lockfile and the merge
# base's, each under its own `osv-scanner.toml`, and
# scripts/osv-base-relative.mjs fails only on an advisory the merge base
# does not match; the others become notices naming the advisory and its
# open finding card. A scan that does not reach a verdict fails closed.
#
# The pin is an untagged upstream commit after the v2.5.0 tag whose
# action.yml runs the ghcr.io/google/osv-scanner-action:v2.5.0 image (the
# comment here used to read v2.3.8). Keep all three `uses:` lines on ONE
# sha: the two verdicts must come from the same scanner.
- name: Audit dependencies for known vulnerabilities (OSV-Scanner)
if: github.event_name != 'pull_request'
uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # untagged, v2.5.0 image
with:
scan-args: |-
--lockfile=pnpm-lock.yaml
# The checkout is the pull request's test merge (refs/pull/N/merge) at
# depth 1. Its first parent is the base-branch commit GitHub merged the
# pull request into -- the merge base of the scanned tree and the base
# branch -- so ONE depth-1 fetch of that sha is all the history this needs
# (never fetch-depth: 0). When neither pnpm-lock.yaml nor osv-scanner.toml
# differs from it, the pull request cannot introduce an advisory and no
# scan runs. The ledger is part of the condition: a pull request that
# deletes an exemption changes the verdict without touching the lockfile.
- name: Stage the OSV comparison against the merge base (pull request)
id: osv-stage
if: github.event_name == 'pull_request'
run: |
node scripts/osv-base-relative.mjs --self-test
mapfile -t parents < <(git cat-file -p HEAD | sed -n 's/^parent //p')
if [ "${#parents[@]}" -ne 2 ]; then
echo "::error title=OSV base-relative verdict unavailable::HEAD is not the pull request's two-parent test merge (${#parents[@]} parent(s) found), so there is no merge base to judge against"
exit 1
fi
base="${parents[0]}"
git fetch --no-tags --depth=1 origin "$base"
echo "base=$base" >> "$GITHUB_OUTPUT"
if git diff --quiet "$base" HEAD -- pnpm-lock.yaml osv-scanner.toml; then
echo "::notice title=OSV-Scanner verdict (base-relative)::lockfile unchanged — inherits main's verdict. pnpm-lock.yaml and osv-scanner.toml are identical to the merge base ${base:0:12}, so this pull request introduces no advisory; main's daily scheduled scan judges that lockfile."
echo "compare=false" >> "$GITHUB_OUTPUT"
exit 0
fi
for side in base head; do
if [ "$side" = base ]; then rev="$base"; else rev=HEAD; fi
mkdir -p ".osv-compare/$side"
git show "$rev:pnpm-lock.yaml" > ".osv-compare/$side/pnpm-lock.yaml"
git show "$rev:osv-scanner.toml" > ".osv-compare/$side/osv-scanner.toml"
done
echo "compare=true" >> "$GITHUB_OUTPUT"
# Both scans may exit non-zero (findings), so neither stops the job; the
# judge below reads each result file against its step's outcome.
# `--config` is explicit because the scanner looks for osv-scanner.toml
# only in the scanned lockfile's own directory (measured on 2.5.0: a
# ledger one directory up is not applied).
- name: OSV-Scanner on the merge base's lockfile (pull request)
id: osv-scan-base
if: steps.osv-stage.outputs.compare == 'true'
continue-on-error: true
uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # untagged, v2.5.0 image
with:
scan-args: |-
--config=.osv-compare/base/osv-scanner.toml
--format=json
--output-file=.osv-compare/base/results.json
--lockfile=.osv-compare/base/pnpm-lock.yaml
- name: OSV-Scanner on the pull request's lockfile (pull request)
id: osv-scan-head
if: steps.osv-stage.outputs.compare == 'true'
continue-on-error: true
uses: google/osv-scanner-action/osv-scanner-action@f4cfcc01edc9c8b756a9b873b7a623ca674da51e # untagged, v2.5.0 image
with:
scan-args: |-
--config=.osv-compare/head/osv-scanner.toml
--format=json
--output-file=.osv-compare/head/results.json
--lockfile=.osv-compare/head/pnpm-lock.yaml
- name: Judge the pull request's OSV findings against the merge base's (pull request)
if: steps.osv-stage.outputs.compare == 'true'
env:
BASE_OUTCOME: ${{ steps.osv-scan-base.outcome }}
HEAD_OUTCOME: ${{ steps.osv-scan-head.outcome }}
BASE_SHA: ${{ steps.osv-stage.outputs.base }}
# Read-only use: the open-issue search that names an inherited
# advisory's finding card. The verdict never depends on it.
GITHUB_TOKEN: ${{ github.token }}
run: |
node scripts/osv-base-relative.mjs \
--base-results .osv-compare/base/results.json --base-outcome "$BASE_OUTCOME" \
--head-results .osv-compare/head/results.json --head-outcome "$HEAD_OUTCOME" \
--base-sha "$BASE_SHA"
- name: List outdated packages
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
run: |
pnpm outdated --recursive || true