Securing Agentic Data Pipelines

Why eval() Has No Place in Agentic Data Pipelines
Friends don’t let friends use eval() in production, especially when securing AI agent data pipelines.
Key Takeaways:
- Combining dynamic evaluation (
eval(),new Function()) with agentic data ingestion creates direct paths to Remote Code Execution (RCE). - Indirect prompt injections can escape reasoning context into host runtime behavior if downstream rendering evaluates code dynamically.
- Constrained, AST-based template interpreters (like Knap) replace arbitrary execution with deterministic schema rendering.
- Robust agent design separates probabilistic LLM reasoning from deterministic data transformation and analytical calculation.
That advice predates the current wave of AI agents, but agentic architectures have made it relevant again.
Modern AI systems increasingly need to transform structured data into reports, notes, dashboards, configuration files, and other human-readable artifacts. Developers use templating systems, expression evaluators, and dynamic rendering libraries to handle this.
The risk appears when rendering logic crosses into runtime code evaluation.
In JavaScript, functions such as eval() and new Function() take strings and execute them as JavaScript. MDN classifies eval() as an injection sink and warns that untrusted strings can become a vector for arbitrary code execution and cross-site scripting (XSS). OWASP similarly recommends avoiding eval() and new Function() where user-controlled input can reach them.
The Risk: From User Input to Remote Code Execution
There are some JavaScript functions that are dangerous and should only be used where necessary or unavoidable. The first example is the
eval()function. This function takes a string argument and executes it as any other JavaScript source code. Combined with user input, this behavior inherently leads to remote code execution vulnerability.
For conventional applications, this is already a security concern. Agentic systems increase the number of places where untrusted or weakly trusted information can enter the execution path.
An agent may ingest a document, API response, database record, schema description, retrieved web page, user note, or output from another agent.
If externally sourced content can influence a string that is later passed into a dynamic evaluator, the system has created a path from data into executable instructions. In an agentic architecture, where information may pass through several models, tools, and transformations before reaching the renderer, identifying and defending that boundary becomes considerably harder.
Why AI Agents Turn Dynamic Evaluation into an RCE Vector

