Skip to content

feat(nsolid): ns-analyze-behaviors explains a capability the package's code never imports, and calls from code it compiled from a string - #79

Open
brycebaril wants to merge 4 commits into
nsolid-console-7from
behaviors-not-in-code
Open

brycebaril wants to merge 4 commits into
nsolid-console-7from
behaviors-not-in-code

Conversation

@brycebaril

@brycebaril brycebaril commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The N|Solid MCP server's list_behaviors now marks two kinds of behavior, and ns-analyze-behaviors explains both.

  • notInScan: the package was seen running programs or compiling code, but its published code imports none of the Node.js modules that do it. 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 explains the mark, and the skill says what can:

    • the app or another package hands it a function from that module;
    • a loader the scan doesn't follow;
    • code on disk that isn't the published package's.

    If the version's checks show risk/uses-dynamic-require or risk/uses-eval anyway, the skill treats the scan as stale or the checks as another version's, and confirms which before using them. The package version also fails observed-not-in-scan in app_packages.

  • compiled: the calls came from code the package compiled from a string (new Function, eval). That's the shape of an injected payload. The skill asks whether the package compiles code by design: a template engine, a schema validator or an expression evaluator does, and such packages rarely run programs or reach the network from that code. With notInScan or versionChange as well, it treats the behavior as a likely compromise.

Each mark weighs a step more. No tool or argument changes.

🤖 Generated with Claude Code

…s code never imports

list_behaviors now marks a behavior notInScan when the package's published
code imports none of the modules it was seen calling. The skill says what
that means, and how app_packages' failing checks (uses-dynamic-require,
uses-eval) can explain it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ns-control-tower

ns-control-tower commented Oct 23, 2026 •

Copy link
Copy Markdown

Walkthrough

Docs-only change to skills/ns-analyze-behaviors/SKILL.md (final diff vs nsolid-console-7, merge base 29aad46: 1 file, +5 lines, 4 commits; no code, tool, or argument changes). It documents two marks list_behaviors can put on a behavior:

  • Lines 15, 24–25 — notInScan (commits 1–2): the package was seen running programs or compiling code 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, and the package version fails observed-not-in-scan in app_packages. Investigation guidance lists what can explain it (a function handed from the module, a loader the scan doesn't follow, code on disk that isn't the published package's) and treats risk/uses-dynamic-require / risk/uses-eval as non-explanations — confirm the exact version and scan freshness instead.
  • Lines 16, 26 — compiled (commits 3–4): the calls came from code compiled from a string (new Function, eval) — the shape of an injected payload; it does not name the compiling package: 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. Judging guidance: check whether that package, or one handing it functions, compiles code by design (a template engine, a schema validator or an expression evaluator) and whether the calls fit what it compiles; with notInScan or versionChange as well, treat it as a likely compromise only while the calls stay unexplained or other evidence backs it.

Commit 2e113e2c reworks lines 16, 24 and 26 (3 insertions, 3 deletions vs 596d50c) to address the three findings from the latest human recheck at 596d50c.

Assessment

  • ✔ Resolved at 2e113e2c — P2 on compiled attribution: line 16 no longer implies the named package compiled the 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.
  • ✔ Resolved at 2e113e2c — P2 on the compromise conclusion: line 26 now conditions "likely compromise" on the calls staying unexplained or other evidence backing it, with the template-engine example inline.
  • ✔ Resolved at 2e113e2c — P3 on the observed modules: line 24 uses app_packages only for the failing observed-not-in-scan check and points to the behavior's modules in list_behaviors for the observed modules.
  • The earlier P2 on notInScan gating remains fixed — lines 15 and 25 are untouched by 2e113e2c.
  • No new findings at 2e113e2c: the reworked prose is internally consistent with the rest of the skill, and notInScan / compiled / observed-not-in-scan / risk/uses-* still appear only in this file.
  • Repo hygiene checks re-run at 2e113e2c in the review clone: node scripts/sync-plugin-assets.mjs --check → "plugin:sync up to date."; node scripts/materialize-github-marketplace.mjs --check → "plugin:root:check OK — root manifests match bundle.json".
  • CI: test run 42158523694 (.github/workflows/test.yml) succeeded for 2e113e2c. The workflow lints, builds and tests code and only asserts that skill files exist — it does not exercise skill prose, so no CI signal is relied on for this verdict.
  • Assumption on record (as in the earlier reviews): app_packages, observed-not-in-scan, risk/uses-dynamic-require, risk/uses-eval and the compiled attribute belong to the external nsolid MCP server and appear nowhere in this repository; their semantics are taken as given from the server-side changes the commit messages describe.
  • No PR Lens diagram: the diff is pure prose with no component interaction to draw (unchanged from the previous reviews).

Verdict: APPROVE — all three findings from the latest human recheck are verifiably fixed at 2e113e2c, the rework introduces no new findings, and the repository's hygiene checks and CI pass at this head.

Comment thread skills/ns-analyze-behaviors/SKILL.md Outdated
- `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` lists the version's failing checks. `risk/uses-dynamic-require` (a module imported by a computed name) or `risk/uses-eval` (code compiled from a string) could explain how it reached the module. With neither, it got there by an unusual loader (such as `module.constructor._load`) or through code that isn't in the published package, fetched at run time: either is a strong sign.

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] Align the notInScan investigation with the backend's current gates

The current backend does not mark a scanned version notInScan when it contains a computed import or eval: seenNotInScan explicitly returns false for either scan.dynamicImport.count > 0 or scan.eval.count > 0, and its unit tests cover both exclusions.

