Node.js adversary
Reviews JavaScript projects for security hazards, lifecycle cleanup leaks, and configured project-linter diagnostics.
Goals
The adversary is designed to produce a small number of high-confidence, actionable findings grounded in concrete repository evidence. Its review should be deterministic where possible, explicit about impact, and quiet when the available evidence does not justify a finding.
Scope
It evaluates Node.js source for command execution, dynamic evaluation, TLS, path confinement, weak randomness, VM isolation, and shared listener cleanup.
The complete detector or review inventory is maintained in CHECKS.md.
Boundaries
It owns framework- or language-specific review in this domain. Infrastructure, CI, dependency-manager, and unrelated application concerns remain with specialist adversaries.
Project lint
lang/nodejs owns the package-lint phase for JavaScript, JSX, TypeScript, and TSX
projects, including React and Next.js. Framework adversaries do not duplicate it.
Node.js detection includes these source extensions, package manifests, and supported linter
configuration. The CLI's existing composition deduplicates this adversary when it
is selected more than once.
For each discovered package.json with a nonempty scripts.lint, the phase
recognizes these project-installed tools:
| Tool | Supported commands | Structured output |
|---|---|---|
| ESLint | eslint ... | Built-in JSON formatter |
| Biome | biome lint ..., biome check ..., biome ci ... | GitLab Code Quality; inject --max-diagnostics=none unless explicitly set |
| Oxlint | oxlint ... | JSON formatter |
Direct commands work regardless of whether the project's package manager is npm,
Yarn, pnpm, or Bun. npm exec, yarn exec, pnpm exec, and bun exec wrappers
(with an optional --) resolve to the same local tool without lifecycle hooks or
downloads. npx, bunx, and bun x (optionally --no-install) likewise
resolve only installed local tool packages. Package/version requests, installer
options, and --bun runtime switches remain unsupported. Exact same-package
npm run NAME, pnpm run NAME, yarn run NAME, and bun run NAME aliases are
followed with cycle detection and an eight-alias depth bound; argument forwarding
and workspace selection are not inferred. All original scripts remain recorded
in check observations. Quoted paths, supported config and ignore options, rule selectors,
severity overrides, and tool-specific check options retain their order. Unknown
flags are skipped rather than guessed. Output-format options are replaced with
the adapter's built-in structured reporter; Biome colors are disabled for CI.
The executable is resolved from installed dependencies inside the scanned
repository (including ancestor workspace installs); nothing is downloaded or
installed.
Biome receives --max-diagnostics=none by default so its implicit console limit
of 20 is not carried into the invocation. An explicit --max-diagnostics VALUE
or --max-diagnostics=VALUE is preserved without injecting a duplicate. Reporter
behavior varies: Biome 2.5.15's GitLab reporter emits all diagnostics even with an
explicit finite limit. The SDK's separate final-finding ranking/count policy
still applies; collecting all tool diagnostics does not mean every diagnostic
becomes a presented finding. Time and output limits still bound execution.
Each package runs at most once per review. Root workspace delegation scripts are
unsupported and skipped; supported scripts in child packages run independently.
Identical diagnostics from overlapping runs of the same tool are deduplicated.
Full scans report source diagnostics. Source-only change reviews intersect
existing changed files with the script's directory, file, or glob targets and
invoke the tool on that intersection, retaining config and rule options. Empty
intersections are explicit skips. Config/dependency or other non-source edits,
ambiguous targets, and ESLint's version-dependent implicit target retain the
original script invocation. Check observations record executionScope as
changed-files or full-script. Findings still retain only changed files and
lines. Errors are medium severity and warnings are low severity. Biome
check and ci also report source-located formatting/assist diagnostics according
to the project's settings; file-level formatting diagnostics use the reporter's
line 1 anchor. Check observations retain the tool and exit status, including
nonzero exits without eligible source diagnostics. Skipped and unavailable
checks never fabricate code findings.
Mixed-tool command chains (including prettier --check . && eslint .), shell
operators, environment assignments, workspace/Turbo delegation, arbitrary
wrappers, next lint, other linters, and unknown options are unsupported. The
runner does not partially execute a chain and then call the overall script a
success. Prettier plugins/configuration loaded by ESLint remain supported; the
standalone Prettier CLI is not a tool adapter. Benign ESLint flags include
--no-color, --report-unused-disable-directives-severity, legacy --rulesdir
and --resolve-plugins-relative-to, and compact -fjson formatter syntax. The
installed ESLint version must support the original flags. Fixing, caching,
suppression writes,
initialization, file output, daemon use, and watch options are rejected. Yarn
Plug'n'Play installs without a resolvable repository tool package are skipped.
PnP archive/cache resolution requires a loader and a different dependency
boundary; missing tool resolution in a PnP project records pnp-unsupported.
Bun wrappers change command discovery, not runtime selection: all installed CLI
wrappers run under the adversary's Node 22 runtime for consistent behavior. No
prelint or postlint hooks are invoked.
Adapters validate each tool's output schema. Unsupported installed versions or malformed output become unavailable checks. Biome and Oxlint fixtures include captured output from Biome 2.5.15 and Oxlint 1.86.0, with volatile Oxlint timing and thread metadata removed. See the Biome CLI and Oxlint output formats for the reporter contracts.
Execution boundary
Unlike bundled actionlint, this phase runs repository-provided tool code, configuration, and plugins. It uses a direct argument array, a five-minute timeout, a 64 MiB output limit, and a minimal environment without inherited credentials. These limits are not a sandbox: config and plugins can execute arbitrary code, write files, or access the network. The production host must supply filesystem, process, and network isolation for untrusted repositories. The manifest's advisory permissions do not enforce that isolation. Missing dependencies, configuration failures, invalid output, and exceeded limits are reported as skipped or unavailable check observations. Timeouts and output-limit failures retain distinct reasons and limit values, rather than an undifferentiated failure. The low-level runner accepts per-invocation limit overrides for integrations.
SDK source coverage
This release uses SDK 0.1.43. This reviewer uses deterministic prepared evidence rather than repository-tool model retrieval, so it has no model coverage result to reinterpret. Existing evidence gates and deterministic findings are preserved.
SDK 0.1.43
This release rebuilds with SDK 0.1.43. SDK PR #73 adds a tested neutral fallback to formatOpinionAsync when concern wording validation is exhausted, preserving completed findings and the ship decision. Reviewers that do not call this API retain their existing opinion behavior. Provider and transport failures remain errors. Domain rules and evidence requirements are unchanged.