Skip to content

Commit e7b6b99

Browse files
authored
Migrate dependency checker and add support for LTS branches (#59)
* Import dependency checker script from node repo * Add support for LTS branches * Use migrated dependency checker in GH actions * Add workflows for v14.x and v16.x
1 parent da404c7 commit e7b6b99

File tree

8 files changed

+686
-7
lines changed

8 files changed

+686
-7
lines changed

.github/workflows/14x-main.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Check v14.x for vulns daily
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: 10 0 * * *
7+
8+
permissions:
9+
contents: read
10+
issues: write
11+
12+
jobs:
13+
check-vulns:
14+
uses: ./.github/workflows/check-vulns.yml
15+
with:
16+
nodejsStream: v14.x
17+
secrets: inherit

.github/workflows/16x-main.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: Check v16.x for vulns daily
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: 10 0 * * *
7+
8+
permissions:
9+
contents: read
10+
issues: write
11+
12+
jobs:
13+
check-vulns:
14+
uses: ./.github/workflows/check-vulns.yml
15+
with:
16+
nodejsStream: v16.x
17+
secrets: inherit

.github/workflows/check-vulns.yml

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,28 @@ jobs:
3131
uses: actions/setup-python@v3
3232
with:
3333
python-version: '3.9'
34+
- name: Checkout current repository
35+
uses: actions/checkout@v3
36+
- name: Installing pre-reqs
37+
working-directory: ./dep_checker
38+
run: pip install -r requirements.txt
3439
- name: Checkout node.js repo
3540
uses: actions/checkout@v3
3641
with:
3742
repository: nodejs/node
3843
path: node
3944
ref: ${{ inputs.nodejsStream }}
40-
- name: Installing pre-reqs
41-
working-directory: ./node/tools/dep_checker
42-
run: pip install -r requirements.txt
4345
- name: Run the check
44-
working-directory: ./node/tools/dep_checker
46+
working-directory: ./dep_checker
4547
run: |
4648
(
4749
set -o pipefail
48-
python main.py --gh-token ${{ secrets.GITHUB_TOKEN }} --nvd-key=${{ secrets.NVD_API_KEY }} 2>&1 | tee result.log
50+
python main.py --gh-token ${{ secrets.GITHUB_TOKEN }} --nvd-key=${{ secrets.NVD_API_KEY }} ../node ${{ inputs.nodejsStream }} 2>&1 | tee result.log
4951
)
5052
- name: build matrix
5153
id: set_matrix
5254
if: ${{ failure() }}
53-
working-directory: ./node/tools/dep_checker
55+
working-directory: ./dep_checker
5456
run: |
5557
matrix=$((echo '{ "vulnerability" : ['
5658
cat result.log | sed -n 's/.*\(CVE-.*\|GHSA-.*\).*/"\1",/p' | sed '$s/,//'
@@ -61,7 +63,7 @@ jobs:
6163
- name: collect error
6264
id: collect_error
6365
if: ${{ failure() }}
64-
working-directory: ./node/tools/dep_checker
66+
working-directory: ./dep_checker
6567
run: |
6668
content=$(cat result.log)
6769
# New lines must be escaped since outputs cannot be multi-line

dep_checker/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Node.js dependency vulnerability checker
2+
3+
This script queries the [National Vulnerability Database (NVD)](https://nvd.nist.gov/) and
4+
the [GitHub Advisory Database](https://github.com/advisories) for vulnerabilities found
5+
in Node's dependencies.
6+
7+
## How to use
8+
9+
### Database authentication
10+
11+
- In order to query the GitHub Advisory Database,
12+
a [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token)
13+
has to be created (no permissions need to be given to the token, since it's only used to query the public database).
14+
- The NVD can be queried without authentication, but it will be rate limited to one query every six seconds. In order to
15+
remove
16+
that limitation [request an API key](https://nvd.nist.gov/developers/request-an-api-key) and pass it as a parameter.
17+
18+
### Running the script
19+
20+
Once acquired, the script can be run as follows:
21+
22+
```shell
23+
git clone https://github.com/nodejs/node.git
24+
cd node && git checkout v18.x && cd ..
25+
cd dep_checker/
26+
pip install -r requirements.txt
27+
28+
# Python >= 3.9 required
29+
python main.py --gh-token=$PERSONAL_ACCESS_TOKEN --nvd-key=$NVD_API_KEY ../node v18.x
30+
31+
# The command can also be run without parameters
32+
# This will skip querying the GitHub Advisory Database, and query the NVD
33+
# using the anonymous (rate-limited) API
34+
python main.py ../node v18.x
35+
```
36+
37+
## Example output
38+
39+
```
40+
WARNING: New vulnerabilities found
41+
- npm (version 1.2.1) :
42+
- GHSA-v3jv-wrf4-5845: https://github.com/advisories/GHSA-v3jv-wrf4-5845
43+
- GHSA-93f3-23rq-pjfp: https://github.com/advisories/GHSA-93f3-23rq-pjfp
44+
- GHSA-m6cx-g6qm-p2cx: https://github.com/advisories/GHSA-m6cx-g6qm-p2cx
45+
- GHSA-4328-8hgf-7wjr: https://github.com/advisories/GHSA-4328-8hgf-7wjr
46+
- GHSA-x8qc-rrcw-4r46: https://github.com/advisories/GHSA-x8qc-rrcw-4r46
47+
- GHSA-m5h6-hr3q-22h5: https://github.com/advisories/GHSA-m5h6-hr3q-22h5
48+
- acorn (version 6.0.0) :
49+
- GHSA-6chw-6frg-f759: https://github.com/advisories/GHSA-6chw-6frg-f759
50+
51+
For each dependency and vulnerability, check the following:
52+
- Check the vulnerability's description to see if it applies to the dependency as
53+
used by Node. If not, the vulnerability ID (either a CVE or a GHSA) can be added to the ignore list in
54+
dependencies.py. IMPORTANT: Only do this if certain that the vulnerability found is a false positive.
55+
- Otherwise, the vulnerability found must be remediated by updating the dependency in the Node repo to a
56+
non-affected version.
57+
```
58+
59+
## Implementation details
60+
61+
- For each dependency in Node's `deps/` folder, the script parses their version number and queries the databases to find
62+
vulnerabilities for that specific version.
63+
- The queries can return false positives (
64+
see [this](https://github.com/nodejs/security-wg/issues/802#issuecomment-1144207417) comment for an example). These
65+
can be ignored by adding the vulnerability to the `ignore_list` in `dependencies.py`
66+
- If no NVD API key is provided, the script will take a while to finish (~2 min) because queries to the NVD
67+
are [rate-limited](https://nvd.nist.gov/developers/start-here)
68+
- If any vulnerabilities are found, the script returns 1 and prints out a list with the ID and a link to a description
69+
of
70+
the vulnerability. This is the case except when the ID matches one in the ignore-list (inside `dependencies.py`) in
71+
which case the vulnerability is ignored.
72+
73+

dep_checker/dependencies.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""A list of dependencies, including their CPE, names and keywords for querying different vulnerability databases"""
2+
3+
from typing import Optional, Callable
4+
from pathlib import Path
5+
import versions_parser as vp
6+
7+
8+
class CPE:
9+
def __init__(self, vendor: str, product: str):
10+
self.vendor = vendor
11+
self.product = product
12+
13+
14+
class Dependency:
15+
def __init__(
16+
self,
17+
version_parser: Callable[[Path], str],
18+
cpe: Optional[CPE] = None,
19+
npm_name: Optional[str] = None,
20+
keyword: Optional[str] = None,
21+
):
22+
self.version_parser = version_parser
23+
self.cpe = cpe
24+
self.npm_name = npm_name
25+
self.keyword = keyword
26+
27+
def get_cpe(self, repo_path: Path) -> Optional[str]:
28+
if self.cpe:
29+
return f"cpe:2.3:a:{self.cpe.vendor}:{self.cpe.product}:{self.version_parser(repo_path)}:*:*:*:*:*:*:*"
30+
else:
31+
return None
32+
33+
34+
ignore_list: list[str] = [
35+
"CVE-2018-25032", # zlib, already fixed in the fork Node uses (Chromium's)
36+
"CVE-2007-5536", # openssl, old and only in combination with HP-UX
37+
"CVE-2019-0190", # openssl, can be only triggered in combination with Apache HTTP Server version 2.4.37
38+
]
39+
40+
common_dependencies: list[str] = [
41+
"acorn",
42+
"brotli",
43+
"c-ares",
44+
"CJS Module Lexer",
45+
"corepack",
46+
"HdrHistogram",
47+
"ICU",
48+
"llhttp",
49+
"nghttp2",
50+
"npm",
51+
"OpenSSL",
52+
"libuv",
53+
"uvwasi",
54+
"zlib",
55+
]
56+
57+
dependencies_per_branch: dict[str, list[str]] = {
58+
"main": common_dependencies + ["nghttp3", "ngtcp2", "undici"],
59+
"v18.x": common_dependencies + ["nghttp3", "ngtcp2", "undici"],
60+
"v16.x": common_dependencies + ["undici"],
61+
"v14.x": common_dependencies,
62+
}
63+
64+
65+
dependencies_info: dict[str, Dependency] = {
66+
"zlib": Dependency(
67+
version_parser=vp.get_zlib_version,
68+
cpe=CPE(vendor="zlib", product="zlib"),
69+
),
70+
# TODO: Add V8
71+
# "V8": Dependency("cpe:2.3:a:google:chrome:*:*:*:*:*:*:*:*", "v8"),
72+
"uvwasi": Dependency(
73+
version_parser=vp.get_uvwasi_version, cpe=None, keyword="uvwasi"
74+
),
75+
"libuv": Dependency(
76+
version_parser=vp.get_libuv_version,
77+
cpe=CPE(vendor="libuv_project", product="libuv"),
78+
),
79+
"undici": Dependency(
80+
version_parser=vp.get_undici_version,
81+
cpe=CPE(vendor="nodejs", product="undici"),
82+
npm_name="undici",
83+
),
84+
"OpenSSL": Dependency(
85+
version_parser=vp.get_openssl_version,
86+
cpe=CPE(vendor="openssl", product="openssl"),
87+
),
88+
"npm": Dependency(
89+
version_parser=vp.get_npm_version,
90+
cpe=CPE(vendor="npmjs", product="npm"),
91+
npm_name="npm",
92+
),
93+
"nghttp3": Dependency(
94+
version_parser=vp.get_nghttp3_version, cpe=None, keyword="nghttp3"
95+
),
96+
"ngtcp2": Dependency(
97+
version_parser=vp.get_ngtcp2_version, cpe=None, keyword="ngtcp2"
98+
),
99+
"nghttp2": Dependency(
100+
version_parser=vp.get_nghttp2_version,
101+
cpe=CPE(vendor="nghttp2", product="nghttp2"),
102+
),
103+
"llhttp": Dependency(
104+
version_parser=vp.get_llhttp_version,
105+
cpe=CPE(vendor="llhttp", product="llhttp"),
106+
npm_name="llhttp",
107+
),
108+
"ICU": Dependency(
109+
version_parser=vp.get_icu_version,
110+
cpe=CPE(vendor="icu-project", product="international_components_for_unicode"),
111+
),
112+
"HdrHistogram": Dependency(
113+
version_parser=lambda x: "0.11.2", cpe=None, keyword="hdrhistogram"
114+
),
115+
"corepack": Dependency(
116+
version_parser=vp.get_corepack_version,
117+
cpe=None,
118+
keyword="corepack",
119+
npm_name="corepack",
120+
),
121+
"CJS Module Lexer": Dependency(
122+
version_parser=vp.get_cjs_lexer_version,
123+
cpe=None,
124+
keyword="cjs-module-lexer",
125+
npm_name="cjs-module-lexer",
126+
),
127+
"c-ares": Dependency(
128+
version_parser=vp.get_c_ares_version,
129+
cpe=CPE(vendor="c-ares_project", product="c-ares"),
130+
),
131+
"brotli": Dependency(
132+
version_parser=vp.get_brotli_version,
133+
cpe=CPE(vendor="google", product="brotli"),
134+
),
135+
"acorn": Dependency(
136+
version_parser=vp.get_acorn_version, cpe=None, npm_name="acorn"
137+
),
138+
}

0 commit comments

Comments
 (0)