You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit d00f50a
Browse filesBrowse the repository at this point in the historyBrowse files
Never run found binaries by default; sanitize output
The version now comes from the file itself (embedded headers URL or node_version.h). --exec-version is opt-in and runs a binary only through a pinned O_PATH descriptor when root or the caller owns it and every directory above it. Control characters from command lines, file and container names are escaped as \xNN in text and JSON.
Copy file name to clipboardExpand all lines: README.md
+13-3Lines changed: 13 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,15 +7,15 @@ sudo python3 node_runtime_audit.py # or without sudo: your own processes
7
7
8
8
Patching Node is a one-line version bump. Finding every Node your servers actually run is the work. This script lists the Node binary behind every running process and every place that will start Node later, and checks each one against the [Node.js security release of 21 October 2026](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases) (fixed in 22.23.4, 24.22.1 and 26.12.1; every end-of-life line counts as vulnerable).
9
9
10
-
It is one file of standard-library Python (3.8+, Linux). It only reads: it never restarts, edits or upgrades anything.
10
+
It is one file of standard-library Python (3.8+, Linux). It only reads: it never restarts, edits or upgrades anything, and by default it never runs a binary it finds (see Safety below).
11
11
12
12
Written for the DevOps Daily post: https://devops-daily.com/posts/node-october-2026-security-release-find-every-node
13
13
14
14
## What it checks
15
15
16
16
| Where | How |
17
17
| --- | --- |
18
-
| Running processes |`readlink /proc/<pid>/exe` for the real binary, then the version compiled into that file (read through `/proc/<pid>/exe`, so it works for deleted binaries and for processes inside containers). `--version` is also run when the file can only be changed by root or you, and the two must agree. |
18
+
| Running processes |`readlink /proc/<pid>/exe` for the real binary, then the version compiled into that file (read through `/proc/<pid>/exe`, so it works for deleted binaries and for processes inside containers), or `include/node/node_version.h` of the same install. A version from a directory name alone is shown as a hint and stays unknown. |
19
19
| pm2, live |`pm2 jlist` for your own `PM2_HOME` when its daemon is running: the daemon's binary, each entry's interpreter and script when they point into one Node install, the first `node` on the entry's saved `PATH` (or the daemon's Node for cluster mode, which forks workers from the daemon), and the Node processes really running under the entry. |
20
20
| pm2, saved |`dump.pm2`, the list `pm2 resurrect` and a reboot start. |
21
21
| Version managers | nvm, fnm, volta, asdf, mise, nodenv and n installs, and the default alias. |
@@ -49,12 +49,22 @@ for h in web1 web2; do ssh "$h" 'sudo python3 - --json' < node_runtime_audit.py
49
49
## Results
50
50
51
51
-`results/pm2-lab-20261022T063002Z.txt`: pm2 6.0.14 with Node v24.14.0 and v24.22.1.
52
-
-`examples/run-20261022T062905Z.txt`: the script on our Raspberry Pi.
52
+
-`examples/run-20261022T063927Z.txt`: the script on our Raspberry Pi.
53
53
-`results/registry-20261022T062338Z.json`: 24 official tags, amd64 and arm64.
54
54
-`results/pgrep-vs-proc-20261022T063334Z.txt`: `pgrep -c node` returns 0 on the Pi while five processes run a `node` binary.
55
55
56
56
Facts and their sources are in [SOURCES.md](SOURCES.md).
57
57
58
+
## Safety
59
+
60
+
The script is meant to run as root on servers where other users run Node, so it treats everything it finds as untrusted:
61
+
62
+
-**It does not execute the binaries it finds.** The version comes from the file itself: the `node-vX.Y.Z-headers.tar.gz` URL that official builds compile in, read through `/proc/<pid>/exe`, or `include/node/node_version.h` of the install. Running `<path> --version` as root on a path that another user controls would run their code as root.
63
+
-**`--exec-version` is opt-in** and only runs a binary when root (or you, when you are not root) owns the file and every directory from `/` down to it, and none of them is writable by group or others. As root it refuses any binary not owned by root. The file is opened with `O_PATH | O_NOFOLLOW`, checked with `fstat` against the inode the process runs, and executed through `/proc/self/fd/N`, so it cannot be swapped between the check and the exec. If the result disagrees with the embedded version, the answer is unknown.
64
+
-**Output is sanitized.** Command lines, process names, file names and container names and images come from other users and containers. Every control character (`0x00` to `0x1f`, `0x7f` and `0x80` to `0x9f`) is printed as `\xNN`, in text and in JSON, so nothing can send escape sequences to your terminal.
65
+
-**Secrets stay out.** It prints only the first two arguments of a command line and drops any that contain `=`. From pm2 environments, crontabs and units it prints only the Node paths, never whole lines or other variables.
66
+
- It only runs `pm2 jlist` for your own `PM2_HOME` and only when that daemon is already running, because `pm2 jlist` starts a daemon otherwise and, as root, would leave root-owned files in another user's `PM2_HOME`.
67
+
58
68
## Limits
59
69
60
70
- Linux only (`/proc`). Not root: other users' processes and crontabs are not visible, and the script says so.
Copy file name to clipboardExpand all lines: SOURCES.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -83,5 +83,5 @@ From `doc/changelogs/CHANGELOG_V22.md`, `CHANGELOG_V24.md` and `CHANGELOG_V26.md
83
83
84
84
-`results/pgrep-vs-proc-20261022T063334Z.txt`: on the Pi, `pgrep -c node` and `pgrep -c -x node` both returned 0 while five processes ran a `node` binary, named `PM2 v6.0.14: Go`, `MainThread` and `next-server (v1` in `/proc/<pid>/comm`.
85
85
86
-
-`examples/run-20261022T062905Z.txt` and `.json`: `node_runtime_audit.py --redact` on the Raspberry Pi we build on, 2026-10-22T06:29:05Z, as a normal user.
86
+
-`examples/run-20261022T063927Z.txt` and `.json`: `node_runtime_audit.py --redact` on the Raspberry Pi we build on, 2026-10-22T06:39:27Z, as a normal user.
87
87
- The production upgrade described in the post (six Node apps under pm2, installed with nvm) was done by the team on 21 October. Its details come from that work and are generalized; the lab reproduces the pm2 behaviors on a separate machine.
0 commit comments