SAST Rule Authoring for Agent Workflow Code
Custom SAST rules must model agent-specific attack patterns that generic libraries cannot detect.

Authoring SAST rules for agent workflow code starts with modeling how agents use data and authority. Tool call injection, unsafe delegation, credential pass-through, and unbounded action loops each need their own detection logic, which generic rule libraries do not supply. Good coverage needs those custom static rules along with runtime enforcement and audit logs, because inspecting code cannot show every choice made during inference.
Why agent workflow code creates vulnerability patterns that existing SAST rule sets do not cover
Conventional Static Application Security Testing, or SAST, checks logic whose paths are set out in code. Agent scaffolding instead lets an LLM plan actions, delegate tasks, and call tools with delegated authority. The code defines the machinery, but inference selects how that machinery is used. That separates the structure a scanner can inspect from the decisions that cause effects in external systems.
The attack surface shifts with that separation. Untrusted input can arrive through a context window, a tool schema, an inter-agent message, or a memory read as well as a familiar form field. Delegated credentials then determine how much authority an influenced action can exercise. The source code contains the interfaces to those objects, but not every payload or decision they will carry.
OWASP’s Top 10 for Agentic Applications 2026 lists agent goal hijacking, tool misuse and exploitation, and memory and context poisoning as risks specific to agents. These risks come from autonomous decision-making, delegation, and tool integration, beyond what text generation alone can cause. Their common feature is that data can influence not just an answer but an action. That is the structural gap conventional rules leave exposed.
The four agent-specific vulnerability patterns a rule author must model before writing any detection logic
Tool call injection begins with untrusted prompt content, an external tool response, or an MCP server payload. Its destination is a tool invocation argument, reached without validation or escaping appropriate to that use. An attacker-controlled string can therefore redirect an agent’s action. The signature is not merely suspicious text, but the missing check between that text and execution.
Unsafe delegation chains begin with a task or credential scope held by an orchestrator. Their destination is a downstream agent invocation that inherits authority without scope reduction or re-authorization. The missing control is a decision about what the receiving agent actually needs. A handoff can therefore be unsafe even when the task itself is legitimate.
Credential pass-through begins with a secret or token retrieved from a credential store. Its destination is a child agent or tool call, reached verbatim through an argument or environment variable without a scoped proxy. This differs from unsafe delegation because the credential itself is exposed to the sub-agent’s full action space. The missing boundary concerns both possession of the secret and the breadth of permission.
Unbounded action loops begin with repeated tool or sub-agent invocations. They can lead to more external action without a bounded exit, convergence check, or human gate. The missing control is a limit on repetition, enabling resource exhaustion or runaway side effects. This signature concerns control flow, while the other patterns concern the journey of a particular value.
Why generic SAST rule libraries miss these patterns even when the tools are capable of detecting them
A 2024 EASE study found that missing rules accounted for the large majority of false negatives in Semgrep, exceeding the contribution of analysis engine limitations. That makes rule authorship the primary lever for improving coverage. Replacing the engine does not automatically supply the missing vocabulary.
Generic libraries know the usual sources and sinks, such as HTTP request parameters, database queries, and shell arguments. They do not supply the corresponding agent-specific inventory of LLM outputs, tool responses, memory reads, MCP payloads, and downstream agent calls. Research on agent SAST identifies prompt injection, self-modification, and tool poisoning as gaps in generic coverage. Dynamic delegation adds an authorization problem that depends on application semantics and needs more than a matching expression to detect.
TAINTAWI, a taint analysis system for GitHub Actions workflows, illustrates the cost of incomplete specifications. It achieved high precision on workflow injection, but its remaining false negatives arose from incomplete taint specifications and embedded-script analysis. Snyk’s ToxicSkills study in February 2026 found that a sizable share of the agent skills it audited had a critical issue. Skills and tools are therefore scan targets in their own right. Custom coverage must describe both the workflow and the supply chain that feeds it.
How taint analysis breaks when data crosses an LLM inference boundary or an agent handoff
Taint analysis tracks untrusted data from a source, through transformations called propagators, to a dangerous consumption point called a sink. In ordinary code, those links can show up as assignments, arguments, and return values. An LLM inference call changes the problem because it transforms and re-emits data behind an API boundary. Its output carries influence from earlier inputs, yet appears in source code only as a returned value.
An agent handoff introduces a similar discontinuity. Tracking a call between helper functions is not the same as modeling a task passed to another agent whose behavior is selected during inference. Context assembly, tool responses, and memory retrieval can also break the chain that the code shows. Rules that need a source-level path through every one of those steps can miss the dangerous link altogether.
Argus research describes limits in existing LLM-SAST integrations, including single-pass inference, hallucination-driven false positives, and failure to address disrupted data flows. Adding an LLM to an analyzer therefore does not by itself repair the boundary. The rule needs an explicit model of what emerges from it and where that output goes.
That model can treat the returned LLM output as a fresh untrusted source. A rule can then detect that output reaching a shell sink or an unvalidated tool argument. This captures the dangerous structure without pretending to know the model’s internal reasoning.
Defining agent-aware sources, sinks, and propagators before touching rule syntax
Sources are where untrusted data enters the model. They include fields in an LLM API response, such as content, tool_calls, and returned function arguments, as well as external tool payloads and sub-agent return values. MCP tool descriptions, resource contents, and server-provided arguments also belong here. Memory reads from vector stores, key-value stores, or conversation history are sources, as is prompt content assembled from user input and retrieved context.
Sinks define where that data can cause a dangerous effect. They include agent-selected shell or subprocess calls, filesystem operations directed by agent output, and downstream agent invocation arguments. Network calls with model-derived URLs, headers, or bodies are sinks too. Tool registration and schema modification calls matter for poisoning patterns, while credential lookups matter when their results are forwarded to another call. A lookup can consequently be a consumption point in one rule and the origin of a secret in another.
Propagators define how influence survives the plumbing. They include context assembly functions, tool argument construction helpers, delegation wrappers, and memory write/read pairs that reintroduce stored content. These definitions connect the framework’s abstractions to the behavior the rule must track. Because inference remains a black box, its output must be treated as tainted regardless of the prompt. A reassuring prompt is not a sanitizer.
Translating each vulnerability pattern into detection logic for Semgrep and CodeQL
Semgrep provides a direct route from this inventory to a rule. A minimal rule has five fields, which are id, pattern specification, message, languages, and severity. A taint rule sets mode to taint and explicitly defines sources, sinks, sanitizers, and propagators. For tool call injection, the source can be an LLM response field and the sink a tool argument construction call, with valid sanitization interrupting the flow.
Credential pass-through uses the same structure with different meanings. Secret-store retrieval is the source, a sub-agent argument is the sink, and a scoped-proxy wrapper provides the recognized safe boundary. If that path crosses helper functions, the analysis must follow calls between functions, which is known as interprocedural analysis. Semgrep’s Pro Engine supports this capability. Rules that cannot follow those calls will miss paths that depend on them.
CodeQL expresses the vocabulary through classes extending taint-tracking library nodes and queries written in its SQL-like QL language. Its interprocedural call-graph analysis fits delegation paths when their call sites are statically analyzable. It cannot show the full call graph from an LLM-selected function name. CodeQL’s April 2025 workflow analysis for missing permissions, dangerous inputs, and script injection is a reference to start from, but still needs work to cover agents.
Unbounded action loops need a different rule structure because repetition is not taint. A Semgrep structural pattern can look for a loop over a tool call without a bounded termination expression. More broadly, rule authors should plan for families of rules. A 2026 research paper called for separate MCP rules for cross-server tool shadowing, tool description poisoning, argument injection, and baseline drift detection.
The mapping follows the mechanism. Tool call injection fits Semgrep taint mode or a CodeQL taint-tracking query from LLM or tool response to tool argument or shell call. Unsafe delegation fits a CodeQL call-graph query or Semgrep interprocedural taint tracking from ambient authority to a sub-agent with unmodified scope. Credential pass-through fits taint tracking from secret retrieval to a sub-agent argument, recognizing a scoped-proxy wrapper. Unbounded action loops fit structural matching for missing termination bounds, without taint mode.
Tuning for signal without suppressing novel patterns
A suppression rate above 20% warrants investigation as a possible sign of security-gate bypass. Findings do little to protect code when developers keep dismissing them. Yet narrowing agent rules until every warning looks familiar defeats their purpose. Novel injection and delegation shapes are precisely the cases these rules need room to catch.
Stage findings as informational warnings, then promote them to blocking only after validation against real codebases for two or more sprint cycles. Keep structural loop rules separate from the taint rules for injection and pass-through. Structural rules have far lower false-positive rates, so they can start blocking sooner.
Rules that spell out allow-listed sanitizer definitions can drop findings for tool responses that are handled correctly without weakening the sink. Source and sink patterns can also be scoped to the framework APIs actually in use, including LangChain, LlamaIndex, the OpenAI Agents SDK, and AutoGen. That is more selective than matching generic string operations while retaining the relevant attack surface. Agent skills and MCP tool descriptions need separate care because their source is natural-language text, where existing sanitizer patterns do not apply.
LLM-assisted post-filtering is another way to cut false positives while keeping recall. However, it introduces hallucination risk and non-determinism that conflict with audit requirements. A filter that changes its mind is an awkward security gatekeeper. This approach can complement tuned rules, but it cannot replace the explicit definitions that make findings explainable.
Where runtime enforcement picks up what static rules structurally cannot catch
Static analysis can flag an LLM response passed into tool invocation logic, but it cannot observe which tool is selected or what credential scope that tool inherits during execution. Emergent delegation chains and runtime credential access therefore remain outside what scaffolding inspection can establish.
Runtime controls supply the missing visibility by discovering agents, monitoring approved AI traffic, and observing actual tool calls, delegation, and credential access. That visibility supports detection of anomalous behavior and enforcement of policy during execution. Memory and context poisoning and agent goal hijacking have behavioral dimensions that source inspection alone cannot settle.
Audit logs complete the picture by recording every agent action and providing context for triage. Without that record, an alert offers too little basis for distinguishing a real violation from a false positive, and developers again risk dismissing findings at another layer. Static analysis, runtime enforcement, and audit are complementary parts of the answer because each covers a boundary the others cannot cross.


