Compare commits
18
Commits
db969f51b4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f3c6f30a9 | ||
|
|
2eb5d74f59 | ||
|
|
bef1b99c78 | ||
|
|
5980e2caba | ||
|
|
27a6d98d0c | ||
|
|
59f169e32d | ||
|
|
1a5338e876 | ||
|
|
bbb163c25c | ||
|
|
2c7a3b8465 | ||
|
|
d37538264a | ||
|
|
cd5eeafa08 | ||
|
|
4ef2b5333a | ||
|
|
3d2ec5869b | ||
|
|
6cabe6796a | ||
|
|
ce479a2c51 | ||
|
|
bc27894b38 | ||
|
|
f38b5d795c | ||
|
|
67cf263ec5 |
No files matched your search
@@ -4,6 +4,14 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# One deploy at a time: `surfer deploy` swaps the site atomically, so two runs
|
||||||
|
# racing each other is not corruption, but it is a wasted build and the last
|
||||||
|
# swap wins.
|
||||||
|
concurrency:
|
||||||
|
group: surfer-datacontroller-io
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-and-publish:
|
Build-and-publish:
|
||||||
@@ -18,8 +26,44 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci --legacy-peer-deps
|
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
|
- name: Build
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
|
# Deploy tooling pinned to an exact version: the workflow publishes
|
||||||
|
# whatever is committed, and an unpinned `latest` would let a breaking or
|
||||||
|
# compromised upstream release ship straight to the live site.
|
||||||
|
#
|
||||||
|
# `deploy` (v7) replaces the site atomically - it extracts to a staging
|
||||||
|
# folder and swaps it in, so a failed upload leaves the current site
|
||||||
|
# serving. The v6 `put` this replaces was token authenticated and is
|
||||||
|
# gone; v7 takes the Cloudron username plus an app password for this app.
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: npx surfer put --token ${{ secrets.SURFER_TOKEN }} --server datacontroller.io public/* /
|
env:
|
||||||
|
SURFER_PASSWORD: ${{ secrets.SURFER_PASSWORD }}
|
||||||
|
REF_NAME: ${{ gitea.ref_name }}
|
||||||
|
COMMIT_SHA: ${{ gitea.sha }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
npx --yes @cloudron/surfer@7.2.0 deploy public \
|
||||||
|
--server datacontroller.io \
|
||||||
|
--username allan \
|
||||||
|
--password "$SURFER_PASSWORD" \
|
||||||
|
--message "Build from ${REF_NAME} @ ${COMMIT_SHA:0:8}"
|
||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,140 @@
|
|||||||
|
---
|
||||||
|
title: 'Derived Variables: computed columns in the EDIT screen'
|
||||||
|
description: Data Controller can now compute columns that do not exist in the table - and, crucially, filter on them. Configured per table in MPE_DERIVED, computed on every read, never stored.
|
||||||
|
date: '2026-10-21 12:00:00'
|
||||||
|
author: 'Allan Bowe'
|
||||||
|
authorLink: https://www.linkedin.com/in/allanbowe/
|
||||||
|
previewImg: './derived-grid.png'
|
||||||
|
tags:
|
||||||
|
- Data Controller
|
||||||
|
- Features
|
||||||
|
---
|
||||||
|
|
||||||
|
# Derived variables: computed columns you can filter on
|
||||||
|
|
||||||
|
Every reporting team has a version of this request, and it always sounds like a five minute job: a column in the EDIT screen that is not in the table. A region worked out from a state code. A line value that is quantity times price. Not stored, not loaded, just *there* - and, as the requester usually adds, filterable.
|
||||||
|
|
||||||
|
The "not stored" part is easy. The "filterable" part is where the design lives, and it is worth writing down, because it explains every other decision in the feature.
|
||||||
|
|
||||||
|
## Why a computed column is not automatically filterable
|
||||||
|
|
||||||
|
You would write it in a DATA step:
|
||||||
|
|
||||||
|
```sas
|
||||||
|
data out;
|
||||||
|
set mylib.mytable;
|
||||||
|
REGION = ifc(STATE in ('CA','OR','WA'),'West','East');
|
||||||
|
where REGION = 'West';
|
||||||
|
run;
|
||||||
|
```
|
||||||
|
|
||||||
|
That fails. A `WHERE` statement applies to the data set named on the `SET` statement - the *input* - and on that input `REGION` does not exist. SAS cannot see a variable that the same step creates later. You can put the assignment and the filter in separate steps, but then the filter runs after the step has read every row, which is precisely the work the filter exists to avoid - and for a large table that is the difference between a fast screen and a coffee break.
|
||||||
|
|
||||||
|
So the calculation has to happen somewhere the filter can see it. In Data Controller it happens in a **DATA step view**:
|
||||||
|
|
||||||
|
```sas
|
||||||
|
data work.vw_derived / view=work.vw_derived;
|
||||||
|
set mylib.mytable;
|
||||||
|
attrib REGION length=$8 label='REGION=ifc(...)' format=$8.;
|
||||||
|
REGION = ifc(STATE in ('CA','OR','WA'),'West','East');
|
||||||
|
run;
|
||||||
|
|
||||||
|
data out;
|
||||||
|
set work.vw_derived;
|
||||||
|
where %inc filtref;;
|
||||||
|
run;
|
||||||
|
```
|
||||||
|
|
||||||
|
The view is not executed when it is defined. When the second step reads it, the assignment runs as part of the view's step, so by the time the `WHERE` is evaluated `REGION` is a column of the input. The filter is unchanged, the row-level security clause is unchanged, and nothing extra is read.
|
||||||
|
|
||||||
|
## What it looks like
|
||||||
|
|
||||||
|
The demo table below has four rows. `LINE_VALUE` is `QTY * PRICE`, `REGION` is derived from `STATE`, and `REGION_LABEL` is built on `REGION`. None of the three is a column of the table, and all three are read-only in the grid - and marked so, with a tinted header carrying an `fx` badge, italic tinted cells, and an edge where the computed block begins, so that "computed" never has to be confused with "locked by a rule":
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Because they are on the input to the filter, they filter like any other column. Here is `REGION = 'West'`, with the filter bar naming the derived variable:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
and a numeric one, `LINE_VALUE > 50`:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The value list in the filter dialog is built the same way, from the table under the rest of the filter - so a derived column gets a dropdown of its distinct values, not a blank box.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`MPE_DERIVED` is itself a Data Controller table, so it is maintained through the EDIT screen with the usual submit / review / approve workflow. One row per variable - these are the three rows behind the screenshots above:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
VAR_NM REGION
|
||||||
|
VAR_DEPS STATE
|
||||||
|
VAR_TYPE C
|
||||||
|
VAR_LEN 8
|
||||||
|
VAR_FMT (blank)
|
||||||
|
VAR_LABEL (blank)
|
||||||
|
VAR_LOGIC ifc(STATE in ('CA','OR','WA'),'West','East')
|
||||||
|
ACTIVE 1
|
||||||
|
```
|
||||||
|
|
||||||
|
and the chained one:
|
||||||
|
|
||||||
|
```
|
||||||
|
VAR_NM REGION_LABEL
|
||||||
|
VAR_DEPS REGION STATE
|
||||||
|
VAR_TYPE C
|
||||||
|
VAR_LEN 14
|
||||||
|
VAR_FMT (blank)
|
||||||
|
VAR_LABEL Region and state
|
||||||
|
VAR_LOGIC cats(REGION,'-',STATE)
|
||||||
|
ACTIVE 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Which produces, at read time:
|
||||||
|
|
||||||
|
```sas
|
||||||
|
attrib REGION length=$8 label='REGION=ifc(STATE in (''CA'',''OR'',''WA''),''West'',''East'')' format=$8.;
|
||||||
|
REGION = ifc(STATE in ('CA','OR','WA'),'West','East');
|
||||||
|
attrib REGION_LABEL length=$14 label='Region and state' format=$14.;
|
||||||
|
REGION_LABEL = cats(REGION,'-',STATE);
|
||||||
|
```
|
||||||
|
|
||||||
|
Type and length are declared rather than guessed, so a value is truncated exactly as specified. The format is optional; when it is left blank a character variable gets `$<length>.` and a numeric one `<length>.`, and a date, time or datetime format makes the column render - and filter - as a date, the same way a stored column does. The label is `VAR_LABEL` when you supply one, otherwise the name, an equals sign and the expression, so a user who switches labels on can see where the value came from.
|
||||||
|
|
||||||
|
## Dependencies, without a sequence number
|
||||||
|
|
||||||
|
`REGION_LABEL` needs `REGION` to exist first. Rather than maintain a sort order, each row declares its inputs in `VAR_DEPS`, and the statements are emitted in dependency order. Adding a variable in the middle of a chain means editing one row's `VAR_DEPS`, not renumbering the table.
|
||||||
|
|
||||||
|
`VAR_DEPS` lists *every* input - physical columns and derived variables alike - and the declaration is checked against the expression in both directions: a derived variable the expression references must be declared, and a declared name the expression does not reference is an error. A declared name must be a column of the table or a derived variable of the same table, and a cycle is rejected with the path that forms it:
|
||||||
|
|
||||||
|
```
|
||||||
|
VAR_DEPS contains a cycle: A -> B -> C -> A
|
||||||
|
```
|
||||||
|
|
||||||
|
## What it will not do
|
||||||
|
|
||||||
|
**Expressions only.** `VAR_LOGIC` must be a single SAS expression. Semicolons, macro triggers (`%`, `&`) and comments are rejected, so one row can never be more than an expression - which is the point: a config table is not a place to write programs. Conditional logic is available through functions such as `ifc()`, `choosec()`, `cats()`, `catx()`, `put()`, `input()` and `intnx()`.
|
||||||
|
|
||||||
|
**No shadowing, no self-reference.** A derived variable may not reuse the name of a column in the target table. Nor may it reference itself: `SUMMER = SUMMER + 1` implies a retained value, which depends on the order rows happen to arrive in.
|
||||||
|
|
||||||
|
**Never stored.** Derived values are computed on every read. Nothing derived is submitted, reviewed or written back - the approval package contains physical columns only, and a crafted submission that includes a derived column is discarded before the security check runs.
|
||||||
|
|
||||||
|
**EDIT screen only, for now.** The VIEW screen and the download services do not show derived columns. A saved filter that references one still applies there and is evaluated the same way.
|
||||||
|
|
||||||
|
**Not for row level security.** A rule in `MPE_ROW_LEVEL_SECURITY` cannot reference a derived column: the rule is evaluated against the submitted data, where the column does not exist. Saving a rule that names one is rejected with an explanation rather than left to fail at submit time.
|
||||||
|
|
||||||
|
## One warning
|
||||||
|
|
||||||
|
An expression can reference a column that is hidden from a user by [Column Level Security](https://docs.datacontroller.io/column-level-security/) - and the derived column will show the result. Configure derived variables with the same care as any other data exposure.
|
||||||
|
|
||||||
|
## Availability
|
||||||
|
|
||||||
|
Derived variables are available on SAS 9, Viya and SASjs Server. The configuration table, the settings and the post-edit validation are created by the usual upgrade - there is nothing to install beyond the release itself.
|
||||||
|
|
||||||
|
The full reference, including every column of `MPE_DERIVED` and the validation rules applied at submit time, is in the [docs](https://docs.datacontroller.io/derived-variables/).
|
||||||
|
|
||||||
|
If a table of yours has been waiting for "just one more column", we would love to [hear from you](/contact).
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
title: 'The column that is not in the table'
|
||||||
|
description: 'Derived variables give the EDIT screen columns that do not exist in the table - computed on every read, never stored, and filterable like any other column. The full story is in the article.'
|
||||||
|
date: '2026-10-27 09:00:00'
|
||||||
|
author: 'Data Controller'
|
||||||
|
authorLink: https://www.linkedin.com/showcase/data-controller-for-sas
|
||||||
|
tags:
|
||||||
|
- Announcements
|
||||||
|
- Features
|
||||||
|
previewImg: './derived-fx.png'
|
||||||
|
---
|
||||||
|
|
||||||
|
# The column that is not in the table
|
||||||
|
|
||||||
|
Every reporting team has a version of this request, and it always sounds like a five minute job: "can we have a region column? It is just the state, mapped. And a line value - quantity times price. We do not need it stored, we just need it on the screen."
|
||||||
|
|
||||||
|
It is never a five minute job - so Data Controller now does the obvious thing instead. You write the expression, and the EDIT screen shows the column: computed on every read, never stored, marked `fx` and read-only, so nobody has to wonder whether it is locked or simply has nothing to edit.
|
||||||
|
|
||||||
|
The part that took the thinking is that they filter like any other column. That needed a DATA step view rather than a data step, and the reasoning is worth reading: [Derived variables: computed columns you can filter on](/derived-variables/).
|
||||||
|
|
||||||
|
Reference: [Derived Variables](https://docs.datacontroller.io/derived-variables/).
|
||||||
|
|
||||||
|
If a table of yours has been waiting for "just one more column", we would love to [hear from you](/contact).
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Feed teaser for the article at /derived-variables/. The article carries the
|
||||||
|
engineering - why a computed column is not automatically filterable, the DATA
|
||||||
|
step view, the configuration table, the limits - so this post links to it
|
||||||
|
rather than repeating it.
|
||||||
|
|
||||||
|
Image prompt (cover): a clean, modern illustration on a white background: a
|
||||||
|
spreadsheet grid rendered in soft slate and light blue, with the leftmost four
|
||||||
|
columns showing plain data and three further columns floating slightly to the
|
||||||
|
right, detached from the table, tinted light blue and italicised, each marked
|
||||||
|
with a small "fx" badge. A thin left border separates the floating block from
|
||||||
|
the stored columns. No text other than the fx badges. Flat vector style, brand
|
||||||
|
palette: slate #314351, light blue #d3e5f2, accent green #79a843 used sparingly
|
||||||
|
as a single dot.
|
||||||
|
-->
|
||||||
Generated
+15
-1120
File diff suppressed because it is too large.
Load diff
@@ -55,7 +55,6 @@
|
|||||||
"@types/react": "^18.3.31",
|
"@types/react": "^18.3.31",
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"bootstrap": "^5.0.0-beta3",
|
"bootstrap": "^5.0.0-beta3",
|
||||||
"cloudron-surfer": "^6.4.1",
|
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"typescript": "^7.0.2"
|
"typescript": "^7.0.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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)`);
|
||||||
Reference in new issue
Block a user