Sentinel-IR 0.2.1: When "eval" Hides Behind an Alias A developer released Sentinel-IR 0.2.1, an AST-based static analysis layer that uses Tree-sitter to extract structured facts from JavaScript and TypeScript so AI systems can reason about code without ingesting full source files. The update adds six regression tests and improves alias tracking for indirect eval calls across switch fallthrough and try/catch control flow, aiming to catch real dangerous paths while avoiding false positives when control flow makes them impossible. Sentinel-IR 0.2.1 https://www.npmjs.com/package/sentinel-ir/v/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 https://gitlab.com/jakubrojicek11/sentinel-ir-mcp/-/tree/v0.2.2?ref type=tags Package: " sentinel-ir-mcp@0.2.2 mailto:sentinel-ir-mcp@0.2.2 on npm" https://www.npmjs.com/package/sentinel-ir-mcp/v/0.2.2 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: 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 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: 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: 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: 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 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: 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 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 Built and tested as an independent project. Feedback, edge cases and reproducible examples are welcome.