Consider a reporting agent. Agentic BI is becoming more common, as we discussed in our previous article on creating fully functional Power BI dashboards with AI and natural language. The agent receives structured financial data, selects a template, performs calculations, and produces a Power BI report.
The LLM might only be responsible for deciding what information belongs in the report. The surrounding system, such as Claude Skills or an MCP server, still needs logic for conditions, loops, formatting, property access, calculations, and document generation.
A conventional template engine makes this easy. Some engines, plugins, and home-grown implementations achieve that flexibility by compiling template expressions into JavaScript or evaluating strings at runtime.
The risk increases once externally sourced content enters the pipeline. Data might be scraped from a webpage, pulled from a Git repository or file host, returned by an API, or supplied by another user.
For example, an agent might retrieve metadata from an API, a user might add a note from a colleague, or another tool might return an unexpected field. A compromised upstream source could embed malicious instructions or crafted syntax inside one of those values.
Indirect prompt injection is already a recognized problem in agentic systems because external tool output can introduce malicious instructions into an agent’s reasoning context. If downstream rendering also introduces dynamic code evaluation, the attack surface becomes considerably more serious. Content can move from influencing model behaviour to potentially influencing executable runtime behaviour.
The exact consequences depend on where the evaluator runs, and that needs to be the focus when securing AI agent data pipelines.
In a browser, unsafe dynamic evaluation can contribute to XSS and arbitrary script execution. In a server-side JavaScript environment, successful code execution may expose application capabilities, credentials, files, network access, or other resources available to the process. OWASP specifically notes that combining eval() with user-controlled input can result in remote code execution in Node.js applications.
This architecture is widely used in conversational (chat-bots) and agentic applications. A front-end chat interface accepts user input or externally sourced input, that information moves through tools and orchestration layers, and downstream services may use it to construct queries, invoke functions, update databases, or generate artifacts. Many of these systems use JavaScript or Node.js somewhere in the execution path.
Where dynamic evaluation is introduced into that pipeline, the system creates a potential path from externally influenced strings to executable behaviour.
For systems designed to autonomously traverse information and call tools, that is an execution path worth removing wherever possible.
Safe Rendering in Practice: The SimBI and VaultGraph Approach
We have already been applying a similar design principle in the publicly released SimBI Power BI MCP server, which allows AI agents such as Claude to generate Power BI reports from natural language. Rather than giving the model unrestricted access to construct and manipulate a Power BI project however it chooses, SimBI Power BI MCP exposes a defined set of operations for parsing the semantic model, linting measures, validating report layouts, and emitting the final Power BI Report files.
The model still makes generative decisions about the report, including what visuals to use and how the dashboard should be structured. Those decisions are then passed through more constrained stages that validate the inputs and translate them into the underlying PBIR representation. The same architecture is now influencing how we are developing SimBI more broadly and, in particular, VaultGraph.
VaultGraph is currently under development and is designed as a local-first environment where structured data, documents, notes, analytical outputs, policies, and provenance can be connected through explicit relationships.
As part of that architecture, agents need to generate structured, human-readable artifacts from machine-readable data. A VaultGraph workflow might combine calculated values, source material, policy results, structured properties, and an LLM-generated interpretation into a Markdown artifact.
That requires many of the capabilities normally associated with a template engine: conditional rendering, loops, filters, YAML serialization, tables, variable substitution, and property access.
Those requirements led us to Knap, the Markdown template engine maintained by Obsidian and used across Obsidian tooling such as Web Clipper and Importer.
Knap parses templates into an Abstract Syntax Tree (AST) and interprets that AST using a defined template language. Its documentation explicitly states that it does not use eval() and does not execute arbitrary JavaScript.
A template such as: {{ portfolio.exposure | number_format:2 }} represents a constrained operation understood by the template engine. It is not converted into an arbitrary JavaScript expression and passed to the runtime.

Conditions, loops, property access, and filters operate within the grammar the engine understands. For VaultGraph, this gives us the same type of separation we use in the SimBI Power BI MCP server: the model determines what should be produced, while deterministic components control how those instructions are validated, transformed, and ultimately written into the target artifact.
Defense in Depth: Constraining the Template Rendering Environment
Removing eval() addresses only one part of the problem in securing AI agent data pipelines. A template engine can avoid arbitrary code execution and still expose excessive computational or data-access capabilities.
Knap includes several controls that reduce this exposure.
The engine applies configurable limits to template size, output size, intermediate values, work operations, and nesting. When those limits are exceeded, rendering terminates with a structured LIMIT_EXCEEDED result and empty output rather than continuing indefinitely.
I think of this in terms of the operational thresholds we used in derivatives reporting. New data rows that exceeded predefined ranges could be flagged before anomalous values flowed into downstream reports. The same principle applies here, except the constrained variable is computational work. If a render exceeds an expected threshold, it terminates and produces a structured error that can be logged and reviewed. That telemetry can then inform whether the thresholds remain appropriate as workloads change.
Property resolution is also constrained. Knap’s changelog describes the behavior directly:
“Resolve only own data properties … skipping inherited values and getters.”
This reduces exposure to prototype-chain behavior when templates traverse supplied objects. A template can access the data explicitly provided to it without automatically traversing inherited properties or invoking getters that may execute additional application logic.
Templates can also be parsed and statically validated before rendering. Filter names and parameters are checked against the configured registry, while errors include stable codes and source locations. This creates an opportunity to reject malformed or unsupported templates before they reach the rendering stage.
There are still capabilities that require additional consideration. Knap supports regular expressions in some filters by default. Its maintainers explicitly recommend worker or process isolation when native regex matching is enabled for untrusted input, and provide allowRegex: false for environments requiring stricter controls.
The security characteristics therefore depend on the capabilities actually exposed by the engine configuration. Removing eval() does not make the rendering layer inherently safe.
The same applies to custom filters and variable resolvers. Knap allows applications to register custom and asynchronous filters, and variables absent from the supplied data can be resolved through application-defined functions. Those integrations execute code provided by the host application and can access whatever capabilities that application makes available to them.
The security boundary therefore extends beyond the template syntax itself. It includes the engine configuration, registered filters and resolvers, supplied data, resource limits, and the runtime surrounding the renderer.
Separation of Concerns: Deterministic Computation vs. Probabilistic LLMs

