Join our Newsletter — 33% off our NHI Course

What breaks when Kotlin applications build SQL with raw user input?

The code-data boundary breaks, which means the database can execute attacker-controlled logic instead of the intended lookup or update. In practice, that can expand a single-record request into table-wide disclosure, mass updates, or destructive operations. The failure is not just vulnerability to attack, but loss of control over what the application is authorised to do.

How raw SQL input breaks the code-data boundary

Raw user input becomes dangerous in Kotlin as soon as it is concatenated into a query string, because the database can no longer distinguish data from executable SQL. A lookup intended to read one row can be rewritten into a broader predicate, a second statement, or a destructive command if the input is treated as part of the query structure rather than a parameter.

That boundary failure is the core issue, not Kotlin itself. The language will happily pass the string along; the break occurs where application text is interpreted by the database engine as code. This is why the same pattern can affect JDBC, ORM escape hatches, ad hoc reporting code, and any helper that builds SQL fragments dynamically.

In practical terms, the problem changes the meaning of the request. Instead of “fetch the customer with this ID,” the system may execute attacker-shaped logic that expands scope, changes filters, or alters rows. The impact is usually larger than the original feature, because the database executes with the privileges granted to the application account, not the end user’s intent.

What attackers gain once the query shape is user-controlled

Once an attacker can influence SQL structure, they can often move from a single-object request to broader disclosure or modification. That can include broadening a WHERE clause, UNION-based retrieval, stacked statements where supported, or manipulating ORDER BY, LIMIT, or boolean logic to change how the application behaves.

The security consequence is privilege amplification through interpretation. The application may only expose one endpoint, but the database session can expose many tables, many rows, or write paths that were never meant to be reachable from that feature. The risk is especially high when the database role has more permissions than the specific use case requires.

Prepared statements and bound parameters matter here because they preserve the code-data boundary. The query structure is fixed, while the variable values are transmitted as values, not executable syntax. String escaping alone is not a reliable substitute, because it depends on dialect, context, and developer discipline.

Where Kotlin projects most often go wrong

Kotlin code usually becomes vulnerable through convenience patterns, not because the language lacks safeguards. Common failure points include string interpolation inside SQL literals, dynamic query builders that splice raw fragments, repository methods that pass unchecked filter text, and “temporary” admin or analytics paths that later reach production.

Query composition also becomes risky when teams build optional filters, sort clauses, or table names from user input. Those fields are often treated as harmless because they do not look like values, but they still control query structure. If they are not constrained to a strict allowlist, they can become an injection surface just as quickly as a login form.

The safest design is to keep user input in value positions and to model any structural choice as a closed set of known-safe options. Where true dynamic SQL is unavoidable, the structural pieces must come from application-controlled constants, with identifiers and clauses validated separately from ordinary parameters.

Risk and Threat Considerations

raw sql construction turns a local input-handling bug into a data-access and privilege problem. The threat is not limited to read theft: once query structure is attacker-controlled, the same path can support mass updates, destructive statements, lateral discovery, or abuse of any over-privileged database account.

Failure mechanism: The application embeds untrusted text into executable SQL, so the database parses attacker input as part of the command tree instead of as a bound value.

Impact: An attacker can expand the blast radius of a single request, bypass intended application logic, and trigger disclosure or modification far beyond the original record scope.

Standards & Framework Alignment

This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.

OWASP ASVS, NIST SP 800-53 Rev 5 and CIS Controls v8 set the technical controls, while ISO/IEC 27001:2022 defines the regulatory obligations.

Framework Control / Reference Relevance
OWASP ASVS V4 — API and Web Service SQL injection prevention is part of web service input handling and data access safety.
Recommendation — Use bound parameters and strict input handling for all database-facing request data.
NIST SP 800-53 Rev 5 SC-39 — Process Isolation Separating untrusted input from executable query logic preserves control boundaries.
Recommendation — Isolate data handling from executable logic and reject user-controlled SQL structure.
CIS Controls v8 CIS-16 — Application Software Security Secure coding controls directly address injection flaws in application data access paths.
Recommendation — Implement secure coding practices that eliminate SQL concatenation with untrusted input.
ISO/IEC 27001:2022 A.8.28 — Secure coding Secure coding guidance applies because raw SQL assembly is a code-level injection weakness.
Recommendation — Require secure coding patterns that keep SQL structure separate from user-supplied data.

Practitioner Guidance

What to verify: Check that every variable value reaches SQL through parameter binding, not concatenation or interpolation. If a code path must choose tables, columns, or sort directions dynamically, verify that the choice is constrained to a closed allowlist rather than free-form text.

Common mistake: Teams often secure the primary query but leave one “small” helper unsafely building WHERE, ORDER BY, or IN-clause text. That is enough to reintroduce the same control failure, especially in reporting, search, and admin features.

Practitioner takeaway: Treat query structure as trusted code and user input as data only; once the application lets outside text shape SQL syntax, the database can no longer be trusted to enforce the intended business boundary.