SEC.INJ.RAW_INTERPOLATION — A runtime value was built into the statement instead of bound to it
- Category: security
- Severity: high
- Level: 0
- Downtime class: none — the finding is about a call site in PHP, not about a statement
- Stability: stable
- Suites:
analyse - Applies to: every engine — it reads PHP source, not a server
What it reports
A raw-SQL call site whose text was assembled from something that is not known until the program
runs: an interpolated string, a concatenation, a sprintf, an implode.
DB::table('orders')->whereRaw("status = '{$status}'")->get(); // reported
DB::select("SELECT * FROM orders WHERE status = '{$status}'"); // reported
How to answer it
Pass the value as a binding. Every method this rule looks at takes one.
DB::table('orders')->whereRaw('status = ?', [$status])->get(); // silent
DB::select('SELECT * FROM orders WHERE status = ?', [$status]); // silent
That is the whole fix, and it is not a workaround for the rule: a bound value never becomes part of the statement, so it cannot change what the statement does — whatever it contains.
What it looks at
Both halves of Laravel's raw surface:
- the statement sinks on the
DBfacade and on a connection —select,selectOne,scalar,cursor,statement,unprepared,insert,update,delete,affectingStatement,selectResultSets,selectFromWriteConnection; - the fragment methods on the query builder —
selectRaw,fromRaw,whereRaw,orWhereRaw,havingRaw,orHavingRaw,orderByRaw,groupByRaw,rawValue.
Eloquent counts. Order::query()->whereRaw(…) and $order->lines()->whereRaw(…) are the same call
site as the query builder's, and the rule sees them.
What stays silent, on purpose
Parameterized calls. whereRaw('status = ?', [$status]) is idiomatic Laravel and is never
reported. A rule that flagged it would be switched off within a week — taking the real findings with
it.
A constant statement. Text assembled from class or global constants is still entirely the
author's, so 'ANALYZE '.self::TABLE is not a finding. Neither is a lookup into a constant map,
which is the shape a team uses as an allowlist.
whereIntegerInRaw() and its family. They cast every value to an integer before it reaches the
statement, and that cast is the mitigation. Reporting them would flag the safe form of exactly the
pattern this rule looks for.
An argument the analyzer could not read. A value assembled in another method, a property, a
method call — the rule sees one call site and cannot follow it. That is undetermined, not a
finding. Reporting doubt at high severity is how a security rule teaches a team to ignore it.
A whereRaw() on somebody else's class. The receiver's type decides, not the method name — a
codebase is full of repositories and collections with methods that share a name.
What it does not claim
- It is a syntactic pattern, not taint analysis. It reports that a runtime value reached the statement's text. It makes no claim that the value is attacker-controlled or reachable from a request — SQLens does not do taint analysis at any point.
- It never quotes your query. A finding travels into CI logs, SARIF files and agent artifacts, and a reproduced fragment would be a second copy of whatever the statement touched. The message names the shape — interpolation, concatenation — and the method, never the SQL.
- It sees exactly one call site, with no value flow across function boundaries.
Why High, when its neighbor is Low
SEC.INJ.RAW_SQL_WITHOUT_REASON
reports a missing sentence: nobody wrote down why raw SQL was chosen. That is a policy gap.
This one reports that a value reached the statement rather than the parameters — the property that decides whether an injection is possible at all. Whether it is exploitable today depends on what flows into that value, which this rule cannot see and does not pretend to; what it can say is that the safe form was available and not used.
Turning it on
includes:
- vendor/pushery/sqlens-for-laravel/extension.neon
See the analyse suite for how it sits beside
Larastan and phpstan-dba.
Sources
- SQL Injection — OWASP, the attack this rule is about
- SQL Injection Prevention Cheat Sheet — OWASP. Its first defense is the one this rule asks for: a parameterized query, not an escaped one
- Raw expressions — Laravel 13, the
*Raw()family this rule watches - Parameter binding — Laravel 13, the second argument that turns a reported call into a silent one