{"slug": "sentinel-ir-0-2-1-when-eval-hides-behind-an-alias", "title": "Sentinel-IR 0.2.1: When \"eval\" Hides Behind an Alias", "summary": "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.", "body_md": "Sentinel-IR 0.2.1\n\n[https://www.npmjs.com/package/sentinel-ir/v/0.2.1](https://www.npmjs.com/package/sentinel-ir/v/0.2.1)\n\nThanks for support..\n\nRelease: \"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))\n\nPackage: \"[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))\n\nA focused improvement in AST-based analysis, six regression tests, and a closer look at two tricky JavaScript control-flow paths.\n\nThe first 30 seconds\n\nWhat happens when dangerous code execution does not look like \"eval()\"?\n\nSometimes JavaScript stores \"eval\" in another variable and calls that variable later. A static analyzer that only looks for direct calls can miss the connection.\n\nIn Sentinel-IR 0.2.1, I focused on two situations where this can happen: \"switch\" fallthrough and \"try/catch\" control flow.\n\nThe 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.\n\nNo magic. Just better reasoning about code.\n\nPart 1: The 3-minute explanation for everyone\n\nFirst, what does Sentinel-IR actually do?\n\nSentinel-IR is a small structural analysis layer for code.\n\nInstead of sending an entire JavaScript or TypeScript source file to an AI model, it extracts structured facts using Tree-sitter.\n\nThink of it as giving the AI a map instead of handing it an entire city.\n\nThe resulting information can include:\n\nThis makes it easier for another system to reason about code without treating every line of source as equally important.\n\nProject: \"Sentinel-IR on GitLab\" ([https://gitlab.com/jakubrojicek11/sentinel-ir](https://gitlab.com/jakubrojicek11/sentinel-ir))\n\nThe problem: a function can have another name\n\nConsider this:\n\nconst execute = eval;\n\nexecute(\"1 + 1\");\n\nA beginner might look for the word \"eval\" and assume the problem is easy.\n\nBut the function is being called through another variable.\n\nThat variable is an alias. The analyzer must understand what it refers to, not just what its name looks like.\n\nAnd JavaScript has a few places where this gets complicated.\n\nProblem one: switch fallthrough\n\nLook at this example:\n\nlet execute = (value) => value;\n\nswitch (mode) {\n\n    case 0:\n\n        execute = eval;\n\n```\ncase 1:\n    execute(\"1 + 1\");\n    break;\n```\n\n}\n\nNotice something?\n\nThere is no \"break\" after \"case 0\".\n\nJavaScript continues into the next case. The alias assigned in the first case can therefore be used in the second one.\n\nA simple analyzer may treat these cases as completely separate blocks and miss the connection.\n\nThink of \"break\" as a stop sign. Without it, the baton keeps moving.\n\nProblem two: try/catch\n\nAnother example:\n\ntry {\n\n    execute = eval;\n\n    throw new Error(\"Something happened\");\n\n} catch (error) {\n\n    execute(\"1 + 1\");\n\n}\n\nThe alias is assigned before the exception.\n\nWhen the exception moves execution into \"catch\", the analyzer needs to understand that the assignment can reach the code there.\n\nBut the opposite matters too.\n\nAn 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.\n\nThis is where false positives and false negatives start competing for attention.\n\nWhat changed in 0.2.1?\n\nThe update improves those two control-flow situations.\n\nIt adds checks for:\n\nThe result should be more accurate detection, not simply more things marked as dangerous.\n\nPart 2: The developer section\n\nWhat changed inside the AST analysis?\n\nThe important change is not a new search for the word \"eval\".\n\nIt is more accurate tracking of alias assignments and their visibility across control-flow paths.\n\nThe implementation contains dedicated helpers for:\n\nA parser detail worth mentioning\n\nDuring the investigation, I encountered a subtle issue with Tree-sitter node wrappers.\n\nTwo JavaScript objects can represent the same logical AST node without being the same object reference.\n\nComparing them using:\n\nnodeA === nodeB\n\nis therefore not reliable for this kind of comparison.\n\nThe updated implementation compares the node type and source boundaries instead:\n\nfunction evalAliasSameNode(a, b) {\n\n    return Boolean(\n\n        a &&\n\n        b &&\n\n        a.type === b.type &&\n\n        a.startIndex === b.startIndex &&\n\n        a.endIndex === b.endIndex\n\n    );\n\n}\n\nThis gives the analysis a stable way to recognize the same logical node.\n\nSource: \"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))\n\nThe six regression tests\n\nThe six new cases are documented in the release test suite.\n\nTest scenario| Expected result\n\nAlias passes through switch fallthrough| Critical\n\n\"break\" blocks the alias flow| Low\n\nLater case overwrites the alias| Low\n\nAlias assigned before throw reaches catch| Critical\n\nThrow happens before assignment| Low\n\nNo later explicit throw in try| Low\n\nThe tests verify more than a boolean detection result.\n\nThey also check that:\n\nHere is the core shape of the regression assertions:\n\nconst facts = AstFacts.extract(source);\n\nassert.equal(facts.ast, true);\n\nassert.equal(\n\n    Boolean(facts.operations.dynamicEval),\n\n    expected\n\n);\n\nassert.equal(\n\n    AstFacts.riskOf(facts),\n\n    expected ? \"critical\" : \"low\"\n\n);\n\nassert.equal(\n\n    facts.riskSignals.some(\n\n        signal => signal.signal === \"dynamic_code\"\n\n    ),\n\n    expected\n\n);\n\nFull 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))\n\nValidation results\n\nThe release work was tested locally before tagging.\n\nValidation| Result\n\nExisting tests| 100 passed\n\nNew regression tests| 6 passed\n\nComplete test suite| 106/106 passed\n\nPackaged tarball smoke checks| 7/7 passed\n\nThe package was also installed into a clean consumer project for a separate smoke test.\n\nThat matters because a successful source test does not automatically prove that the packaged artifact works after installation.\n\nWhat this does not mean\n\nSentinel-IR is not a complete JavaScript interpreter.\n\nIts 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.\n\nIt should be treated as an analysis component, not as a guarantee that dangerous code will always be detected.\n\nThat distinction matters, especially when an AI agent uses the output to make decisions.\n\nFinal thoughts\n\nVersion 0.2.1 is a focused release.\n\nIt 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.\n\nSometimes progress in static analysis is not about detecting more patterns.\n\nSometimes it is about understanding why two pieces of code are connected, and when they are not.\n\nAnd occasionally, the biggest bug is hiding behind a missing \"break\".\n\nSentinel-IR 0.2.1 is released.\n\nProject links\n\nBuilt and tested as an independent project. Feedback, edge cases and reproducible examples are welcome.", "url": "https://wpnews.pro/news/sentinel-ir-0-2-1-when-eval-hides-behind-an-alias", "canonical_source": "https://dev.to/jackymencz/sentinel-ir-021-when-eval-hides-behind-an-alias-2pkj", "published_at": "2026-11-01 06:49:15+00:00", "updated_at": "2026-11-01 06:51:56.799132+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "ai-safety", "ai-agents"], "entities": ["Sentinel-IR", "sentinel-ir-mcp", "Tree-sitter", "npm", "GitLab"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/sentinel-ir-0-2-1-when-eval-hides-behind-an-alias", "markdown": "https://wpnews.pro/news/sentinel-ir-0-2-1-when-eval-hides-behind-an-alias.md", "text": "https://wpnews.pro/news/sentinel-ir-0-2-1-when-eval-hides-behind-an-alias.txt", "jsonld": "https://wpnews.pro/news/sentinel-ir-0-2-1-when-eval-hides-behind-an-alias.jsonld"}}