Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions skills/ns-analyze-behaviors/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,18 @@ The watch learns what each app's packages do from its CPU profiles, and reports
- `list_behaviors` (open ones, most severe first), with `app` when the user names one, `kind` (`capability`, `package`, `regression`, `drift`) or `where`, e.g. `severity >= "high"`, `capability == "network"`, `versionChange`.
- Each behavior names the app, the package and its versions, what it `does`, its severity, the modules and functions involved, when it was first and last seen, and how many profiles showed it against how many before did not.
- `versionChange` means it arrived with a new version of the package while earlier versions never did it. That is how a compromised release of a dependency shows at run time, and it weighs a step more.
- `notInScan` means the package was seen running programs or compiling code that its published code never imports. N|Solid marks it only when the scan read all of that version's code and found no computed import and no `eval`, so neither of those explains it. It weighs a step more too, and the package version fails `observed-not-in-scan` in `app_packages`.
- `compiled` means the calls came from code compiled from a string (`new Function`, `eval`): the shape of an injected payload, which is how a remote code execution runs what it was handed. It doesn't say which package compiled that code: the calls count for the nearest package below it on the stack, the one that called it, and another package may have compiled it and handed it over (a callback, an option). It weighs a step more too.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Limit compiled to at least one observed call

The behavior-level compiled flag is true if any observed call came from string-compiled code; it does not apply to every entry in modules or functions. The aggregation retains all function names while combining the flag with a maximum/OR.

I reproduced a single package version with a generated node:fs.readFileSync call and an ordinary node:fs.writeFileSync call. Both functions belong to the same capability record, which has compiled: true. Saying "the calls came from code compiled from a string" can therefore make the analyst attribute both calls to generated code and explain away an ordinary call as expected template activity.

Please say that at least one observed call came from string-compiled code, and clarify that this flag alone does not identify which listed calls came through generated code.


### 2. Judge Each One
- **New capability** (`process`: runs programs; `code`: compiles code; `network`; `threads`; `fs`): is it expected of that package? A logging library that starts spawning processes, or a date library that starts using the network, is suspicious; a database driver using the network is not.
- `package_calls` with the `app` and `callee` (e.g. `node:child_process`, `node:net`) shows which packages call that built-in, with which version and function, and since when.
- `compare_release_calls` for the app's release that brought it shows the call against the release before, and the package upgrades that came with it.
- `find_package` with the package name shows which apps and processes run which versions of it.
- `list_vulnerabilities` with `where: '"<package>" in packages'` shows whether the version is a known malicious or vulnerable release (`MAL-…` reports are malicious packages).
- For a `notInScan` behavior, `app_packages` with the `app` shows the version failing `observed-not-in-scan`, and the behavior's `modules` in `list_behaviors` are the ones it was seen using. What can explain it: the app or another package hands it a function from that module (a callback or an option); a loader the scan doesn't follow (such as `module.constructor._load`); or code on disk that isn't the published package's, patched into the image or written at run time. The last is a strong sign, and so is a `drift` behavior on the same package.
- If that version's checks also include `risk/uses-dynamic-require` or `risk/uses-eval`, they don't explain the mark: the scan changed since the behavior was marked, or the checks are for another of its versions. Confirm the exact version and when it was scanned before drawing anything from them.
- For a `compiled` behavior, ask whether that package, or one handing it functions, compiles code by design (a template engine, a schema validator or an expression evaluator does) and whether the calls fit what it compiles. Such packages rarely run programs or reach the network from that code. With `notInScan` or `versionChange` as well, treat it as a likely compromise only while the calls stay unexplained or other evidence backs it: a template engine's new version may read its partials through `node:fs` by design.
- **New package** (running at all for the first time): which dependency brought it in (`get_sbom` with the `app` and `package` gives the chain) and whether that was expected.
- **Regression**: a package taking more of the CPU in a release; `compare_releases` shows the rest of that release. Hand it to `ns-analyze-release`.
- **Drift**: dependency versions that changed with no new release (a rebuilt image of the same version, or a change on disk under a running process). Ask whether the build pins its lockfile; an unexpected change under a running process deserves an incident.
Expand Down
Loading