This fits into a broader design principle we have been applying across SimBI and VaultGraph: give each component a narrowly defined responsibility and keep deterministic operations outside the generative reasoning layer wherever practical.
Within VaultGraph’s Obsidian-based environment, DuckDB handles analytical computation.
VaultGraph’s rule evaluation layer handles deterministic policy checks, thresholds, and defined conditions.
The LLM handles interpretation, synthesis, classification, and other tasks where probabilistic reasoning is useful.
Knap, exposed through a dedicated MCP server in our architecture, handles schema-governed Markdown rendering.
The result is a pipeline where the model can determine what needs to be communicated without also being responsible for calculating every value or generating every piece of document syntax from scratch.
This becomes particularly important when we create frozen analytical artifacts. For example, an analyst might capture an interim operating margin calculated from a company’s financial statements at a specific point in time.
An Analysis Freeze in VaultGraph can preserve the underlying data, calculations, provenance, policy evaluations, structured properties, and narrative interpretation associated with that analysis. The resulting Markdown artifact needs to be reproducible and syntactically reliable.
If an LLM generates the entire artifact directly, small variations can appear in YAML, property names, tables, formatting, or document structure between runs. Those differences matter when the artifact is expected to remain stable, machine-readable, and traceable back to the analysis that produced it.
A deterministic renderer provides a defined final stage. The model produces or contributes structured inputs, while the rendering layer maps those inputs into a controlled document schema.
This also makes validation easier. We can inspect the structured values entering the renderer, validate the template independently, and test the resulting artifact against expected schemas before it is written.
Why Execution Boundaries Matter Across All Agentic Architectures
The underlying issue extends beyond Knap, Markdown, or VaultGraph.
Agentic architectures increasingly combine probabilistic reasoning with conventional software execution. Models retrieve data, choose tools, construct parameters, generate queries, create documents, update systems, and initiate workflows.
Every transition from generated or externally sourced information into executable behaviour deserves scrutiny.
Dynamic code evaluation creates an unusually permissive transition because a string can become instructions for the host runtime.
A constrained interpreter reduces that capability surface. A typed function narrows it further, while a deterministic rule with validated parameters provides an even more explicit execution contract.
The objective is to make execution capabilities intentional and proportionate to the task.
If the requirement is to format a Markdown table, the renderer should have the capability to format a Markdown table. It does not need access to arbitrary JavaScript execution or a library that exposes it.
If the requirement is to evaluate a threshold, the system should expose a threshold operation with validated inputs. It does not need a general-purpose expression evaluator with access to the surrounding runtime.
These architectural decisions become increasingly important as agents gain access to proprietary data, internal systems, and tools capable of producing consequential actions.
Conclusion: Designing Out the Execution Path
Security controls frequently focus on detecting malicious behaviour after a capability has already been exposed.
There are situations where detection and sandboxing are necessary. There are also situations where the capability itself can be removed.
For our reporting and knowledge workflows, arbitrary dynamic JavaScript execution falls into the latter category.
Knap gives us the template functionality we require through an AST interpreter, finite rendering limits, explicit filter registries, structured validation, and constrained property access. We then apply additional controls around the environment in which it operates.
The broader architectural principle is simple: understand which components can execute, which can reason, which can transform data, and which can only render within a defined grammar.
In agentic systems, those boundaries determine how far an untrusted input can travel and what it can ultimately cause the system to do. They become the guardrails in which we are securing AI agent data pipelines.
Reducing that execution surface is now part of how we are designing SimBI and VaultGraph as we extend deterministic controls across the analytical and reporting pipeline.
