Skip to content

Commit 7765639

Browse files
feat: add vex generator
1 parent f2b2be5 commit 7765639

19 files changed

Lines changed: 3810 additions & 1 deletion

.github/workflows/generate-vex.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: "Generate VEX document"
2+
3+
on:
4+
workflow_run:
5+
workflows:
6+
- "Update core index.json"
7+
- "Update deps index.json"
8+
- "Update npm index.json"
9+
types:
10+
- completed
11+
workflow_dispatch:
12+
push:
13+
branches:
14+
- main
15+
paths:
16+
- 'vuln/core/index.json'
17+
- 'vuln/npm/index.json'
18+
- 'vuln/deps/index.json'
19+
- 'tools/vex/**'
20+
21+
concurrency:
22+
group: generate-vex
23+
cancel-in-progress: true
24+
25+
permissions:
26+
contents: write
27+
pull-requests: write
28+
29+
jobs:
30+
generate-vex:
31+
runs-on: ubuntu-latest
32+
steps:
33+
- name: Harden Runner
34+
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
35+
with:
36+
egress-policy: audit
37+
38+
- name: Checkout repository
39+
uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
40+
with:
41+
persist-credentials: false
42+
43+
- name: Setup Go
44+
uses: actions/setup-go@v5
45+
with:
46+
go-version: '1.23.x'
47+
48+
- name: Generate VEX document
49+
working-directory: tools/vex
50+
run: |
51+
go run .
52+
53+
- name: Detect changes
54+
id: detect
55+
run: |
56+
if git diff --quiet node.openvex.json; then
57+
echo "no_changes=true" >> $GITHUB_OUTPUT
58+
else
59+
echo "no_changes=false" >> $GITHUB_OUTPUT
60+
fi
61+
62+
- name: Create Pull Request
63+
if: steps.detect.outputs.no_changes == 'false'
64+
uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5
65+
env:
66+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67+
with:
68+
commit-message: 'vex: regenerate node.openvex.json'
69+
title: regenerate node.openvex.json
70+
body: 'Automated regeneration of node.openvex.json after vulnerability index update. cc: @nodejs/security-wg'
71+
assignees: ${{ github.actor }}
72+
labels: security-wg-agenda
73+
branch: regenerate-vex
74+
update-pull-request-title-and-body: true
75+
76+
- name: No changes summary
77+
if: steps.detect.outputs.no_changes == 'true'
78+
run: echo "No changes to node.openvex.json; skipping PR creation."
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: "Update deps index.json"
2+
on:
3+
workflow_dispatch:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'vuln/deps/*.json'
9+
- '!vuln/deps/index.json'
10+
11+
permissions:
12+
contents: write
13+
pull-requests: write
14+
15+
jobs:
16+
stale:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Harden Runner
20+
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
21+
with:
22+
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
23+
24+
- uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2
25+
with:
26+
persist-credentials: false
27+
28+
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
29+
with:
30+
node-version: 18
31+
32+
- name: Install deps
33+
run: npm ci
34+
35+
- name: Update deps index.json
36+
run: |
37+
npm run create-deps-index
38+
39+
- name: Create Pull Request
40+
uses: gr2m/create-or-update-pull-request-action@77596e3166f328b24613f7082ab30bf2d93079d5
41+
env:
42+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43+
with:
44+
commit-message: 'vuln: update deps index.json'
45+
title: update deps index.json
46+
body: 'update deps index.json. cc: @nodejs/security-wg'
47+
assignees: ${{ github.actor }}
48+
labels: security-wg-agenda
49+
branch: deps-index-updated
50+
update-pull-request-title-and-body: true

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"test": "node --test",
66
"validate": "node tools/vuln_valid",
77
"create-npm-index": "node tools/create_index/create_npm_index.js",
8-
"create-core-index": "node tools/create_index/create_core_index.js"
8+
"create-core-index": "node tools/create_index/create_core_index.js",
9+
"create-deps-index": "node tools/create_index/create_deps_index.js"
910
},
1011
"keywords": [],
1112
"author": "",
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const fs = require('node:fs')
2+
const path = require('node:path')
3+
4+
const depsVulnerabilitiesPath = path.join(__dirname, '../../vuln/deps/')
5+
6+
// Valid justification values from OpenVEX spec v0.2.0
7+
// See: https://github.com/openvex/spec/blob/main/OPENVEX-SPEC.md#status-justifications
8+
const validJustifications = [
9+
'component_not_present',
10+
'vulnerable_code_not_present',
11+
'vulnerable_code_not_in_execute_path',
12+
'vulnerable_code_cannot_be_controlled_by_adversary',
13+
'inline_mitigations_already_exist'
14+
]
15+
16+
let vuln = {}
17+
18+
function createDepsIndex() {
19+
const files = fs.readdirSync(depsVulnerabilitiesPath)
20+
getVulnDirectoryContents(files)
21+
writeIndex(vuln)
22+
}
23+
24+
function getVulnDirectoryContents(files) {
25+
for (const file of files) {
26+
const filename = file.slice(0, file.toString().indexOf('.json'))
27+
if (filename !== 'index') {
28+
const data = fs.readFileSync(depsVulnerabilitiesPath + file)
29+
const json = JSON.parse(data)
30+
31+
if (!json.reason) {
32+
throw new Error(`Missing 'reason' field in ${file}`)
33+
}
34+
35+
if (!validJustifications.includes(json.reason)) {
36+
throw new Error(
37+
`Invalid justification '${json.reason}' in ${file}. ` +
38+
`Valid values are: ${validJustifications.join(', ')}`
39+
)
40+
}
41+
42+
createVulnObject(filename, json)
43+
}
44+
}
45+
}
46+
47+
function createVulnObject(identifier, json) {
48+
vuln[identifier] = json
49+
}
50+
51+
function writeIndex(data) {
52+
fs.writeFileSync(depsVulnerabilitiesPath + 'index.json', JSON.stringify(data, null, 2))
53+
console.log('Successfully wrote ' + depsVulnerabilitiesPath + 'index.json for deps vulnerabilities.')
54+
}
55+
56+
createDepsIndex()

