Skip to content

feat: inventory language runtimes on all platforms and Windows system packages - #261

Draft
swarit-stepsecurity wants to merge 7 commits into
step-security:mainfrom
swarit-stepsecurity:swarit/feat/wt/runtime-inventory
Draft

swarit-stepsecurity wants to merge 7 commits into
step-security:mainfrom
swarit-stepsecurity:swarit/feat/wt/runtime-inventory

Conversation

@swarit-stepsecurity

Copy link
Copy Markdown
Member

A customer asked for Windows coverage on System Packages after a Node.js fire drill. Qualys finds node by running node -v on PATH and misses everything else: nvm, fnm, Volta, Scoop, zips, and copies bundled inside other apps. Today the Windows tab is greyed out, and none of the three platforms has a way to answer "where is every install of runtime X".

This PR adds two collectors (phase 1 of 3; agent-api and console follow).

Runtime inventory (macOS, Linux, Windows): new phase runtime_scan, new payload field runtime_scan.

  • One signature table covers node, python, php, java, go, ruby, deno, bun and dotnet. Each row lists the executable names, the version strategies and the known install roots.
  • Versions are read without executing anything:
    • files the install ships (node_version.h, patchlevel.h, Java release, Go VERSION, rbconfig.rb, php_version.h)
    • on Windows, the PE version resource
    • the install path
    • otherwise unknown
  • Each record gets an installer attribution (nvm, fnm, volta, pyenv, uv, sdkman, brew, scoop, choco, xampp, go-toolchain, system, installer, manual, …).
  • Runtimes shipped inside an app are flagged bundled_by (Xcode, Visual Studio, JetBrains jbr, VS Code Server/extensions, Adobe, …).
  • The walk covers the user's home and the machine install locations, plus deeper walks of known version-manager roots. It is breadth-first with a per-root entry budget, so truncation drops the deepest levels instead of whole subtrees, and it reports truncated.
  • The walk skips node_modules, VCS dirs, the Go module cache and virtualenvs (the python scan covers venvs). It never follows symlinks or junctions, and resolved binaries are deduped by real path.
  • On by default; --disable-runtime-scan / enable_runtime_scan turns it off.

Windows system packages: sent in the existing system_package_scans with new scan_types arp, choco, scoop and winget.

  • ARP: the Uninstall keys from HKLM (64- and 32-bit views) and HKCU, filtered like Programs and Features (no SystemComponent, no updates or hotfixes, no nameless entries).
  • Chocolatey nuspecs, Scoop manifests (user and global), and winget portable package dirs.
  • Registry and file reads only. Neither winget nor Win32_Product is invoked.
  • SystemPackage gains scope (machine/user).

Backend note: agent-api has no platform discriminator on system packages yet, so until phase 2 lands, Windows rows would show up under the Linux tab. Merge this after (or together with) the agent-api change.

Testing:

  • Fixture-tree tests for every runtime layout on Unix and Windows: versions, attribution, bundled, dedupe, depth cap, budget truncation, cancellation.
  • reg query twin tests for ARP filtering, plus Chocolatey, Scoop and winget parsing.
  • A real --json run on macOS found 21 installs in about 1.8 s. That run caught three bugs, each now covered by a test: #define parsing ate NODE_MAJOR_VERSION after an include guard, the Python.framework/.../Python dylib matched as an interpreter, and the walk was depth-first so the budget starved later subtrees.
  • Not yet run on a real Windows or Linux box; that's next.

… packages

Add a runtime inventory (node, python, php, java, go, ruby, deno, bun,
dotnet) that finds installs on disk regardless of installer, and collect
Windows system packages from the Uninstall registry keys, Chocolatey,
Scoop and winget portable packages.
Comment thread internal/detector/winpkg.go Fixed
Report truncated_roots when only per-root budgets ran out, so the backend
can replace installs outside those roots instead of merging everything.
Give /Applications and Program Files a larger budget and walk only pnpm's
nodejs dir, not its package store.
From a Fedora end-to-end run:
- skip version-manager launchers (links to a non-runtime binary, scripts,
  scoop .shim) and conda's pkgs cache
- accept versioned distro binary names (node-22, ruby-mri)
- read distro runtime versions from rpm/dpkg (version_source "pkg") and
  versioned executable names
- read ruby's version from MAJOR/MINOR/TEENY when RUBY_PROGRAM_VERSION is
  a template
- attribute ~/.volta, ~/sdk/go*, ~/.dotnet; mark runtimes inside another
  snap as bundled by it
Comment thread internal/detector/runtimeinv/misc.go Fixed
- scoop: read scoop-manifest.json / scoop-install.json (Scoop 0.6) with the
  old names as fallback
- choco: vendor from nuspec authors, maintainer from owners; install time
  from the package dir, not the packaged nuspec
- runtimes: skip version-manager shim directories (volta, scoop shims,
  chocolatey bin, pyenv/rbenv/asdf/mise shims) and the stdlib venv
  launcher templates; bundled detection covers Electron resources/app and
  Eclipse's justj JRE; walk winget portable packages deeper
When no version file exists (single-binary installs, VS Code Server's
node), stream the executable for the runtime's own version string
(nodejs.org release URL, Deno/x.y.z, Bun/x.y.z) and report it with
version_source "binary". The binary is read, never executed; the scan is
capped at 256 MiB and the highest match wins.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants