From 2eb5d74f5963d825d47faed814bf2cd458e7cdd7 Mon Sep 17 00:00:00 2001 From: dc Date: Wed, 28 Oct 2026 12:17:36 +0000 Subject: [PATCH] chore(ci): gate deploys on new advisories, restore lint and typecheck Second review follow-up. Audit gate: a flat `npm audit --audit-level=` cannot work here at any level - the tree reports 102 advisories (12 low, 38 moderate, 52 high) and `--omit=dev` does not change the counts, because gatsby itself is a runtime dependency of the build. Gating on the tree as a whole stops publishing permanently; ignoring it lets a new advisory through. scripts/audit-gate.mjs fails on any critical/high advisory that is not in scripts/audit-baseline.json, which carries an owner and a reason, so today's set is a tracked exception and anything new blocks the deploy. Verified both ways: exit 0 on the committed baseline, exit 1 with an entry removed. Lint and typecheck: the deleted GitHub workflow was the only place they ran, so `npm run typecheck` and `npm run lint` are now steps in the publish workflow, ahead of the build. Lint could not simply be switched on - prettier reported 8 pre-existing files on main, all single-line indentation - so those are fixed here and the check is clean rather than baselined. Verified locally in the order the workflow runs: audit gate 0, typecheck clean, lint clean (8 files reformatted, indentation only), build 136 pages. --- .gitea/workflows/publish.yml | 25 +- scripts/audit-baseline.json | 940 ++++++++++++++++++++++++++++++++++ scripts/audit-gate.mjs | 107 ++++ src/pages/404.tsx | 2 +- src/pages/about.tsx | 2 +- src/pages/contact.tsx | 2 +- src/pages/faq.tsx | 2 +- src/pages/index.tsx | 2 +- src/templates/blog-list.tsx | 2 +- src/templates/blog-search.tsx | 2 +- src/templates/feed-list.tsx | 2 +- 11 files changed, 1072 insertions(+), 16 deletions(-) create mode 100644 scripts/audit-baseline.json create mode 100644 scripts/audit-gate.mjs diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index 729c66b..eac30b9 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -26,14 +26,23 @@ jobs: - name: Install dependencies run: npm ci --legacy-peer-deps - # Blocking gate, at CRITICAL only. `npm audit` fails on the severity you - # name, so `--audit-level=low` (or the default) would stop every deploy - # today: the Gatsby 5 tree carries ~100 advisories at low/moderate/high - # that have no non-breaking fix, and publishing would simply never run. - # Critical is the line worth stopping a deploy for, and the tree is clear - # of it - keep it that way rather than gating on noise nobody will action. - - name: Audit for critical vulnerabilities - run: npm audit --audit-level=critical + # Blocking gate. `npm audit --audit-level=` is all-or-nothing and this + # tree cannot pass a flat gate at any level: the Gatsby 5 build toolchain + # carries ~100 advisories with no non-breaking fix, so gating on the tree + # as a whole stops publishing permanently. scripts/audit-gate.mjs instead + # accepts the known set (scripts/audit-baseline.json, with an owner and a + # reason) and FAILS on any new critical/high advisory. + - name: Audit for new critical/high advisories + run: node scripts/audit-gate.mjs + + # The prettier and type checks used to live in a GitHub workflow that + # never ran on this forge; keep them ahead of the build so a type or + # formatting error blocks the deploy rather than shipping. + - name: Typecheck + run: npm run typecheck + + - name: Check prettier offences + run: npm run lint - name: Build run: npm run build diff --git a/scripts/audit-baseline.json b/scripts/audit-baseline.json new file mode 100644 index 0000000..a17da35 --- /dev/null +++ b/scripts/audit-baseline.json @@ -0,0 +1,940 @@ +{ + "$comment": "Advisories at critical/high accepted for this tree. scripts/audit-gate.mjs fails the build on anything not listed here, so removing an entry re-arms the gate for that advisory. Regenerate with: node scripts/audit-gate.mjs --write-baseline", + "owner": "Site maintainers (allan@4gl.io)", + "reason": "Gatsby 5 build toolchain: no non-breaking fix available upstream, and these are build-time dependencies of a statically generated site. Re-check on each Gatsby major bump.", + "accepted": [ + { + "pkg": "@ardatan/relay-compiler", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@babel/plugin-transform-modules-systemjs", + "severity": "high", + "advisory": "GHSA-fv7c-fp4j-7gwp", + "title": "@babel/plugin-transform-modules-systemjs generates arbitrary code when compiling malicious input", + "range": ">=7.12.0 <=7.29.3" + }, + { + "pkg": "@graphql-codegen/add", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-codegen/core", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-codegen/plugin-helpers", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-codegen/schema-ast", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-codegen/typescript-operations", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-codegen/typescript", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-codegen/visitor-plugin-common", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/code-file-loader", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/graphql-tag-pluck", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/load", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/merge", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/relay-operation-optimizer", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/schema", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "@graphql-tools/utils", + "severity": "high", + "advisory": "GHSA-7mx3-vvmw-hjmv", + "title": "GraphQL Tools has prototype pollution in well-established utility function `mergeDeep`", + "range": "<=12.0.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-35jp-ww65-95wh", + "title": "axios Vulnerable to Full Man-in-the-Middle via Prototype Pollution Gadget in `config.proxy`", + "range": ">=1.0.0 <1.16.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-3g43-6gmg-66jw", + "title": "axios Vulnerable to Credential Theft and Response Hijacking via Prototype Pollution Gadget in Config Merge", + "range": ">=1.0.0 <1.15.2" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-3p68-rc4w-qgx5", + "title": "Axios has a NO_PROXY Hostname Normalization Bypass that Leads to SSRF", + "range": ">=1.0.0 <1.15.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-3pq3-5fj3-cg6v", + "title": "Axios: HTTP/2 adapter bypasses configured DNS lookup and proxy controls", + "range": ">=1.13.0 <1.20.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-3w6x-2g7m-8v23", + "title": "Axios: Invisible JSON Response Tampering via Prototype Pollution Gadget in `parseReviver`", + "range": ">=1.0.0 <1.15.2" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-42h9-826w-cgv3", + "title": "Axios: Excessive recursion in formDataToJSON can cause denial of service", + "range": ">=1.0.0 <1.18.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-43fc-jf86-j433", + "title": "Axios is Vulnerable to Denial of Service via __proto__ Key in mergeConfig", + "range": ">=1.0.0 <=1.13.4" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-445q-vr5w-6q77", + "title": "Axios: CRLF Injection in multipart/form-data body via unsanitized blob.type in formDataToStream", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-4hqw-qxg8-jxx2", + "title": "Axios: Fetch Adapter Header Injection via Inherited FormData getHeaders", + "range": ">=1.12.0 <1.20.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-542g-h47m-68v8", + "title": "Axios: Denial of Service via Unhandled 'error' Event in HTTP/2 ClientHttp2Session Initialization", + "range": ">=1.13.0 <1.20.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-5c9x-8gcm-mpgx", + "title": "Axios' HTTP adapter-streamed uploads bypass maxBodyLength when maxRedirects: 0", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-62hf-57xw-28j9", + "title": "Axios: unbounded recursion in toFormData causes DoS via deeply nested request data", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-6chq-wfr3-2hj9", + "title": "Axios: Header Injection via Prototype Pollution", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-777c-7fjr-54vf", + "title": "Allocation of Resources Without Limits or Throttling in Axios", + "range": ">=1.7.0 <1.16.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-7q8q-rj6j-mhjq", + "title": "Axios: Nested axios option objects can consume polluted prototype values", + "range": ">=1.0.0 <1.18.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-898c-q2cr-xwhg", + "title": "axios has DoS & Header Injection via Prototype Pollution Read-Side Gadgets in axios merge functions", + "range": ">=1.0.0 <1.16.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-9fr6-4gfg-395g", + "title": "Axios: Prototype-Pollution Gadget in the Default Instance Allows Inherited Object.prototype.method to Override HTTP Method", + "range": ">=1.0.0 <1.20.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-fvcv-3m26-pcqx", + "title": "Axios has Unrestricted Cloud Metadata Exfiltration via Header Injection Chain", + "range": ">=1.0.0 <1.15.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-hfxv-24rg-xrqf", + "title": "Axios: Regular Expression Denial of Service (ReDoS) via Cookie Name Injection", + "range": ">=1.0.0 <1.16.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-j5f8-grm9-p9fc", + "title": "Axios: Proxy-Authorization header leaks to redirect target when proxy is re-evaluated to direct connection", + "range": ">=1.0.0 <1.16.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-j8rh-479h-cp32", + "title": "Axios: Header Injection via Inherited headers After Minimal Interceptor", + "range": ">=1.0.0 <1.20.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-jqh4-m9w3-8hp9", + "title": "Axios: Fetch adapter `ReadableStream` uploads bypass `maxBodyLength`", + "range": ">=1.7.0 <1.18.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-m7pr-hjqh-92cm", + "title": "Axios: no_proxy bypass via IP alias allows SSRF", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-mmx7-hfxf-jppx", + "title": "Axios: Prototype pollution gadgets can alter axios request construction", + "range": ">=1.0.0 <1.18.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-mwf2-3pr3-8698", + "title": "Axios: HTTP/2 streamed uploads bypass `maxBodyLength`", + "range": ">=1.13.0 <1.18.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-p92q-9vqr-4j8v", + "title": "Axios: Proxy-Authorization Credential Leak to Origin Server Across HTTP-to-HTTPS Redirect in Axios Node.js HTTP Adapter", + "range": ">=1.0.0 <1.16.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-pf86-5x62-jrwf", + "title": "Axios: Prototype Pollution Gadgets - Response Tampering, Data Exfiltration, and Request Hijacking", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-pmv8-rq9r-6j72", + "title": "Axios: Deep formToJSON Key Recursion Can Cause Denial of Service", + "range": ">=1.0.0 <1.18.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-pmwg-cvhr-8vh7", + "title": "Axios: Incomplete Fix for CVE-2025-62718 — NO_PROXY Protection Bypassed via RFC 1122 Loopback Subnet (127.0.0.0/8) in Axios 1.15.0", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-q8qp-cvcw-x6jj", + "title": "Axios has prototype pollution read-side gadgets in HTTP adapter that allow credential injection and request hijacking", + "range": ">=1.0.0 <1.15.2" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-vf2m-468p-8v99", + "title": "Axios: HTTP adapter streamed responses bypass maxContentLength", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-vh66-26gq-q6x8", + "title": "Axios: Prototype pollution gadget in fetch adapter can alter outbound requests", + "range": ">=1.7.0 <1.20.0" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-w9j2-pvgh-6h63", + "title": "Axios: Authentication Bypass via Prototype Pollution Gadget in `validateStatus` Merge Strategy", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-xhjh-pmcv-23jw", + "title": "Axios: Null Byte Injection via Reverse-Encoding in AxiosURLSearchParams", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "axios", + "severity": "high", + "advisory": "GHSA-xx6v-rp6x-q39c", + "title": "Axios: XSRF Token Cross-Origin Leakage via Prototype Pollution Gadget in `withXSRFToken` Boolean Coercion", + "range": ">=1.0.0 <1.15.1" + }, + { + "pkg": "body-parser", + "severity": "high", + "advisory": "GHSA-27r3-jxmh-p828", + "title": "body-parser vulnerable to Denial of Service via trailing bytes after a compressed request body", + "range": "<1.20.9" + }, + { + "pkg": "body-parser", + "severity": "high", + "advisory": "GHSA-qwcr-r2fm-qrc7", + "title": "body-parser vulnerable to denial of service when url encoding is enabled", + "range": "<1.20.3" + }, + { + "pkg": "body-parser", + "severity": "high", + "advisory": "GHSA-v422-hmwv-36x6", + "title": "body-parser vulnerable to denial of service when invalid limit value silently disables size enforcement", + "range": "<1.20.6" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-3jxr-9vmj-r5cp", + "title": "brace-expansion: DoS via exponential-time expansion of consecutive non-expanding {} groups", + "range": "<1.1.16" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-6j4f-fj2g-mc7p", + "title": "brace-expansion: DoS via uncontrolled recursion in parseCommaParts causing stack exhaustion", + "range": "<1.1.19" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-f886-m6hf-6m8v", + "title": "brace-expansion: Zero-step sequence causes process hang and memory exhaustion", + "range": "<1.1.13" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-mh99-v99m-4gvg", + "title": "brace-expansion: DoS via unbounded expansion length causing an out-of-memory process crash", + "range": "<1.1.17" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-q2hr-2g5m-vwhr", + "title": "brace-expansion: Quadratic-time expansion of the `{a},b}` rewrite causes CPU denial of service", + "range": "<1.1.21" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-qhr7-859c-m2p7", + "title": "brace-expansion: DoS via uncontrolled recursion on nested brace groups causing stack exhaustion", + "range": "<1.1.20" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-rgw5-rvv9-x895", + "title": "brace-expansion: DoS via unbounded intermediate arrays, bypassing the CVE-2026-14257 mitigation", + "range": "<1.1.18" + }, + { + "pkg": "brace-expansion", + "severity": "high", + "advisory": "GHSA-v6h2-p8h4-qcjw", + "title": "brace-expansion Regular Expression Denial of Service vulnerability", + "range": ">=1.0.0 <=1.1.11" + }, + { + "pkg": "browserslist", + "severity": "high", + "advisory": "GHSA-73wf-gq98-2v4g", + "title": "Browserslist: Uncaught crash / prototype write via untrusted browserslist-stats.json custom stats (normalizeStats)", + "range": "<=4.28.6" + }, + { + "pkg": "browserslist", + "severity": "high", + "advisory": "GHSA-c83g-rgw3-j3cx", + "title": "Browserslist: Unbounded memory growth (no cache eviction) via distinct query results, leading to eventual OOM", + "range": "<=4.28.6" + }, + { + "pkg": "compression", + "severity": "high", + "advisory": "GHSA-vc2v-76pw-4v95", + "title": "compression vulnerable to Denial of Service via memory leak on premature response close", + "range": "<1.8.2" + }, + { + "pkg": "cross-spawn", + "severity": "high", + "advisory": "GHSA-3xgq-45jj-v275", + "title": "Regular Expression Denial of Service (ReDoS) in cross-spawn", + "range": ">=7.0.0 <7.0.5" + }, + { + "pkg": "engine.io", + "severity": "high", + "advisory": "GHSA-2gc4-cqfq-p2gv", + "title": "Socket.IO: Engine.IO Protocol Revision Mismatch DoS", + "range": ">=6.6.0 <6.6.10" + }, + { + "pkg": "engine.io", + "severity": "high", + "advisory": "GHSA-gr94-w7qr-f4j3", + "title": "Socket.IO: Engine.IO WebTransport SID DoS", + "range": ">=6.5.0 <6.6.7" + }, + { + "pkg": "engine.io", + "severity": "high", + "advisory": "GHSA-r635-g3xr-vw7x", + "title": "Socket.IO: Engine.IO Polling Transport Connection Exhaustion", + "range": ">=4.1.0 <6.6.7" + }, + { + "pkg": "express", + "severity": "high", + "advisory": "GHSA-qw6h-vgh9-j6wx", + "title": "express vulnerable to XSS via response.redirect()", + "range": "<4.20.0" + }, + { + "pkg": "flatted", + "severity": "high", + "advisory": "GHSA-25h7-pfq9-p65f", + "title": "flatted vulnerable to unbounded recursion DoS in parse() revive phase", + "range": "<3.4.0" + }, + { + "pkg": "flatted", + "severity": "high", + "advisory": "GHSA-rf6f-7fwh-wjgh", + "title": "Prototype Pollution via parse() in NodeJS flatted", + "range": "<=3.4.1" + }, + { + "pkg": "form-data", + "severity": "high", + "advisory": "GHSA-hmw2-7cc7-3qxx", + "title": "form-data: CRLF injection in form-data via unescaped multipart field names and filenames", + "range": ">=4.0.0 <4.0.6" + }, + { + "pkg": "gatsby-plugin-manifest", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "gatsby-plugin-sharp", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "gatsby-plugin-utils", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "gatsby-sharp", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "gatsby-transformer-sharp", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "gatsby", + "severity": "high", + "advisory": "transitive" + }, + { + "pkg": "http-cache-semantics", + "severity": "high", + "advisory": "GHSA-ch52-4w7c-c8xp", + "title": "http-cache-semantics max-stale handling can disclose cross-user cached responses", + "range": "<=4.2.0" + }, + { + "pkg": "immutable", + "severity": "high", + "advisory": "GHSA-v56q-mh7h-f735", + "title": "Immutable.js `List` 32-bit trie overflow → unrecoverable DoS", + "range": "<3.8.4" + }, + { + "pkg": "immutable", + "severity": "high", + "advisory": "GHSA-wf6x-7x77-mvgw", + "title": "Immutable is vulnerable to Prototype Pollution", + "range": "<3.8.3" + }, + { + "pkg": "immutable", + "severity": "high", + "advisory": "GHSA-xvcm-6775-5m9r", + "title": "Immutable: Hash-collision algorithmic complexity denial of service in Immutable.Map/Set", + "range": "<3.8.4" + }, + { + "pkg": "joi", + "severity": "high", + "advisory": "GHSA-6h2x-m376-mqjq", + "title": "joi: Quadratic regular-expression backtracking in `Joi.string().isoDate()`", + "range": ">=17.2.0 <17.13.7" + }, + { + "pkg": "joi", + "severity": "high", + "advisory": "GHSA-6w3j-5fw6-r9vr", + "title": "joi: Prototype pollution via a `__proto__` language key in custom messages", + "range": ">=17.2.0 <17.13.6" + }, + { + "pkg": "joi", + "severity": "high", + "advisory": "GHSA-gg4h-3hg2-grpc", + "title": "joi: object().rename() with a template target can set the validated object's prototype", + "range": ">=16.0.0 <17.13.5" + }, + { + "pkg": "joi", + "severity": "high", + "advisory": "GHSA-wr44-6hxh-3jwq", + "title": "joi messages compilation allows prototype replacement through __proto__ error codes", + "range": "<17.13.8" + }, + { + "pkg": "js-yaml", + "severity": "high", + "advisory": "GHSA-2883-xcg3-v3hh", + "title": "js-yaml: maxTotalMergeKeys does not limit CPU use for empty merge sources", + "range": ">=3.0.0 <3.15.2" + }, + { + "pkg": "js-yaml", + "severity": "high", + "advisory": "GHSA-52cp-r559-cp3m", + "title": "js-yaml: YAML merge-key chains can force quadratic CPU consumption", + "range": ">=3.0.0 <3.15.0" + }, + { + "pkg": "js-yaml", + "severity": "high", + "advisory": "GHSA-5p4m-2wfm-xmqj", + "title": "JS-YAML: Quadratic CPU consumption in !!omap resolution (3.x and 4.x) — CVE-2026-59870 fix not backported", + "range": ">=3.0.0 <3.15.1" + }, + { + "pkg": "js-yaml", + "severity": "high", + "advisory": "GHSA-h67p-54hq-rp68", + "title": "JS-YAML: Quadratic-complexity DoS in merge key handling via repeated aliases", + "range": "<3.15.0" + }, + { + "pkg": "js-yaml", + "severity": "high", + "advisory": "GHSA-mh29-5h37-fv8m", + "title": "js-yaml has prototype pollution in merge (<<)", + "range": "<3.14.2" + }, + { + "pkg": "lodash", + "severity": "high", + "advisory": "GHSA-f23m-r3pf-42rh", + "title": "lodash vulnerable to Prototype Pollution via array path bypass in `_.unset` and `_.omit`", + "range": "<=4.17.23" + }, + { + "pkg": "lodash", + "severity": "high", + "advisory": "GHSA-r5fr-rjxr-66jc", + "title": "lodash vulnerable to Code Injection via `_.template` imports key names", + "range": ">=4.0.0 <=4.17.23" + }, + { + "pkg": "lodash", + "severity": "high", + "advisory": "GHSA-xxjr-mmjv-4gpg", + "title": "Lodash has Prototype Pollution Vulnerability in `_.unset` and `_.omit` functions", + "range": ">=4.0.0 <=4.17.22" + }, + { + "pkg": "minimatch", + "severity": "high", + "advisory": "GHSA-23c5-xmqv-rm74", + "title": "minimatch ReDoS: nested *() extglobs generate catastrophically backtracking regular expressions", + "range": "<3.1.4" + }, + { + "pkg": "minimatch", + "severity": "high", + "advisory": "GHSA-3ppc-4f35-3m26", + "title": "minimatch has a ReDoS via repeated wildcards with non-matching literal in pattern", + "range": "<3.1.3" + }, + { + "pkg": "minimatch", + "severity": "high", + "advisory": "GHSA-7r86-cg39-jmmj", + "title": "minimatch has ReDoS: matchOne() combinatorial backtracking via multiple non-adjacent GLOBSTAR segments", + "range": "<3.1.3" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-3p4h-7m6x-2hcm", + "title": "Multer vulnerable to Denial of Service via incomplete cleanup of aborted uploads", + "range": ">=2.0.0-alpha.1 <2.2.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-535w-7cp7-47q4", + "title": "multer vulnerable to Denial of Service via oversized array index in field names", + "range": "<2.3.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-5528-5vmv-3xc2", + "title": "Multer Vulnerable to Denial of Service via Uncontrolled Recursion", + "range": "<2.1.1" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-72gw-mp4g-v24j", + "title": "Multer vulnerable to Denial of Service via deeply nested field names", + "range": ">=1.0.0 <2.2.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-q7qp-rjg7-q26m", + "title": "multer vulnerable to Denial of Service via a pending asynchronous fileFilter after request teardown", + "range": ">=2.0.0 <2.5.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-qvfw-j98x-7q72", + "title": "multer vulnerable to file size limit bypass via async fileFilter race condition", + "range": "<2.3.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-v52c-386h-88mc", + "title": "Multer vulnerable to Denial of Service via resource exhaustion", + "range": "<2.1.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-wc9g-mqfw-jrwm", + "title": "multer vulnerable to Denial of Service via crafted multipart field names", + "range": "<2.3.0" + }, + { + "pkg": "multer", + "severity": "high", + "advisory": "GHSA-xf7r-hgr6-v32p", + "title": "Multer vulnerable to Denial of Service via incomplete cleanup", + "range": "<2.1.0" + }, + { + "pkg": "nanoid", + "severity": "high", + "advisory": "GHSA-28wg-ghj8-5hjv", + "title": "nanoid: non-secure generators can loop indefinitely with negative size", + "range": "<3.3.16" + }, + { + "pkg": "nanoid", + "severity": "high", + "advisory": "GHSA-2v37-7h3g-55p8", + "title": "nanoid: custom generators can loop indefinitely when size is zero", + "range": "<3.3.18" + }, + { + "pkg": "nanoid", + "severity": "high", + "advisory": "GHSA-mwcw-c2x4-8c55", + "title": "Predictable results in nanoid generation when given non-integer values", + "range": "<3.3.8" + }, + { + "pkg": "nanoid", + "severity": "high", + "advisory": "GHSA-xwg4-73v4-xw9w", + "title": "nanoid: Integer Overflow or Wraparound", + "range": "<3.3.12" + }, + { + "pkg": "path-to-regexp", + "severity": "high", + "advisory": "GHSA-37ch-88jc-xwx2", + "title": "path-to-regexp vulnerable to Regular Expression Denial of Service via multiple route parameters", + "range": "<0.1.13" + }, + { + "pkg": "path-to-regexp", + "severity": "high", + "advisory": "GHSA-9wv6-86v2-598j", + "title": "path-to-regexp outputs backtracking regular expressions", + "range": "<0.1.10" + }, + { + "pkg": "path-to-regexp", + "severity": "high", + "advisory": "GHSA-rhx6-c78j-4q9w", + "title": "path-to-regexp contains a ReDoS", + "range": "<0.1.12" + }, + { + "pkg": "picomatch", + "severity": "high", + "advisory": "GHSA-3v7f-55p6-f55p", + "title": "Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching", + "range": "<2.3.2" + }, + { + "pkg": "picomatch", + "severity": "high", + "advisory": "GHSA-c2c7-rcm5-vvqj", + "title": "Picomatch has a ReDoS vulnerability via extglob quantifiers", + "range": "<2.3.2" + }, + { + "pkg": "postcss", + "severity": "high", + "advisory": "GHSA-6g55-p6wh-862q", + "title": "PostCSS: Arbitrary file read and information disclosure via attacker-controlled sourceMappingURL in CSS comments", + "range": "<=8.5.11" + }, + { + "pkg": "postcss", + "severity": "high", + "advisory": "GHSA-fxqj-rqcc-2cmp", + "title": "PostCSS: incomplete fix of GHSA-6g55-p6wh-862q — attacker-controlled sourceMappingURL reads arbitrary .map files when `from` is unset", + "range": "<=8.5.22" + }, + { + "pkg": "postcss", + "severity": "high", + "advisory": "GHSA-qx2v-qp2m-jg93", + "title": "PostCSS has XSS via Unescaped in its CSS Stringify Output", + "range": "<8.5.10" + }, + { + "pkg": "postcss", + "severity": "high", + "advisory": "GHSA-r28c-9q8g-f849", + "title": "PostCSS: Path Traversal in Previous Source Map Auto-Loading (sourceMappingURL) leads to Arbitrary .map File Disclosure", + "range": "<=8.5.17" + }, + { + "pkg": "probe-image-size", + "severity": "high", + "advisory": "GHSA-gjj5-9665-rwrc", + "title": "probe-image-size: Quadratic-time Denial of Service in the SVG Parser", + "range": "<=7.3.0" + }, + { + "pkg": "serialize-javascript", + "severity": "high", + "advisory": "GHSA-5c6j-r48x-rmvq", + "title": "Serialize JavaScript is Vulnerable to RCE via RegExp.flags and Date.prototype.toISOString()", + "range": "<=7.0.2" + }, + { + "pkg": "serialize-javascript", + "severity": "high", + "advisory": "GHSA-m5mm-p8h7-m29v", + "title": "Serialize JavaScript: Cross-site scripting (XSS) via unescaped =5.0.0 <7.0.5" + }, + { + "pkg": "sharp", + "severity": "high", + "advisory": "GHSA-f88m-g3jw-g9cj", + "title": "sharp inherited vulnerabilities in libvips: CVE-2026-33327, CVE-2026-33328, CVE-2026-35590, CVE-2026-35591", + "range": "<0.35.0" + }, + { + "pkg": "sharp", + "severity": "high", + "advisory": "GHSA-rgj7-g3m4-5g8c", + "title": "sharp: Vulnerabilities in libheif: GHSA-g89c-p67h-r497 and GHSA-2jg2-4ch7-h545", + "range": "<0.35.4" + }, + { + "pkg": "sharp", + "severity": "high", + "advisory": "GHSA-wq5f-xc86-pv6w", + "title": "sharp : Vulnerability in librsvg dependency CVE-2026-96889", + "range": "<0.35.5" + }, + { + "pkg": "socket.io-parser", + "severity": "high", + "advisory": "GHSA-2m8v-j782-fhvr", + "title": "Socket.IO: Zero-attachment Memory Exhaustion", + "range": ">=4.0.0 <4.2.7" + }, + { + "pkg": "socket.io-parser", + "severity": "high", + "advisory": "GHSA-677m-j7p3-52f9", + "title": "socket.io allows an unbounded number of binary attachments", + "range": ">=4.0.0 <4.2.6" + }, + { + "pkg": "source-map-js", + "severity": "high", + "advisory": "GHSA-68fv-2mgg-jv7q", + "title": "source-map-js allows event-loop denial of service through indexed source-map section offsets", + "range": ">=1.0.0 <1.2.2" + }, + { + "pkg": "svgo", + "severity": "high", + "advisory": "GHSA-2p49-hgcm-8545", + "title": "SVGO removeScripts plugin leaves some executable scripts intact", + "range": ">=1.0.0 <2.8.3" + }, + { + "pkg": "svgo", + "severity": "high", + "advisory": "GHSA-4vpr-x523-8j87", + "title": "SVGO: removeScripts incompletely sanitizes executable HTML in SVG foreignObject elements", + "range": ">=1.0.0 <2.8.4" + }, + { + "pkg": "svgo", + "severity": "high", + "advisory": "GHSA-w27v-7q3p-w38r", + "title": "SVGO: removeScripts allows executable links through namespace and control-character bypasses", + "range": ">=1.0.0 <2.8.4" + }, + { + "pkg": "svgo", + "severity": "high", + "advisory": "GHSA-xpqw-6gx7-v673", + "title": "SVGO DoS through entity expansion in DOCTYPE (Billion Laughs)", + "range": ">=2.1.0 <2.8.1" + }, + { + "pkg": "tmp", + "severity": "high", + "advisory": "GHSA-52f5-9888-hmc6", + "title": "tmp allows arbitrary temporary file / directory write via symbolic link `dir` parameter", + "range": "<=0.2.3" + }, + { + "pkg": "tmp", + "severity": "high", + "advisory": "GHSA-ph9p-34f9-6g65", + "title": "tmp has Path Traversal via unsanitized prefix/postfix that enables directory escape", + "range": "<0.2.6" + }, + { + "pkg": "webpack-dev-middleware", + "severity": "high", + "advisory": "GHSA-g84c-rxfj-3j2c", + "title": "webpack-dev-middleware vulnerable to Path Traversal via non-slash-terminated publicPath", + "range": "<7.4.5" + }, + { + "pkg": "ws", + "severity": "high", + "advisory": "GHSA-58qx-3vcg-4xpx", + "title": "ws: Uninitialized memory disclosure", + "range": ">=8.0.0 <8.20.1" + }, + { + "pkg": "ws", + "severity": "high", + "advisory": "GHSA-96hv-2xvq-fx4p", + "title": "ws: Memory exhaustion DoS from tiny fragments and data chunks", + "range": ">=8.0.0 <8.21.0" + } + ] +} diff --git a/scripts/audit-gate.mjs b/scripts/audit-gate.mjs new file mode 100644 index 0000000..dfbe9d5 --- /dev/null +++ b/scripts/audit-gate.mjs @@ -0,0 +1,107 @@ +#!/usr/bin/env node +/** + * Audit gate: fail the build on any advisory that is not a tracked exception. + * + * `npm audit --audit-level=` is all-or-nothing, and this tree cannot pass a + * flat gate: the Gatsby 5 build toolchain carries ~100 advisories at + * low/moderate/high with no non-breaking fix, because gatsby is a runtime + * dependency of the build. Blocking on any of those stops publishing + * permanently; ignoring the tree lets a genuinely new advisory through. So the + * known set is committed to audit-baseline.json with an owner and a reason, + * and this script fails only when something NEW appears at or above the + * threshold. + * + * Usage: node scripts/audit-gate.mjs [--write-baseline] + */ +import { execFileSync } from 'node:child_process'; +import { readFileSync, writeFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = dirname(fileURLToPath(import.meta.url)); +const BASELINE = join(HERE, 'audit-baseline.json'); +const BLOCKING = new Set(['critical', 'high']); + +/** npm audit exits non-zero when it finds anything, so ignore the status. */ +function auditReport() { + let out; + try { + out = execFileSync('npm', ['audit', '--json', '--omit=optional'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + maxBuffer: 32 * 1024 * 1024 + }); + } catch (err) { + out = err.stdout; + if (!out) throw err; + } + return JSON.parse(out); +} + +/** Flatten the report to one key per package/severity/advisory triple. */ +function currentFindings(report) { + const findings = new Map(); + for (const [pkg, v] of Object.entries(report.vulnerabilities ?? {})) { + if (!BLOCKING.has(v.severity)) continue; + const advisories = (v.via ?? []).filter((x) => typeof x === 'object'); + if (advisories.length === 0) { + // via is a chain of parent package names - attribute to the package. + findings.set(`${pkg}|${v.severity}|transitive`, { pkg, severity: v.severity, advisory: 'transitive' }); + continue; + } + for (const a of advisories) { + const id = (a.url ?? '').split('/').pop() || a.title || 'unknown'; + findings.set(`${pkg}|${v.severity}|${id}`, { pkg, severity: v.severity, advisory: id, title: a.title, range: a.range }); + } + } + return findings; +} + +const report = auditReport(); +const current = currentFindings(report); + +if (process.argv.includes('--write-baseline')) { + const accepted = [...current.values()].sort((a, b) => `${a.pkg}${a.advisory}`.localeCompare(`${b.pkg}${b.advisory}`)); + writeFileSync( + BASELINE, + JSON.stringify( + { + $comment: + 'Advisories at critical/high accepted for this tree. scripts/audit-gate.mjs fails the build on anything not listed here, so removing an entry re-arms the gate for that advisory. Regenerate with: node scripts/audit-gate.mjs --write-baseline', + owner: 'Site maintainers (allan@4gl.io)', + reason: + 'Gatsby 5 build toolchain: no non-breaking fix available upstream, and these are build-time dependencies of a statically generated site. Re-check on each Gatsby major bump.', + accepted + }, + null, + 2 + ) + '\n' + ); + console.log(`wrote ${accepted.length} accepted advisories to ${BASELINE}`); + process.exit(0); +} + +const baseline = JSON.parse(readFileSync(BASELINE, 'utf8')); +const accepted = new Set((baseline.accepted ?? []).map((a) => `${a.pkg}|${a.severity}|${a.advisory}`)); + +const fresh = [...current.entries()].filter(([key]) => !accepted.has(key)); +const resolved = [...accepted].filter((key) => !current.has(key)); + +for (const [, f] of fresh) { + console.error(`NEW ${f.severity.toUpperCase()}: ${f.pkg} - ${f.title ?? f.advisory} (${f.advisory})`); +} + +if (resolved.length > 0) { + // Not a failure: a bumped or removed dependency closes advisories, and the + // baseline should be trimmed in the same change that closes them. + console.log(`${resolved.length} baseline entries no longer present - consider re-running with --write-baseline:`); + for (const key of resolved) console.log(` stale: ${key}`); +} + +if (fresh.length > 0) { + console.error(`\n${fresh.length} untracked advisory/ies at critical or high.`); + console.error('Fix it, or add it to scripts/audit-baseline.json with a reason and an owner.'); + process.exit(1); +} + +console.log(`audit gate: no new critical/high advisories (${current.size} tracked in baseline)`); diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 9e8c5ac..e0a37b4 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -14,7 +14,7 @@ const NotFound: React.FC> = ({ pageContext }) => ( -
+
> = ({ location }) => { heading="Behind Data Controller" desc="Data Controller is a product of 4GL, a brand of Bowe IO Ltd" > -
+
About Data Controller diff --git a/src/pages/contact.tsx b/src/pages/contact.tsx index dc68eb2..3371d08 100644 --- a/src/pages/contact.tsx +++ b/src/pages/contact.tsx @@ -58,7 +58,7 @@ const Contact: React.FC> = ({ location }) => { heading="Book a Demo" desc="Schedule a demonstration of the Data Controller to see our latest features and explore use cases" > -
+
Schedule a Call diff --git a/src/pages/faq.tsx b/src/pages/faq.tsx index c9898cc..b0bbd4e 100644 --- a/src/pages/faq.tsx +++ b/src/pages/faq.tsx @@ -20,7 +20,7 @@ const Faq: React.FC> = ({ location }) => { heading="Data Controller – FAQ" desc="Most common user questions and answers regarding the Data Controller" > -
+
> = ({ data, location }) => { heading="Flexible and Secure Data Modification" desc="Data Controller for SAS® enables business users to update IT owned source tables, secured by review / approve workflow and full audit trail." > -
+
diff --git a/src/templates/blog-list.tsx b/src/templates/blog-list.tsx index 4c1e691..6c81b54 100644 --- a/src/templates/blog-list.tsx +++ b/src/templates/blog-list.tsx @@ -69,7 +69,7 @@ const BlogListTemplate: React.FC> = ({ heading="Data Controller’s Knowledge Base" desc="A section dedicated to news, updates and educational pieces." > -
+
diff --git a/src/templates/blog-search.tsx b/src/templates/blog-search.tsx index 34a3d1a..309c53c 100644 --- a/src/templates/blog-search.tsx +++ b/src/templates/blog-search.tsx @@ -71,7 +71,7 @@ const BlogSearchTemplate: React.FC> = ({ heading="Data Controller’s Knowledge Base" desc="A section dedicated to news, updates and educational pieces." > -
+
diff --git a/src/templates/feed-list.tsx b/src/templates/feed-list.tsx index db64f40..fdb69d4 100644 --- a/src/templates/feed-list.tsx +++ b/src/templates/feed-list.tsx @@ -68,7 +68,7 @@ const FeedListTemplate: React.FC> = ({ heading="Data Controller’s Feed" desc="Announcements and social media style updates from the Data Controller team." > -
+
-- 2.53.0