On this page
- Why Rust deserves real security scanning
- Why most Rust SAST is pattern-matching theatre
- Dataflow vs pattern matching, in one example
- What a dataflow-aware Rust ruleset catches
- Inside the agent loop, not just the dashboard
- Frequently asked questions
- Is Rust memory safety enough to prevent vulnerabilities?
- Can SQL injection happen in Rust with sqlx or diesel?
- Why do Rust SAST tools produce so many false positives?
- What is dataflow (taint) analysis?
- Does dataflow scanning work inside AI coding agents?

Production Rust runs the internet now, and most static analysis tools still treat it like a checkbox. They added *.rs to the file glob, shipped a list of dangerous function names, and called it Rust support. The actual job, following untrusted input through async functions, generic database connections and macro-derived helpers until it reaches a SQL query or an HTML response, was left undone. That is exactly where the vulnerabilities live. This is why pattern matching fails on Rust, and what dataflow analysis catches instead.
Why Rust deserves real security scanning
Rust stopped being a systems-only curiosity years ago. Cloudflare serves a large share of the web through Pingora. Discord rewrote performance-critical services in it. AWS ships Firecracker and Bottlerocket in Rust, Stripe runs it on payment paths, and the list (Linkerd, Polkadot, Solana, Tauri) grows every month. This is code that handles untrusted input, money and credentials at scale.
The static analysis market mostly responded by adding Rust to a glob and moving on. The result is tooling that either floods teams with false positives on lines that are obviously safe, or quietly misses the lines that are not. Either way the report stops being trusted, the scanner gets disabled in CI, and the language that was supposed to be safer ends up with less security coverage than the JavaScript service next to it. Memory safety is not application security: the borrow checker will not stop you from concatenating user input into a SQL string.
Why most Rust SAST is pattern-matching theatre
Static analysis on Rust is genuinely hard, for reasons that do not show up in vendor marketing. A scanner that only matches text patterns hits five walls at once.
Stack these together and Rust becomes a language where pattern matching is loud where it should be quiet and silent where it should be loud. The fix is not more patterns. It is dataflow.
Dataflow vs pattern matching, in one example
Here is the kind of bug that separates the two approaches. It uses sqlx, the modern async default, and its QueryBuilder.
use sqlx::{PgPool, QueryBuilder, Postgres};
pub async fn search(pool: &PgPool, input: SearchInput) -> Result<Vec<Item>, sqlx::Error> {
let mut q: QueryBuilder<Postgres> =
QueryBuilder::new("SELECT * FROM items WHERE 1 = 1");
if let Some(name) = input.name {
// .push_bind() is safe (parameterized). .push() is concatenation.
// This is direct SQL injection.
q.push(" AND name LIKE '%").push(&name).push("%'");
}
q.build_query_as::<Item>().fetch_all(pool).await
}
A pattern scanner looking for format! next to sqlx::query finds nothing here. There is no format!, no string concatenation operator, no obvious sink. The taint flows through QueryBuilder::push, a function that is perfectly safe with a constant string and dangerous with user input. The scanner cannot tell the difference because it does not follow the value, so it ships a clean report on a file with a real SQL injection.
A dataflow engine traces the taint from input.name (deserialized from the request by serde and axum) through the .push(&name) call, recognizes QueryBuilder::push as a SQL-text sink, and reports it with the full path. The fix is one method:
// .push_bind() keeps the value out of the SQL text.
q.push(" AND name LIKE ").push_bind(format!("%{}%", name));
Same shape, completely different security property. Only an analysis that follows the data can tell them apart.
What a dataflow-aware Rust ruleset catches
The CybeDefend Rust ruleset is built on this model, with library-specific sink models so the taint is not lost when it crosses an API boundary. Every finding ships with the full path: source location, every intermediate hop, and the sink.
SQL drivers tracked: sqlx, diesel, rusqlite, tokio-postgres
web frameworks for XSS: actix-web, axum, rocket
async HTTP clients for SSRF: reqwest, ureq, hyper, surf
- SQL injection across the four major drivers, following user input through async functions, generic connection types and macro-derived query helpers, not just the obvious
format!cases. - Cross-site scripting (XSS) on the three mainstream frameworks, including HTML produced through the
askamaandteratemplate engines, normalized into a single response-sink model. - CORS misconfiguration, such as a wildcard
Access-Control-Allow-Origincombined with credentialed requests, or an origin reflected from the request without an allowlist, caught at construction time. - Server-side request forgery (SSRF) in async HTTP clients, with taint followed through every URL-builder helper and stopped when a host allowlist or IP filter is present on the path.
The point of tracking sanitizers as carefully as sinks is that a finding only fires when the dangerous path is actually reachable with no protection in between. That is what keeps the report trustworthy enough to leave on in CI.
Inside the agent loop, not just the dashboard
The Rust rules are not only a CI report. They are pushed into your AI coding agent through VibeDefend, the layer that runs inside Claude Code, Cursor, OpenAI Codex, Windsurf and VS Code Copilot. When the agent generates Rust, the ruleset is already loaded as context, so a tainted path into one of the sinks above gets rewritten before the line is ever suggested to you.
This is the difference between catching a bug at PR review, after the agent has shipped 2,000 lines nobody read end to end, and never writing it in the first place. We do not stop the diff at review. We stop it at the prompt.
Frequently asked questions
Is Rust memory safety enough to prevent vulnerabilities?
No. Rust's borrow checker prevents memory-corruption bugs (use-after-free, buffer overflows, data races), which is a large and valuable class. It does nothing for application-level vulnerabilities. SQL injection, XSS, CORS misconfiguration, SSRF, broken access control and insecure deserialization all happen in safe Rust, because they are about how untrusted data flows through your logic, not about memory. A memory-safe language still needs application security scanning.
Can SQL injection happen in Rust with sqlx or diesel?
Yes. Both libraries offer safe, parameterized APIs (sqlx compile-time-checked macros and .bind(), Diesel's typed DSL), but both also expose escape hatches: sqlx::query(&str) and QueryBuilder::push take runtime strings, and diesel::sql_query runs raw SQL. The moment user input is concatenated into one of those, you have injection, regardless of how modern the driver is. The same applies to rusqlite and tokio-postgres.
Why do Rust SAST tools produce so many false positives?
Because most of them pattern-match on function names and string shapes instead of following the data. Rust's lifetimes, async chains, generics, derived macros and trait objects hide the real data path, so a pattern scanner flags every call that looks like a sink (drowning the team in noise) or skips the rule entirely (missing real bugs). Dataflow analysis cuts the noise by only reporting paths that are actually reachable from an untrusted source with no sanitizer.
What is dataflow (taint) analysis?
It is an analysis that models a program as a graph of values and asks, for each dangerous operation, whether untrusted input can reach it without being sanitized. Input points are "sources," dangerous operations are "sinks," and a clean path between them with no protection is a real, exploitable finding. Unlike pattern matching, it understands that the same function can be safe or unsafe depending on what data reaches it.
Does dataflow scanning work inside AI coding agents?
Yes. Through VibeDefend, the Rust ruleset is loaded into the agent's context at prompt time, so Claude Code, Cursor, Codex and the others apply it while they write rather than after. If a generated change would introduce a tainted path into a SQL, HTML or HTTP sink, the agent produces the safe, parameterized or validated version on the first try, before the diff exists. For the broader picture of securing each agent, see our guides to Claude Code, Cursor, GitHub Copilot and OpenAI Codex security.


