Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/generate-vex.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: "Generate VEX document"

on:
workflow_run:
workflows:
- "Update core index.json"
- "Update deps index.json"
- "Update npm index.json"
types:
- completed
workflow_dispatch:
push:
branches:
- main
paths:
- 'vuln/core/index.json'
- 'vuln/npm/index.json'
- 'vuln/deps/index.json'
- 'tools/vex/**'

concurrency:
group: generate-vex
cancel-in-progress: true

permissions:
contents: write
pull-requests: write

jobs:
generate-vex:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit

- name: Checkout repository
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23.x'

- name: Generate VEX document
working-directory: tools/vex
run: |
go run .

- name: Detect changes
id: detect
run: |
if git diff --quiet node.openvex.json; then
echo "no_changes=true" >> $GITHUB_OUTPUT
else
echo "no_changes=false" >> $GITHUB_OUTPUT
fi

- name: Create Pull Request
if: steps.detect.outputs.no_changes == 'false'
uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit-message: 'vex: regenerate node.openvex.json'
title: regenerate node.openvex.json
body: 'Automated regeneration of node.openvex.json after vulnerability index update. cc: @nodejs/security-wg'
assignees: ${{ github.actor }}
labels: security-wg-agenda
branch: regenerate-vex
update-pull-request-title-and-body: true

- name: No changes summary
if: steps.detect.outputs.no_changes == 'true'
run: echo "No changes to node.openvex.json; skipping PR creation."
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node-version: [18.x, 20.x, 22.x]
node-version: [20.x, 22.x, '24.x']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-core-index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 18
node-version-file: '.nvmrc'

- name: Install deps
run: npm ci
Expand Down
50 changes: 50 additions & 0 deletions .github/workflows/update-deps-index.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: "Update deps index.json"
on:
workflow_dispatch:
push:
branches:
- main
paths:
- 'vuln/deps/*.json'
- '!vuln/deps/index.json'

permissions:
contents: write
pull-requests: write

jobs:
stale:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Member Author

@marco-ippolito marco-ippolito Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
with:
persist-credentials: false

- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version-file: '.nvmrc'

- name: Install deps
run: npm ci

- name: Update deps index.json
run: |
npm run create-deps-index

- name: Create Pull Request
uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit-message: 'vuln: update deps index.json'
title: update deps index.json
body: 'update deps index.json. cc: @nodejs/security-wg'
assignees: ${{ github.actor }}
labels: security-wg-agenda
branch: deps-index-updated
update-pull-request-title-and-body: true
2 changes: 1 addition & 1 deletion .github/workflows/update-npm-index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 18
node-version-file: '.nvmrc'

- name: Install deps
run: npm ci
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate-vulnerability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: 18
node-version-file: '.nvmrc'

- name: Install deps
run: npm ci
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"test": "node --test",
"validate": "node tools/vuln_valid",
"create-npm-index": "node tools/create_index/create_npm_index.js",
"create-core-index": "node tools/create_index/create_core_index.js"
"create-core-index": "node tools/create_index/create_core_index.js",
"create-deps-index": "node tools/create_index/create_deps_index.js"
},
"keywords": [],
"author": "",
Expand Down
56 changes: 56 additions & 0 deletions tools/create_index/create_deps_index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const fs = require('node:fs')
const path = require('node:path')

const depsVulnerabilitiesPath = path.join(__dirname, '../../vuln/deps/')

// Valid justification values from OpenVEX spec v0.2.0
// See: https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-justifications
const validJustifications = [
'component_not_present',
'vulnerable_code_not_present',
'vulnerable_code_not_in_execute_path',
'vulnerable_code_cannot_be_controlled_by_adversary',
'inline_mitigations_already_exist'
]

let vuln = {}

function createDepsIndex() {
const files = fs.readdirSync(depsVulnerabilitiesPath)
getVulnDirectoryContents(files)
writeIndex(vuln)
}

