5 Commits
Author SHA1 Message Date
allan 5f3c6f30a9 Merge pull request 'chore(ci): gate deploys on new advisories, restore lint and typecheck' (#32) from chore/audit-gate-and-lint-gates into main
publish / Build-and-publish (push) Successful in 4m24s
Reviewed-on: #32
2026-10-28 12:32:01 +00:00
dc 2eb5d74f59 chore(ci): gate deploys on new advisories, restore lint and typecheck
Second review follow-up.

Audit gate: a flat `npm audit --audit-level=<x>` 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.
2026-10-28 12:17:50 +00:00
allan bef1b99c78 Merge pull request 'chore(deploy): drop the stale v6 surfer dependency and the gh-pages workflow' (#31) from chore/prune-stale-deploy-tooling into main
publish / Build-and-publish (push) Successful in 4m7s
Reviewed-on: #31
2026-10-28 12:16:33 +00:00
dc 5980e2caba chore(ci): regenerate the lockfile and gate deploys on critical vulns
Review follow-up.

Lockfile: the previous commit pruned the surfer subtree by hand and left
orphaned nested entries behind (node_modules/cloudron-surfer/node_modules/*,
plus children of removed parents such as @cloudron/pankow's filesize and
express-session's cookie). Regenerated from the unmodified main lockfile with
`npm install --package-lock-only --legacy-peer-deps`, so the lock is npm's own
output rather than an edit. No dangling entries remain.

Publish workflow: added a blocking `npm audit --audit-level=critical` step
between install and build. Critical rather than the suggested default/low: the
tree carries 102 advisories at low/moderate/high that have no non-breaking fix
(mostly the Gatsby 5 build toolchain), so a low gate would fail every deploy
from the first run. The tree is now clear of criticals, having bumped the two
that were live, both within their parents' existing ranges:

- proxy-addr 2.0.7 -> 2.0.8 (GHSA-jqcg-44mw-7w3h, via express ~2.0.7)
- shell-quote 1.8.1 -> 1.12.0 (GHSA-w7jw-789q-3m8p, GHSA-395f-4hp3-45gv, via
  react-dev-utils ^1.7.3)

.pre-commit-config.yaml: gitleaks hook pinned to v8.30.1 for staged-content
secret scanning. Developer-side only - `pre-commit install` per clone.

Verified: `npm ci --legacy-peer-deps` clean against the regenerated lock,
`npm audit --audit-level=critical` exits 0, build 136 pages, typecheck clean,
lint unchanged at the 8 pre-existing files.
2026-10-28 12:04:06 +00:00
dc 27a6d98d0c chore(deploy): drop the stale v6 surfer dependency and the gh-pages workflow
The publish workflow now deploys with `npx --yes @cloudron/surfer@7.2.0`
(pinned) and the v6 `put` command it replaced no longer exists, so
`cloudron-surfer@6` in devDependencies was dead weight in every `npm ci`.

Removing it also drops 35 packages that only existed for its bundled admin
UI (monaco-editor, three, openid-client, ejs, webdav-server, ...). Only those
entries are removed from package-lock.json - no re-resolution, so the diff is
pure deletion.

`.github/workflows/publish.yml` is the pre-Cloudron deploy path: it pushes the
build to a `gh-pages` branch with a third-party action and a `DCSITE` secret.
The site is published by surfer from `.gitea/workflows/publish.yml`, so this
file describes a deploy that cannot run and only misleads a reader looking for
how the site goes live.

Verified locally: `npm ci --legacy-peer-deps` (lock still in sync),
`npm run build` (136 pages), `npm run typecheck` clean, `npm run lint` reports
the same 8 pre-existing files as main and no new ones.
2026-10-28 11:50:11 +00:00
15 changed files with 1100 additions and 1159 deletions

No files matched your search

+18
View File
@@ -26,6 +26,24 @@ jobs:
- name: Install dependencies
run: npm ci --legacy-peer-deps
# Blocking gate. `npm audit --audit-level=<x>` 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
-30
View File
@@ -1,30 +0,0 @@
name: Gatsby Publish
on:
push:
branches: main
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js from .nvmrc
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
- name: Install dependencies
run: npm ci --legacy-peer-deps
- name: Check prettier offences
run: npm run lint
- uses: enriikke/gatsby-gh-pages-action@v2
with:
access-token: ${{ secrets.DCSITE }}
deploy-branch: gh-pages
gatsby-args: --prefix-paths
+12
View File
@@ -0,0 +1,12 @@
# Secret scanning for staged content, so a credential is caught before it is
# committed rather than after it has been pushed and published.
#
# gitleaks is pinned to an exact tag: a floating rev would pull whatever the
# hook's upstream does next into every contributor's commit path.
#
# Install once per clone: `pre-commit install`.
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
+15 -1120
View File
File diff suppressed because it is too large. Load diff
-1
View File
@@ -55,7 +55,6 @@
"@types/react": "^18.3.31",
"@types/react-dom": "^18.3.7",
"bootstrap": "^5.0.0-beta3",
"cloudron-surfer": "^6.4.1",
"jquery": "^3.6.0",
"typescript": "^7.0.2"
},
+940
View File
@@ -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 </style> 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 </script at the end of a serialized function body",
"range": "<7.1.3"
},
{
"pkg": "serialize-javascript",
"severity": "high",
"advisory": "GHSA-qj8w-gfj5-8c6v",
"title": "Serialize JavaScript has CPU Exhaustion Denial of Service via crafted array-like objects",
"range": ">=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"
}
]
}
+107
View File
@@ -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=<x>` 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)`);
+1 -1
View File
@@ -14,7 +14,7 @@ const NotFound: React.FC<PageProps<unknown, PageContext>> = ({
pageContext
}) => (
<Layout location={location} heroSection={false}>
<Section color="black" bgColor="white" bottomArrow={false}>
<Section color="black" bgColor="white" bottomArrow={false}>
<SideBar
pageContext={pageContext}
location={location}
+1 -1
View File
@@ -18,7 +18,7 @@ const About: React.FC<PageProps<unknown>> = ({ location }) => {
heading="Behind Data Controller"
desc="Data Controller is a product of 4GL, a brand of Bowe IO Ltd"
>
<Section color="black" bgColor="white">
<Section color="black" bgColor="white">
<div className="row">
<div className="offset-md-2 col-md-8">
<SectionHeading center="no">About Data Controller</SectionHeading>
+1 -1
View File
@@ -58,7 +58,7 @@ const Contact: React.FC<PageProps<unknown>> = ({ location }) => {
heading="Book a Demo"
desc="Schedule a demonstration of the Data Controller to see our latest features and explore use cases"
>
<Section color="black" bgColor="white" bottomArrow={false}>
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-6">
<SectionHeading center="no">Schedule a Call</SectionHeading>
+1 -1
View File
@@ -20,7 +20,7 @@ const Faq: React.FC<PageProps<unknown>> = ({ location }) => {
heading="Data Controller – FAQ"
desc="Most common user questions and answers regarding the Data Controller"
>
<Section color="black" bgColor="white">
<Section color="black" bgColor="white">
<div className="row">
<div className="col-md-8">
<FAQ
+1 -1
View File
@@ -68,7 +68,7 @@ const Home: React.FC<PageProps<IndexPageData>> = ({ 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."
>
<Section color="black" bgColor="white">
<Section color="black" bgColor="white">
<div className="row">
<div className="col-md-3">
<Art src={leftArt} info="Business Owner" />
+1 -1
View File
@@ -69,7 +69,7 @@ const BlogListTemplate: React.FC<PageProps<BlogListData, PageContext>> = ({
heading="Data Controller’s Knowledge Base"
desc="A section dedicated to news, updates and educational pieces."
>
<Section color="black" bgColor="white" bottomArrow={false}>
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-7">
<div className="row">
+1 -1
View File
@@ -71,7 +71,7 @@ const BlogSearchTemplate: React.FC<PageProps<SearchData, PageContext>> = ({
heading="Data Controller’s Knowledge Base"
desc="A section dedicated to news, updates and educational pieces."
>
<Section color="black" bgColor="white" bottomArrow={false}>
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-7">
<div className="row">
+1 -1
View File
@@ -68,7 +68,7 @@ const FeedListTemplate: React.FC<PageProps<FeedListData, PageContext>> = ({
heading="Data Controller’s Feed"
desc="Announcements and social media style updates from the Data Controller team."
>
<Section color="black" bgColor="white" bottomArrow={false}>
<Section color="black" bgColor="white" bottomArrow={false}>
<div className="row">
<div className="col-md-7">
<div className="row">