7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
193 lines
5.8 KiB
Python
193 lines
5.8 KiB
Python
"""Tests for .github/scripts/npm_audit_allowlist.py — the npm-audit release-gate
|
|
allowlist filter.
|
|
|
|
This script is a security gate: it must (1) FAIL (exit 1) on any non-allowlisted
|
|
moderate-or-higher vulnerability, (2) resolve npm's transitive ``via`` chains so
|
|
a parent flagged only because of an allowlisted leaf is itself handled, and
|
|
(3) FAIL SAFE on any input that is not a genuine npm audit report (a
|
|
registry/network error returns valid JSON with no report, and npm's non-zero
|
|
exit is swallowed by the workflow's ``|| true``). These tests pin all three.
|
|
|
|
Run via subprocess to exercise the real exit-code contract the workflow relies
|
|
on, and because the allowlist is read from the environment at import time.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
SCRIPT = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ ".github"
|
|
/ "scripts"
|
|
/ "npm_audit_allowlist.py"
|
|
)
|
|
|
|
ALLOW = "GHSA-aaaa-aaaa-aaaa" # the single allowlisted advisory under test
|
|
|
|
|
|
def run(payload, allowlist=ALLOW):
|
|
"""Run the filter with *payload* (str or dict) on stdin; return exit code."""
|
|
stdin = payload if isinstance(payload, str) else json.dumps(payload)
|
|
proc = subprocess.run(
|
|
[sys.executable, str(SCRIPT)],
|
|
input=stdin,
|
|
capture_output=True,
|
|
text=True,
|
|
env={**os.environ, "AUDIT_ALLOWLIST": allowlist},
|
|
)
|
|
return proc.returncode
|
|
|
|
|
|
def report(vulns):
|
|
"""Wrap a vulnerabilities map in a minimal valid npm v7+ audit report."""
|
|
return {"auditReportVersion": 2, "vulnerabilities": vulns, "metadata": {}}
|
|
|
|
|
|
def advisory(ghsa, severity="moderate"):
|
|
return {
|
|
"severity": severity,
|
|
"via": [{"url": f"https://github.com/advisories/{ghsa}"}],
|
|
}
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Passes the gate (exit 0)
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_clean_report_passes():
|
|
assert run(report({})) == 0
|
|
|
|
|
|
def test_allowlisted_leaf_passes():
|
|
assert run(report({"js-yaml": advisory(ALLOW)})) == 0
|
|
|
|
|
|
def test_via_chain_parent_passes():
|
|
# Parent is vulnerable ONLY because its via-string points to the
|
|
# allowlisted leaf — must resolve and pass.
|
|
payload = report(
|
|
{
|
|
"@lhci/utils": {"severity": "moderate", "via": ["js-yaml"]},
|
|
"js-yaml": advisory(ALLOW),
|
|
}
|
|
)
|
|
assert run(payload) == 0
|
|
|
|
|
|
def test_low_severity_only_passes():
|
|
# Below the moderate threshold — ignored, like `npm audit --audit-level`.
|
|
assert run(report({"x": advisory("GHSA-bbbb-bbbb-bbbb", "low")})) == 0
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Fails the gate (exit 1) — real vulnerabilities
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_non_allowlisted_moderate_fails():
|
|
assert run(report({"evil": advisory("GHSA-bbbb-bbbb-bbbb")})) == 1
|
|
|
|
|
|
@pytest.mark.parametrize("sev", ["high", "critical"])
|
|
def test_high_and_critical_fail(sev):
|
|
assert run(report({"evil": advisory("GHSA-bbbb-bbbb-bbbb", sev)})) == 1
|
|
|
|
|
|
def test_coattail_mixed_via_fails():
|
|
# A package reachable via BOTH the allowlisted leaf AND a separate
|
|
# non-allowlisted advisory must NOT be laundered through the allowlist.
|
|
payload = report(
|
|
{
|
|
"evil": {
|
|
"severity": "moderate",
|
|
"via": [
|
|
{
|
|
"url": "https://github.com/advisories/GHSA-bbbb-bbbb-bbbb"
|
|
},
|
|
"js-yaml",
|
|
],
|
|
},
|
|
"js-yaml": advisory(ALLOW),
|
|
}
|
|
)
|
|
assert run(payload) == 1
|
|
|
|
|
|
def test_cve_only_no_ghsa_fails():
|
|
# An advisory with no GHSA cannot be matched against a GHSA allowlist —
|
|
# fail safe rather than silently drop it.
|
|
payload = report(
|
|
{
|
|
"x": {
|
|
"severity": "high",
|
|
"via": [{"url": "https://nvd.nist.gov/CVE-1"}],
|
|
}
|
|
}
|
|
)
|
|
assert run(payload) == 1
|
|
|
|
|
|
def test_empty_allowlist_fails_even_on_listed_advisory():
|
|
assert run(report({"js-yaml": advisory(ALLOW)}), allowlist="") == 1
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Fail SAFE on non-report / malformed input (gate red, never silently green)
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_registry_error_object_fails():
|
|
# The real fail-open case: npm registry/network error returns valid JSON
|
|
# with no report, and `|| true` swallows npm's non-zero exit.
|
|
err = {"message": "request failed, reason: ETIMEDOUT", "error": {}}
|
|
assert run(err) == 1
|
|
|
|
|
|
def test_report_missing_audit_version_fails():
|
|
assert run({"message": "err", "vulnerabilities": {}}) == 1
|
|
|
|
|
|
def test_report_missing_vulnerabilities_fails():
|
|
assert run({"auditReportVersion": 2}) == 1
|
|
|
|
|
|
def test_non_dict_vulnerabilities_fails():
|
|
assert run({"auditReportVersion": 2, "vulnerabilities": ["x"]}) == 1
|
|
|
|
|
|
def test_empty_stdin_fails():
|
|
assert run("") == 1
|
|
|
|
|
|
def test_malformed_json_fails():
|
|
assert run("not json at all") == 1
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# GHSA extraction robustness
|
|
# --------------------------------------------------------------------------
|
|
|
|
|
|
def test_ghsa_url_with_trailing_path_is_parsed_cleanly():
|
|
# A URL carrying a trailing path/query must extract just the GHSA token,
|
|
# so it still matches the allowlist exactly (no over- or under-match).
|
|
payload = report(
|
|
{
|
|
"js-yaml": {
|
|
"severity": "moderate",
|
|
"via": [
|
|
{
|
|
"url": f"https://github.com/advisories/{ALLOW}/extra?ref=x"
|
|
}
|
|
],
|
|
}
|
|
}
|
|
)
|
|
assert run(payload) == 0
|