tools/vex/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node-vex-generator

tools/vex/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Node.js OpenVEX Generator
2+
3+
This tool produces a single OpenVEX document (`node.openvex.json`) covering:
4+
5+
* Node.js Core vulnerabilities (`vuln/core/index.json`).
6+
* Bundled npm-related vulnerabilities (`vuln/npm/index.json`).
7+
* Dependency that we believe do **not** affect Node.js (`vuln/deps/index.json`) - these are emitted with `status: not_affected`.
8+
9+
## Output
10+
11+
Run:
12+
13+
```
14+
go run .
15+
```
16+
17+
Generates `node.openvex.json`.
18+
19+
## Adding / Updating Vulnerabilities
20+
21+
1. Edit the appropriate index file under `vuln/`.
22+
2. Run `go run .` to regenerate.
23+
24+
**Note:** Entries without a CVE ID are skipped and will not appear in the generated VEX document.
25+
26+
## Do Not Manually Edit Generated File
27+
28+
`node.openvex.json` is generated; modify source indices instead.

tools/vex/go.mod

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module node-vex-generator
2+
3+
go 1.23.5
4+
5+
require (
6+
github.com/Masterminds/semver/v3 v3.4.0 // indirect
7+
github.com/openvex/go-vex v0.2.5 // indirect
8+
github.com/package-url/packageurl-go v0.1.1 // indirect
9+
github.com/sirupsen/logrus v1.9.3 // indirect
10+
golang.org/x/sys v0.8.0 // indirect
11+
gopkg.in/yaml.v3 v3.0.1 // indirect
12+
)

tools/vex/go.sum

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
2+
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
3+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
5+
github.com/openvex/go-vex v0.2.5 h1:41utdp2rHgAGCsG+UbjmfMG5CWQxs15nGqir1eRgSrQ=
6+
github.com/openvex/go-vex v0.2.5/go.mod h1:j+oadBxSUELkrKh4NfNb+BPo77U3q7gdKME88IO/0Wo=
7+
github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU=
8+
github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
11+
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
12+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
14+
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
15+
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
16+
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
19+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
20+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

tools/vex/main.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
)
8+
9+
const outputFile = "node.openvex.json"
10+
11+
func main() {
12+
coreVulns, err := LoadVulnerabilities("../../vuln/core/index.json")
13+
if err != nil {
14+
fmt.Fprintf(os.Stderr, "Failed to load core vulnerabilities: %v\n", err)
15+
os.Exit(1)
16+
}
17+
18+
npmVulns, err := LoadVulnerabilities("../../vuln/npm/index.json")
19+
if err != nil {
20+
fmt.Fprintf(os.Stderr, "Failed to load npm vulnerabilities: %v\n", err)
21+
os.Exit(1)
22+
}
23+
24+
depsVulns, err := LoadVulnerabilities("../../vuln/deps/index.json")
25+
if err != nil {
26+
fmt.Fprintf(os.Stderr, "Warning: Failed to load deps vulnerabilities: %v\n", err)
27+
depsVulns = make(map[string]VulnEntry)
28+
}
29+
30+
doc, err := GenerateVEXDocument("Node.js Security WG", "Project")
31+
if err != nil {
32+
fmt.Fprintf(os.Stderr, "Error generating VEX: %v\n", err)
33+
os.Exit(1)
34+
}
35+
36+
doc.GenerateCanonicalID()
37+
38+
fmt.Println("Validating OpenVEX spec compliance...")
39+
if err := ValidateVEX(doc); err != nil {
40+
fmt.Fprintf(os.Stderr, "OpenVEX validation failed: %v\n", err)
41+
os.Exit(1)
42+
}
43+
44+
fmt.Println("Validating against source vulnerability data...")
45+
if err := ValidateVEXAgainstSource(doc, coreVulns, npmVulns, depsVulns); err != nil {
46+
fmt.Fprintf(os.Stderr, "Source validation failed: %v\n", err)
47+
os.Exit(1)
48+
}
49+
50+
fmt.Println("All validations passed!")
51+
52+
file, err := os.Create(outputFile)
53+
if err != nil {
54+
fmt.Fprintf(os.Stderr, "Failed to create file: %v\n", err)
55+
os.Exit(1)
56+
}
57+
defer file.Close()
58+
59+
encoder := json.NewEncoder(file)
60+
encoder.SetIndent("", " ")
61+
62+
if err := encoder.Encode(doc); err != nil {
63+
fmt.Fprintf(os.Stderr, "Failed to encode VEX: %v\n", err)
64+
os.Exit(1)
65+
}
66+
67+
fmt.Printf("VEX document written to %s\n", outputFile)
68+
}

0 commit comments

Comments
 (0)