This new guidance therefore sends the analyst looking for checks that prevent the mark from being emitted for that version. Please update this paragraph and the examples above to explain that the mark requires a complete scan with neither condition. If those checks appear alongside an existing mark, first verify the exact package version and scan freshness rather than presenting them as its normal explanation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, thanks. Fixed in 1c68a46:

  • Step 1 now says the mark needs a scan that read all of the version's code and found no computed import and no eval. So neither can explain it.
  • This step lists what can: a function the app or another package hands it from the module, a loader the scan doesn't follow, or code on disk that isn't the published package's.
  • If risk/uses-dynamic-require or risk/uses-eval do show up next to the mark, the skill treats it as a stale scan or another version. It confirms the exact version and when that version was scanned before using them.

… a computed import or eval

N|Solid marks a behavior `notInScan` only when the scan read all of the
version's code and found no computed import and no `eval`, so neither can
explain it. The skill sent the analyst looking for those checks. It now
lists what can: a function handed to the package from the module, a loader
the scan doesn't follow, or code on disk that isn't the published
package's. If those checks do show up next to the mark, the scan changed
since the behavior was marked or they belong to another version, so the
skill checks the exact version and when it was scanned first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iled from a string

list_behaviors now marks a behavior `compiled` when its calls came from code
the package compiled with `new Function` or `eval`, and weighs it a step
more. The skill says what the mark means and how to judge it: a package that
compiles code by design, such as a template engine, rarely runs programs or
reaches the network from that code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brycebaril brycebaril changed the title feat(nsolid): ns-analyze-behaviors explains a capability the package's code never imports feat(nsolid): ns-analyze-behaviors explains a capability the package's code never imports, and calls from code it compiled from a string Nov 4, 2026
@brycebaril
brycebaril requested a review from dobleuber November 4, 2026 18:33
@brycebaril

Copy link
Copy Markdown
Contributor Author

@dobleuber, ready for another look when you have a moment. Your P2 is fixed in 1c68a46: the skill now says the notInScan mark needs a scan that read all of the version's code and found no computed import and no eval, so neither explains it. 596d50c adds the compiled mark, which the N|Solid MCP server's list_behaviors returns now. CI is green.

@dobleuber dobleuber left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rechecked at 596d50c. The earlier notInScan gating feedback is addressed. Three remaining findings are attached inline.

Comment thread skills/ns-analyze-behaviors/SKILL.md Outdated
- 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 the package 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. The compiling itself shows nowhere; the calls count for the package whose compiled code made them. 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] Qualify which package compiled the code

compiled establishes that the call came through string-compiled code, but it does not establish which package created that code. For example, package A can create a function with new Function and hand it to package B. If B invokes it and it calls a Node.js built-in, the profiler attributes the call to the nearest identifiable package on that stack, B. I reproduced that attribution with a synthetic stack and an ordinary-code control.

Saying B compiled the function would send the investigation to the wrong package. Please describe the flag as a call from string-compiled code, and qualify package attribution rather than treating it as proof of compiler provenance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right: a call from string-compiled code counts for the nearest package below it on the stack, the one that called it. So it shows who ran that code, not who compiled it. 2e113e2 says so, and that another package may have compiled the function and handed it over (a callback, an option).

Comment thread skills/ns-analyze-behaviors/SKILL.md Outdated
- `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's `observed-not-in-scan` check and the modules it named. 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 the package 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.

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] Preserve the expected-use check before concluding likely compromise

A legitimate template-engine upgrade can intentionally start reading partials through node:fs from generated render functions. That can produce both compiled and versionChange, even when the calls fit the package's documented purpose.

The final sentence nevertheless instructs the analyst to treat that combination as a likely compromise, overriding the expected-use assessment earlier in the paragraph and the later distinction between unexplained and expected behavior. Please make this conclusion conditional on the calls remaining unexpected or unexplained, or on corroborating evidence.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. In 2e113e2 it's a likely compromise only while the calls stay unexplained or other evidence backs it, with your template-engine case as the example. The check also asks about a package that hands it functions, since that one may be the compiler.

Comment thread skills/ns-analyze-behaviors/SKILL.md Outdated
- `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's `observed-not-in-scan` check and the modules it named. 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P3] Read the observed modules from list_behaviors

app_packages exposes the per-version failing check as group/name:severity (with its source), but its MCP response does not include the check title, details, or modules. Following this step therefore cannot retrieve the promised module evidence from that tool.

Please use app_packages to confirm the version's observed-not-in-scan check and point to list_behaviors.modules for the observed modules.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, app_packages gives the check only as group/name:severity. In 2e113e2 the failing check comes from app_packages and the modules from the behavior's modules in list_behaviors.

…iler

A call from code compiled from a string counts for the nearest package
below it on the stack, the one that called it. Another package may have
compiled that function and handed it over, so the skill no longer says
the named package compiled it.

A compiled behavior that also has notInScan or versionChange is a likely
compromise only while its calls stay unexplained or other evidence backs
it: a template engine's new version may read partials through node:fs by
design.

app_packages names the failing observed-not-in-scan check but not the
modules; those are the behavior's modules in list_behaviors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brycebaril
brycebaril requested a review from dobleuber November 6, 2026 03:46

@dobleuber dobleuber left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One remaining finding from the full-range re-review at 2e113e2 is attached inline.

- 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.

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.

3 participants