function getVulnDirectoryContents(files) {
for (const file of files) {
const filename = file.slice(0, file.toString().indexOf('.json'))
if (filename !== 'index') {
const data = fs.readFileSync(depsVulnerabilitiesPath + file)
const json = JSON.parse(data)

if (!json.reason) {
throw new Error(`Missing 'reason' field in ${file}`)
}

if (!validJustifications.includes(json.reason)) {
throw new Error(
`Invalid justification '${json.reason}' in ${file}. ` +
`Valid values are: ${validJustifications.join(', ')}`
)
}

createVulnObject(filename, json)
}
}
}

function createVulnObject(identifier, json) {
vuln[identifier] = json
}

function writeIndex(data) {
fs.writeFileSync(depsVulnerabilitiesPath + 'index.json', JSON.stringify(data, null, 2))
console.log('Successfully wrote ' + depsVulnerabilitiesPath + 'index.json for deps vulnerabilities.')
}

createDepsIndex()
1 change: 1 addition & 0 deletions tools/vex/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-vex-generator
28 changes: 28 additions & 0 deletions tools/vex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Node.js OpenVEX Generator

This tool produces a single OpenVEX document (`node.openvex.json`) covering:

* Node.js Core vulnerabilities (`vuln/core/index.json`).
* Bundled npm-related vulnerabilities (`vuln/npm/index.json`).
* Dependency that we believe do **not** affect Node.js (`vuln/deps/index.json`) - these are emitted with `status: not_affected`.

## Output

Run:

```
go run .
```

Generates `node.openvex.json`.

## Adding / Updating Vulnerabilities

1. Edit the appropriate index file under `vuln/`.
2. Run `go run .` to regenerate.

**Note:** Entries without a CVE ID are skipped and will not appear in the generated VEX document.

## Do Not Manually Edit Generated File

`node.openvex.json` is generated; modify source indices instead.
12 changes: 12 additions & 0 deletions tools/vex/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module node-vex-generator

go 1.23.5

require (
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/openvex/go-vex v0.2.5 // indirect
github.com/package-url/packageurl-go v0.1.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/sys v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 20 additions & 0 deletions tools/vex/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ=
github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo=
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
68 changes: 68 additions & 0 deletions tools/vex/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package main

import (
"encoding/json"
"fmt"
"os"
)

const outputFile = "node.openvex.json"

func main() {
coreVulns, err := LoadVulnerabilities("../../vuln/core/index.json")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load core vulnerabilities: %v\n", err)
os.Exit(1)
}

npmVulns, err := LoadVulnerabilities("../../vuln/npm/index.json")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to load npm vulnerabilities: %v\n", err)
os.Exit(1)
}

depsVulns, err := LoadVulnerabilities("../../vuln/deps/index.json")
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: Failed to load deps vulnerabilities: %v\n", err)
depsVulns = make(map[string]VulnEntry)
}

doc, err := GenerateVEXDocument("Node.js Security WG", "Project")
if err != nil {
fmt.Fprintf(os.Stderr, "Error generating VEX: %v\n", err)
os.Exit(1)
}

doc.GenerateCanonicalID()

fmt.Println("Validating OpenVEX spec compliance...")
if err := ValidateVEX(doc); err != nil {
fmt.Fprintf(os.Stderr, "OpenVEX validation failed: %v\n", err)
os.Exit(1)
}

fmt.Println("Validating against source vulnerability data...")
if err := ValidateVEXAgainstSource(doc, coreVulns, npmVulns, depsVulns); err != nil {
fmt.Fprintf(os.Stderr, "Source validation failed: %v\n", err)
os.Exit(1)
}

fmt.Println("All validations passed!")

file, err := os.Create(outputFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create file: %v\n", err)
os.Exit(1)
}
defer file.Close()

encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")

if err := encoder.Encode(doc); err != nil {
fmt.Fprintf(os.Stderr, "Failed to encode VEX: %v\n", err)
os.Exit(1)
}

fmt.Printf("VEX document written to %s\n", outputFile)
}
Loading
Loading