-
Notifications
You must be signed in to change notification settings - Fork 18
## Summary #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
## Summary #192
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| #!/usr/bin/env node | ||
| // Evaluates `npm audit --json` output and fails the build only on high/critical | ||
| // advisories that are NOT explicitly allowlisted. | ||
| // | ||
| // Why this exists: | ||
| // 1. The project stubs out `@posthog/cli` via an aliased override | ||
| // ("@posthog/cli": "npm:empty-npm-package@1.0.0"). npm 10 (bundled with | ||
| // Node 20) crashes on aliased overrides during audit with | ||
| // "Invalid comparator", so the workflow runs audit with npm 11. | ||
| // 2. Some high-severity advisories have no safe upstream fix yet and cannot be | ||
| // patched without breaking changes. Those are documented below and allowed | ||
| // until an upstream release resolves them. | ||
| // | ||
| // Usage: node check-audit.mjs <path-to-npm-audit-json> | ||
|
|
||
| import { readFileSync } from "node:fs"; | ||
|
|
||
| // Advisories that are known, reviewed, and intentionally allowed because no | ||
| // non-breaking fix is available yet. Keep this list short and time-bound. | ||
| const ALLOWLIST = [ | ||
| { | ||
| url: "https://github.com/advisories/GHSA-34r5-q4jw-r36m", | ||
| package: "samlify", | ||
| reason: | ||
| "Surfaced transitively via @better-auth/sso, which pins samlify '~2.10.2'. " + | ||
| "The patched samlify (>=2.13.0) is outside that range, so forcing it risks " + | ||
| "breaking SAML/SSO. No @better-auth/sso release bumps samlify yet (latest 1.6.11). " + | ||
| "Re-evaluate when @better-auth/sso ships a patched samlify.", | ||
| }, | ||
| ]; | ||
|
|
||
| const path = process.argv[2]; | ||
| if (!path) { | ||
| console.error("Usage: check-audit.mjs <audit-json-file>"); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| let report; | ||
| try { | ||
| report = JSON.parse(readFileSync(path, "utf8")); | ||
| } catch (err) { | ||
| console.error(`Could not read/parse audit JSON at ${path}: ${err.message}`); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| // npm prints an `error` object when audit itself failed to run (e.g. the | ||
| // "Invalid comparator" crash on older npm). Treat that as a hard failure so we | ||
| // never silently skip the gate. | ||
| if (report.error) { | ||
| console.error(`npm audit failed to run: ${report.error.summary || JSON.stringify(report.error)}`); | ||
| process.exit(2); | ||
| } | ||
|
|
||
| const allowedUrls = new Set(ALLOWLIST.map((a) => a.url)); | ||
| const vulns = report.vulnerabilities || {}; | ||
|
|
||
| // Collect concrete advisory objects (high/critical) from every vulnerability's | ||
| // `via` chain. Transitive-only entries (whose `via` is just a package name) | ||
| // carry no advisory object and are covered once their root advisory is allowed. | ||
| const advisories = new Map(); // url -> { url, title, severity } | ||
| for (const vuln of Object.values(vulns)) { | ||
| for (const via of vuln.via || []) { | ||
| if (typeof via !== "object") continue; | ||
| if (via.severity !== "high" && via.severity !== "critical") continue; | ||
| if (via.url) advisories.set(via.url, { url: via.url, title: via.title, severity: via.severity }); | ||
| } | ||
| } | ||
|
|
||
| const blocking = []; | ||
| const allowed = []; | ||
| for (const adv of advisories.values()) { | ||
| (allowedUrls.has(adv.url) ? allowed : blocking).push(adv); | ||
| } | ||
|
|
||
| if (allowed.length) { | ||
| console.log("Allowlisted high/critical advisories (not blocking):"); | ||
| for (const a of allowed) console.log(` - [${a.severity}] ${a.title} (${a.url})`); | ||
| } | ||
|
|
||
| if (blocking.length) { | ||
| console.error("\nBlocking high/critical advisories:"); | ||
| for (const a of blocking) console.error(` - [${a.severity}] ${a.title} (${a.url})`); | ||
| console.error( | ||
| "\nResolve these (bump/override the affected package) or, if there is no safe fix, " + | ||
| "add the advisory to the ALLOWLIST in .github/scripts/check-audit.mjs with justification.", | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| console.log(`\nNo blocking high/critical advisories (${allowed.length} allowlisted).`); | ||
| process.exit(0); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,8 +46,14 @@ jobs: | |
| id: audit | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| npm audit --audit-level=high 2>&1 | tee audit-output.txt | ||
| set -uo pipefail | ||
| # npm 10 (bundled with Node 20) crashes on aliased overrides | ||
| # ("npm:...") during audit with "Invalid comparator"; npm 11 handles | ||
| # them. Run audit with npm 11 and evaluate the result via a script | ||
| # that fails on high/critical advisories except documented allowlisted | ||
| # ones (see .github/scripts/check-audit.mjs). | ||
| npx -y npm@11 audit --json > audit-output.json 2> audit-error.txt || true | ||
| node .github/scripts/check-audit.mjs audit-output.json 2>&1 | tee audit-output.txt | ||
|
Comment on lines
+55
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: As of May 29, 2026, the latest version of npm 11 is 11.16.0 [1]. The required Node.js engines range for npm 11 is defined as ^20.17.0 || >=22.9.0 [2]. This range was established to support newer Node.js releases while ensuring compatibility with supported LTS versions [2]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
# Show the workflow section around the referenced lines and any setup-node config nearby.
FILE=".github/workflows/pr-validation.yml"
if [ -f "$FILE" ]; then
echo "== $FILE =="
nl -ba "$FILE" | sed -n '1,140p'
else
echo "Missing $FILE" >&2
exit 1
fi
# Also show the check-audit script for context on what error outputs it consumes.
SCRIPT=".github/scripts/check-audit.mjs"
if [ -f "$SCRIPT" ]; then
echo "== $SCRIPT =="
nl -ba "$SCRIPT" | sed -n '1,200p'
else
echo "Missing $SCRIPT" >&2
exit 1
fiRepository: reqcore-inc/reqcore Length of output: 166 🏁 Script executed: #!/bin/bash
set -euo pipefail
FILE=".github/workflows/pr-validation.yml"
if [ -f "$FILE" ]; then
echo "== $FILE =="
awk 'NR>=1 && NR<=160 {printf "%d:%s\n", NR, $0}' "$FILE"
else
echo "Missing $FILE" >&2
exit 1
fi
echo
echo "== setup-node occurrences =="
rg -n "setup-node|node-version" .github/workflows/pr-validation.yml .github/workflows -S || true
echo
SCRIPT=".github/scripts/check-audit.mjs"
if [ -f "$SCRIPT" ]; then
echo "== $SCRIPT =="
awk 'NR>=1 && NR<=220 {printf "%d:%s\n", NR, $0}' "$SCRIPT"
else
echo "Missing $SCRIPT" >&2
exit 1
fiRepository: reqcore-inc/reqcore Length of output: 8324 Pin
🤖 Prompt for AI Agents |
||
|
|
||
| - name: Build | ||
| id: build | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High/critical advisories without a
urlare silently dropped from the gate.A
viaadvisory object is only recorded whenvia.urlis truthy (Line 65). Any high/critical advisory object lacking aurlis skipped entirely, so it neither blocks nor gets reported. It also can't be allowlisted (the allowlist keys onurl), so the safe default for such an entry is to block. In practice npm/GHSA advisories carry aurl, hence the low priority, but for a security gate it's worth failing closed.🛡️ Fail closed when a high/critical advisory has no url
for (const vuln of Object.values(vulns)) { for (const via of vuln.via || []) { if (typeof via !== "object") continue; if (via.severity !== "high" && via.severity !== "critical") continue; - if (via.url) advisories.set(via.url, { url: via.url, title: via.title, severity: via.severity }); + // Use the url as the dedup/allowlist key; fall back to a synthetic key so + // an advisory without a url still surfaces and blocks (it can't be allowlisted). + const key = via.url || `${via.source ?? via.title ?? "unknown"}`; + advisories.set(key, { url: via.url || "(no url)", title: via.title, severity: via.severity }); } }📝 Committable suggestion
🤖 Prompt for AI Agents