Sentinel-IR 0.2.1
https://www.npmjs.com/package/sentinel-ir/v/0.2.1
Thanks for support..
Release: "Sentinel-IR-MCP v0.2.2 on GitLab" (https://gitlab.com/jakubrojicek11/sentinel-ir-mcp/-/tree/v0.2.2?ref_type=tags)
Package: "sentinel-ir-mcp@0.2.2 on npm" (https://www.npmjs.com/package/sentinel-ir-mcp/v/0.2.2)
A focused improvement in AST-based analysis, six regression tests, and a closer look at two tricky JavaScript control-flow paths.
The first 30 seconds
What happens when dangerous code execution does not look like "eval()"?
Sometimes JavaScript stores "eval" in another variable and calls that variable later. A static analyzer that only looks for direct calls can miss the connection.
In Sentinel-IR 0.2.1, I focused on two situations where this can happen: "switch" fallthrough and "try/catch" control flow.
The update adds six regression tests. The goal is simple: detect the dangerous path when it is real, but avoid raising an alarm when the control flow makes that path impossible.
No magic. Just better reasoning about code.
Part 1: The 3-minute explanation for everyone
First, what does Sentinel-IR actually do?
Sentinel-IR is a small structural analysis layer for code.
Instead of sending an entire JavaScript or TypeScript source file to an AI model, it extracts structured facts using Tree-sitter.
Think of it as giving the AI a map instead of handing it an entire city.
The resulting information can include:
- Imports and exports.
- Routes and environment variables.
- Filesystem, network and process operations.
- Potentially dangerous code patterns.
- Risk signals and their source locations.
This makes it easier for another system to reason about code without treating every line of source as equally important.
Project: "Sentinel-IR on GitLab" (https://gitlab.com/jakubrojicek11/sentinel-ir)
The problem: a function can have another name
Consider this:
const execute = eval;
execute("1 + 1");
A beginner might look for the word "eval" and assume the problem is easy.
But the function is being called through another variable.
That variable is an alias. The analyzer must understand what it refers to, not just what its name looks like.
And JavaScript has a few places where this gets complicated.
Problem one: switch fallthrough
Look at this example:
let execute = (value) => value;
switch (mode) {
case 0:
execute = eval;
case 1:
execute("1 + 1");
break;
}
Notice something?
There is no "break" after "case 0".
JavaScript continues into the next case. The alias assigned in the first case can therefore be used in the second one.
A simple analyzer may treat these cases as completely separate blocks and miss the connection.
Think of "break" as a stop sign. Without it, the baton keeps moving.
Problem two: try/catch
Another example:
let execute = (value) => value;
try {
execute = eval;
throw new Error("Something happened");
} catch (error) {
execute("1 + 1");
}
The alias is assigned before the exception.
When the exception moves execution into "catch", the analyzer needs to understand that the assignment can reach the code there.
But the opposite matters too.
An assignment that happens after an unconditional throw cannot influence the catch block. An assignment in a try block without a later explicit throw should not automatically make the catch block look dangerous either.
This is where false positives and false negatives start competing for attention.
What changed in 0.2.1?
The update improves those two control-flow situations.
It adds checks for:
- Aliases carried through "switch" fallthrough.
- "break" statements that stop that flow.
- Later assignments that overwrite an alias.
- Assignments before a throw that can reach "catch".
- Throws occurring before an alias assignment.
- Try blocks without a later explicit throw.
The result should be more accurate detection, not simply more things marked as dangerous.
Part 2: The developer section
What changed inside the AST analysis?
The important change is not a new search for the word "eval".
It is more accurate tracking of alias assignments and their visibility across control-flow paths.
The implementation contains dedicated helpers for:
- Identifying equivalent AST nodes.
- Tracking function and execution contexts.
- Determining whether switch fallthrough is possible.
- Checking whether a try-to-catch path is reachable.
- Evaluating whether an alias assignment remains visible at a call site.
A parser detail worth mentioning
During the investigation, I encountered a subtle issue with Tree-sitter node wrappers.
Two JavaScript objects can represent the same logical AST node without being the same object reference.
Comparing them using:
nodeA === nodeB
is therefore not reliable for this kind of comparison.
The updated implementation compares the node type and source boundaries instead:
function evalAliasSameNode(a, b) {
return Boolean(
a &&
b &&
a.type === b.type &&
a.startIndex === b.startIndex &&
a.endIndex === b.endIndex
);
}
This gives the analysis a stable way to recognize the same logical node.
Source: "AST implementation in v0.2.1" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/blob/v0.2.1/src/ast-facts.js)
The six regression tests
The six new cases are documented in the release test suite.
Test scenario| Expected result
Alias passes through switch fallthrough| Critical
"break" blocks the alias flow| Low
Later case overwrites the alias| Low
Alias assigned before throw reaches catch| Critical
Throw happens before assignment| Low
No later explicit throw in try| Low
The tests verify more than a boolean detection result.
They also check that:
- The source parses successfully.
- "operations.dynamicEval" matches the expected result.
- The risk classification is correct.
- The "dynamic_code" risk signal agrees with the result.
Here is the core shape of the regression assertions:
const facts = AstFacts.extract(source);
assert.equal(facts.ast, true);
assert.equal(
Boolean(facts.operations.dynamicEval),
expected
);
assert.equal(
AstFacts.riskOf(facts),
expected ? "critical" : "low"
);
assert.equal(
facts.riskSignals.some(
signal => signal.signal === "dynamic_code"
),
expected
);
Full tests: "test/ast-facts.test.js, v0.2.1" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/blob/v0.2.1/test/ast-facts.test.js)
Validation results
The release work was tested locally before tagging.
Validation| Result
Existing tests| 100 passed
New regression tests| 6 passed
Complete test suite| 106/106 passed
Packaged tarball smoke checks| 7/7 passed
The package was also installed into a clean consumer project for a separate smoke test.
That matters because a successful source test does not automatically prove that the packaged artifact works after installation.
What this does not mean
Sentinel-IR is not a complete JavaScript interpreter.
Its control-flow analysis is a conservative approximation, not a full simulation of every possible runtime path. It works with structural facts extracted from source code and has documented limitations.
It should be treated as an analysis component, not as a guarantee that dangerous code will always be detected.
That distinction matters, especially when an AI agent uses the output to make decisions.
Final thoughts
Version 0.2.1 is a focused release.
It does not introduce a new language or a completely new architecture. It improves one important part of the existing analyzer: understanding when an alias to dynamic code execution can actually reach a call site.
Sometimes progress in static analysis is not about detecting more patterns.
Sometimes it is about understanding why two pieces of code are connected, and when they are not.
And occasionally, the biggest bug is hiding behind a missing "break".
Sentinel-IR 0.2.1 is released.
Project links
- "GitLab repository" (https://gitlab.com/jakubrojicek11/sentinel-ir)
- "Sentinel-IR 0.2.1 Release" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/releases/v0.2.1)
- "Version 0.2.0 source" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/tree/v0.2.0)
- "Version 0.2.1 source" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/tree/v0.2.1)
- "New regression tests" (https://gitlab.com/jakubrojicek11/sentinel-ir/-/blob/v0.2.1/test/ast-facts.test.js)
https://www.npmjs.com/package/sentinel-ir/v/0.2.1
Built and tested as an independent project. Feedback, edge cases and reproducible examples are welcome.

Top comments (0)