Dependency & Misconfiguration Scanning
nubos-pilot scans the code and dependencies it writes for known vulnerabilities, leaked credentials, malicious packages, risky infrastructure config and licence problems. It runs offline, without a model call, and needs no external binary.
It exists because the np-dependency-audit skill already demanded a CVE check and a typosquat check, and nothing in the repository ran either. The bar was prose an executor could satisfy by claiming it. See ADR-0031.
What it checks
| Scanner | Looks at | Rule ids |
|---|---|---|
| Advisory | installed versions against a shipped advisory snapshot | NPS-0300 |
| Malicious package | installed packages against removal-queue records | NPS-0400 |
| Secrets | 95 provider credential shapes plus entropy | NPS-0100..0194 |
| Misconfiguration | CI workflows, Kubernetes, Compose, container buildfiles, HCL | NPS-0500..0729 |
| Licence | SPDX policy over the dependency tree | NPS-0801..0806 |
The dependency inventory underneath covers eight ecosystems and fourteen manifest and lockfile formats, normalised to one record per package with a package URL and a resolved scope.
Running it
node np-tools.cjs scan # everything
node np-tools.cjs scan advisory --json # one scanner, machine output
node np-tools.cjs scan sbom # CycloneDX 1.6
node np-tools.cjs scan db-status # is the advisory data there, and how old--fail-on high|critical turns a run into a CI gate. The default is never, so a scan cannot break a build by surprise. --min-severity sets the reporting floor. Both reject an unknown value instead of quietly using a weaker one.
The same scanners run non-blocking on every file write through the in-session security review hooks, so a lockfile edit is checked as it happens.
A check that cannot answer says so
This is the part worth knowing. A scanner that stays quiet when it could not do its job is indistinguishable from one that found nothing, so every gap is reported instead:
NPS-0301— no advisory snapshot on disk. Nothing was checked for vulnerabilities.NPS-0302— a snapshot shard failed its SHA-256. No vulnerability verdict is issued from it at all.NPS-0303— no version comparator for that ecosystem.NPS-0679— a Terraform value is a variable or function call, so the rule could not decide.
Secret findings never carry the matched value, and neither do hardcoded-credential findings in Compose, container buildfiles or HCL. The key is named, the value is not.
The advisory data
A vulnerability corpus is a dataset, not an algorithm. The matcher is built into nubos-pilot; the advisories are shipped with it.
npm run advisory:build fetches one archive per language ecosystem at release time, filters by a source-licence allowlist, drops withdrawn records and the fields the match does not need, and writes per-ecosystem gzip shards with a SHA-256 each. The result ships inside the npm package and is copied once into ~/.nubos-pilot/advisory-db/<version>/, so several projects on one machine share one copy.
It is deliberately not part of the install payload — ADR-0002 and ADR-0005 pin that tree to .cjs and Markdown.
Two consequences to plan around:
- The snapshot is frozen at release. Every finding set carries
db_generated_at, andscan db-updatefetches a delta.npx nubos-pilot doctorreports a snapshot older than 90 days. - A tampered store is worse than no store, because it reports vulnerabilities as absent. Every shard is verified against the manifest digest before it is decompressed, and
doctortreats a mismatch aserrorseverity.
Version comparison
Eight ecosystems, four of which do not follow SemVer. A wrong comparison is a silent false negative, so each comparator was tested against the ecosystem's own reference implementation across more than 320,000 version pairs.
That caught two real cases: in Java, 5.2.25.RELEASE has to compare equal to 5.2.25, and in PHP 1.0.0-stable1 has to equal 1.0.0. Without either fix, every Spring and Netty artifact would have come back unaffected.
An unparseable installed version is skipped. An unparseable advisory boundary throws, because bad data in the shipped store should be loud.
Scope
Source repositories, not running infrastructure. Container images, VM images, OS packages and live clusters are out of scope: they need a registry client and parsers for binary OS package databases, and the distribution advisory feeds are the ones the licence allowlist excludes. A dev-time planning tool does not scan operating systems.
Within the covered ecosystems the match is simpler than a mature scanner's. There is no reachability analysis and no vendor suppression data, so dev-scope packages are excluded from gating by default (security.scan.ignore_scopes). If a dev dependency runs in CI with repository credentials, that default is wrong for it.
Configuration
Everything lives under security.scan in .nubos-pilot/config.json; see Configuration.
