Feature
SQL Injection Detection for GitHub Pull Requests
What Is SQL Injection?
SQL injection occurs when an application includes untrusted input in a SQL statement without proper parameterisation or strict validation. The database executes the combined string as code, not merely as a literal value. A single unescaped quote, comment sequence, or boolean clause can change which rows are returned, which tables are updated, or whether multiple statements run in one round trip.
The attack does not require access to your source repository. It targets live endpoints: search boxes, login forms, API filters, report parameters, OData-style query builders, and administrative export functions. OWASP classifies injection flaws among the most common and damaging application risks because one mistake in query construction can expose an entire database schema, bypass authentication, or write arbitrary rows.
Classic in-band SQL injection returns unexpected data in the HTTP response — extra columns, another user's record, or a verbose database error that confirms syntax. Blind variants infer success from timing delays, boolean differences in page content, or out-of-band DNS/HTTP callbacks. Both classes start in application code where a query string is assembled from runtime values.
Defences are well understood: parameterised queries, prepared statements, ORM APIs that bind values separately from SQL text, allowlists for dynamic identifiers, and least-privilege database accounts. The difficulty is consistency. A secure pattern in one service does not protect another module where a developer concatenates a filter value into a string for convenience during a deadline.
Second-order SQL injection adds another wrinkle. Input is stored safely on insert, then read back later and embedded unsafely in a different query — for example a username saved with parameter binding but later concatenated into a reporting query. The vulnerability appears in the read path, not the write path reviewers inspected last quarter.
Stacked queries are a separate class: some database drivers allow multiple statements in one call when input injects a semicolon and a second command. Even read-only application accounts can be dangerous if the injected statement runs with the same connection privileges. Parameter binding closes both string-breakout and statement stacking for drivers that disable multi-statement execution by default.
Why SQL Injection Still Happens
Teams know the theory. Production incidents continue because query construction is scattered across handlers, repositories, background jobs, analytics scripts, and one-off data fixes — and because several benign-looking shortcuts reintroduce the same flaw.
Dynamic queries
Dynamic SQL is legitimate for optional filters, sort columns, pagination cursors, and reporting. Risk appears when both structure and values come from the request. Building WHERE status = '{userInput}' with string interpolation treats attacker-controlled syntax as part of the query. Even dynamic identifiers (table or column names) need allowlists, not direct passthrough from parameters. Middleware that escapes HTML does not escape SQL metacharacters.
User input
Any field that reaches a query is untrusted: query strings, JSON bodies, GraphQL arguments, gRPC fields, headers copied into audit tables, and internal service calls that forward client data. Microservices amplify this. A gateway may sanitise input, but a downstream service might rebuild SQL from the forwarded payload without the same checks. Internal APIs are not automatically safe — compromised services or SSRF chains can supply malicious strings to trusted backends.
ORM misuse
ORMs do not eliminate SQL injection. They eliminate it when you use bound parameters. Calling raw(), execute() with template strings, $executeRawUnsafe, or string-building helpers with interpolated variables puts you back in the same position as concatenating SQL manually. ORM documentation often shows escape hatches for complex reports; those escape hatches become production code paths under delivery pressure.
Developer mistakes
Copy-pasted snippets, generated boilerplate, and quick fixes during incidents often use readable string formatting instead of parameter binding. Code review catches obvious cases when reviewers know the data path. Subtle flows — input assigned across three functions before reaching query() — are easy to miss in a busy diff. Junior developers may treat ORM documentation examples as production-ready without noticing the unsafe raw-query variant.
Stored procedures and database views do not absolve application code. If the application still builds dynamic SQL to call those objects, the injection surface remains. Defence in depth — WAF rules, database permissions, network segmentation — limits blast radius but does not fix the underlying string concatenation in the pull request your team is merging today.
Legacy migrations and admin tooling are frequent sources. A script that worked when only operators ran it gets exposed through a new self-service UI. The SQL pattern did not change; the trust boundary did.
Why Code Reviews Miss SQL Injection
Manual review remains valuable for architecture, authorisation logic, and schema design. It is a weak primary control for SQL injection for predictable reasons.
Reviewers scan diffs quickly. A changed line that assigns req.query.sort to a variable looks harmless two files away from the repository method that embeds that variable in SQL. Without tracing data flow, the dangerous sink never appears in the same comment thread as the source. GitHub's diff view does not draw taint paths.
Large pull requests hide small query changes. A refactor touching forty files buries a new unsafe execute call on one line. Teams optimise review latency; not every line receives the same depth of security scrutiny. Rubber-stamping familiar filenames is common when CI is green and the ticket is urgent.
Inconsistent standards across languages multiply the problem. A reviewer strong in TypeScript may not recognise risky patterns in a Python admin script added in the same monorepo pull request. Security expertise is unevenly distributed across squads and time zones.
Review is episodic. A pattern rejected in discussion can reappear months later when a different author solves a similar ticket. Automated checks on every pull request apply the same rule every time the diff changes — not only when a security-minded reviewer is online.
Checklist-driven review helps culture but not coverage. Items like "verify parameterised queries" are easy to tick without verifying every new data path in a 600-line diff. Static analysis encodes the checklist into executable rules.
A pull request security scanner fills the gap by operating on diff text and taint paths, independent of reviewer availability and language familiarity.
Examples of SQL Injection
The examples below show the same mistake in four common stacks: interpolating untrusted data into SQL text instead of binding it as a parameter. Each pair is labelled vulnerable versus secure.
JavaScript (node-postgres)
❌ Vulnerable
const email = req.body.email; const sql = "SELECT * FROM users WHERE email = '" + email + "'"; await pool.query(sql);
✅ Secure
const email = req.body.email;
await pool.query("SELECT * FROM users WHERE email = $1", [email]);The vulnerable version lets an attacker close the string and append OR 1=1--. Parameter binding sends email as data; the planner never treats user content as SQL syntax. This pattern is what a github sql injection scanner should flag on the changed lines.
TypeScript (Prisma raw query)
❌ Vulnerable
const status = req.query.status as string;
await prisma.$executeRawUnsafe(
`UPDATE orders SET state = '${status}' WHERE id = ${orderId}`
);✅ Secure
const status = req.query.status as string;
await prisma.$executeRaw`
UPDATE orders SET state = ${status} WHERE id = ${orderId}
`;Prisma's tagged template with $executeRaw parameterises values.$executeRawUnsafe with string interpolation is equivalent to manual concatenation and should be avoided for any user-influenced value.
Python (sqlite3)
❌ Vulnerable
username = request.args.get("username")
cursor.execute(f"SELECT * FROM accounts WHERE name = '{username}'")✅ Secure
username = request.args.get("username")
cursor.execute("SELECT * FROM accounts WHERE name = ?", (username,))f-strings and .format() in SQL are a frequent source of injection in Python codebases. The DB-API placeholder style keeps values separate from the statement template. Django's raw() with params is acceptable; f-string SQL is not.
PHP (PDO)
❌ Vulnerable
$id = $_GET['id'];
$stmt = $pdo->query("SELECT * FROM invoices WHERE id = " . $id);✅ Secure
$id = $_GET['id'];
$stmt = $pdo->prepare("SELECT * FROM invoices WHERE id = :id");
$stmt->execute(['id' => $id]);Numeric-looking input is still untrusted. Attackers supply expressions such as 1 UNION SELECT … when the value is embedded without binding. PDO query() with concatenation bypasses the driver's parameter binding entirely.
The fix is always the same principle: separate query structure from data. Language syntax differs; the failure mode does not.
Detect SQL Injection Before Merge
Finding SQL injection after deployment means incident response, credential rotation, breach notification, and rebuilding trust with customers. Finding it on the pull request means the author fixes the query in the same branch before it touches production data paths. That is the practical meaning of detect sql injection before merge.
Pull request scanning analyses the diff: lines added or modified in the PR. That scope matches what reviewers actually need to approve. A legacy vulnerable query untouched for five years does not block today's unrelated feature merge, but a new concatenated filter in the changed handler surfaces immediately.
Diff-scoped static application security testing reduces noise. Whole-repository scans are necessary for inventory and audit, yet they flood teams with historical debt. PR-level application security testing answers whether this change introduces a new injection path — the question reviewers most often fail to answer systematically.
Can GitHub detect SQL injection?
GitHub offers CodeQL through Advanced Security when enabled on your organisation. CodeQL can surface many injection patterns in supported languages during scheduled or pull request analysis. Coverage depends on language support, query packs, workflow configuration, and whether the vulnerable path sits in analysed code. Secret scanning addresses credentials, not query construction. GitHub does not automatically review every pull request for tainted SQL unless you configure analysis workflows.
Which tools detect SQL injection in pull requests?
Options fall into three practical buckets. First, native GitHub code scanning with CodeQL — deep when configured, but setup and query maintenance sit with your team. Second, commercial SAST platforms integrated into CI — broad language support, often higher latency and licence cost. Third, GitHub Apps that scan PR diffs on each webhook event and post check runs directly on the pull request — optimised for pull request security velocity rather than quarterly full-repo reports.
When evaluating a github security scanner, ask whether findings attach to the pull request under review, whether only changed lines are reported, and whether taint flow is shown from source to sink. A tool that emails a PDF after merge does not support shift left security; a tool that comments on line 42 of the diff does.
GitHub-native results matter for adoption. When findings appear as check runs and inline comments on the pull request, developers fix issues in the workflow they already use. External dashboards alone tend to lag behind merge velocity on active repositories.
How Launchioo Detects SQL Injection
Launchioo is a GitHub App that runs deterministic security analysis on every pull request. It analyses changed lines in the PR diff and posts results as GitHub check runs and review comments. It does not store your full repository; scan metadata and matched snippets are persisted for dashboard history.
SQL injection detection sits inside a broader injection and taint analysis layer — not a standalone regex hunting the word SELECT. Launchioo tracks data from common sources (request body, query parameters, headers, cookies) through assignments and into dangerous sinks. When user-controlled data can reach a SQL-like execution helper, the engine reports a taint-flow finding with file, line, and attack path context.
The taint engine watches flows into query, execute, and raw calls that begin SQL statements with SELECT, INSERT, UPDATE, DELETE, or WITH. That targets the mistake shown in the examples above: building statement text from runtime values instead of binding them.
Alongside SQL injection paths, the same PR scan covers related risks that often appear in the same handlers:
- SSRF — user input reaching dynamic
fetch, HTTP clients, or WebSocket constructors - CSRF — state-changing requests without appropriate token or cookie protections on added routes
- Hardcoded secrets — API keys, tokens, and private key material in the diff
- Exploit chains — multi-step paths from source to sink across changed files in the same PR
- Supply-chain risks — unsafe dependency and CI patterns introduced in the change
Findings include severity, rule name, snippet, and fix guidance. On the Free plan, checks are advisory. On Pro, critical injection and taint findings can fail the check when branch protection requires it — blocking merge until the query is parameterised or the flow is refactored.
Launchioo is not a substitute for database hardening, least-privilege roles, or runtime WAF rules. It addresses the application layer where query strings are assembled — the last editable gate before vulnerable code reaches your default branch.
If you already run github code scanning with CodeQL, Launchioo complements that workflow with PR-scoped deterministic rules and taint analysis tuned for fast feedback on every push. Teams use both: scheduled depth from CodeQL, day-to-day secure code review on changed lines from a dedicated sql injection github pull request scanner.
For sql injection vulnerability detection specifically, the signal you need is a taint path from an HTTP or request-like source into a SQL execution sink in code your author just added. Launchioo surfaces that path in the check run summary so the fix is parameter binding, not debate in a review thread three days after merge.
What happens when Launchioo finds a SQL injection path
On each pull request event, Launchioo fetches the diff, runs deterministic rules and taint analysis on changed lines, and opens or updates a GitHub check run. Critical taint-flow findings into SQL sinks appear with file, line number, rule identifier, and a short attack-path description. Authors refactor to bound parameters or adjust the data path; the check re-runs on the next push.
On Pro, when branch protection requires Launchioo checks to pass, a critical injection finding blocks merge until resolved. On Free, the same finding is visible but the check stays advisory — useful for teams building habits before enabling enforcement. Either way, the finding is tied to the pull request that introduced it, not a quarterly PDF report.
Why Shift Left Security Matters
Shift left means moving security verification earlier in delivery — at the pull request, not the production incident. For SQL injection, the economics are straightforward: fixing a parameterised query during development costs minutes; extracting unauthorised data from a live database costs weeks and sometimes regulatory fines.
Early detection preserves context. The author who wrote the unsafe execute call is still in the branch, still remembers the ticket, and can run tests against the fix. Security teams reviewing logs after exploitation rarely have that continuity. Forensics reconstruct intent; PR scanning catches intent before it ships.
PR scanning also trains habits. When concatenated SQL fails a check twice, teams reach for parameter binding by default on the third task. Consistent automated feedback changes implementation patterns more reliably than annual training slides or policy documents nobody opens during crunch weeks.
Shift left does not mean shifting blame to developers. It means giving developers the same fast signal that CI already provides for type errors and failing unit tests — applied to repository security and secure software development practices your organisation already documents in its secure coding standard.
For regulated environments, evidence that every pull request received automated github security scanning supports audit narratives. For high-velocity teams, it prevents the slow leak of injection flaws into services that handle customer PII. Both benefit from the same control: stop unsafe query construction before it merges.
Sql injection prevention at merge time is cheaper than detection at runtime. Runtime tools help; they cannot un-merge code. The pull request is the last gate where fixing the query is a normal development task instead of an emergency change window.
Frequently Asked Questions
- What is SQL injection?
- SQL injection is a vulnerability where untrusted input is incorporated into a SQL statement in a way that changes the query's logic. An attacker can append operators, subqueries, or stacked commands to read, modify, or delete data the application did not intend to expose.
- Can GitHub detect SQL injection on its own?
- GitHub provides CodeQL and secret scanning, which can surface some injection-related patterns when configured. It does not replace application-specific review of how your code builds queries. A GitHub App focused on pull request diffs can add deterministic checks on every PR without waiting for scheduled analysis.
- Does GitHub Code scanning find SQL injection?
- CodeQL can detect many SQL injection patterns in supported languages when you enable code scanning and maintain query packs. Coverage depends on language, query configuration, and whether the vulnerable path appears in analysed code. PR-scoped scanning limits findings to changed lines, which reduces noise on large repositories.
- How does pull request scanning detect SQL injection?
- PR scanners analyse added and changed lines in the diff. They look for string concatenation into SQL, unsafe ORM raw-query helpers, and taint flow from request parameters into query execution sinks. Only scanning new code keeps results relevant to the change under review.
- What is the difference between SAST and manual review for SQL injection?
- Manual review relies on reviewers spotting unsafe query construction in a diff. SAST applies deterministic rules and taint analysis at scale on every PR. Manual review still matters for business logic and schema design; SAST catches repetitive mistakes and risky patterns reviewers often skim past.
- Does Launchioo detect SQL injection in JavaScript, TypeScript, Python, and PHP?
- Launchioo analyses pull request diffs regardless of language. Its taint engine flags user-controlled data reaching SQL-like query sinks (query, execute, raw helpers with SELECT/INSERT/UPDATE/DELETE). Language-specific ORM patterns in changed lines are evaluated alongside general injection and taint rules.
- Are ORMs safe from SQL injection?
- ORMs reduce risk when you use parameterised APIs and avoid passing raw SQL strings built from user input. They are not safe by default: methods like raw(), execute() with string templates, and dynamic fragment concatenation reintroduce the same vulnerability as hand-written SQL.
- What is a parameterised query?
- A parameterised query separates the SQL structure from user-supplied values. The database driver binds values as data, not executable syntax, so quotes and operators in input cannot alter query logic. Prepared statements and bound parameters are the standard fix for SQL injection.
- Can SQL injection be detected before merge?
- Yes, if scanning runs on the pull request before it lands on the default branch. Findings posted as check runs or review comments give authors a chance to fix query construction in the same PR. Pro plans can fail checks when critical injection paths are detected and branch protection requires passing checks.
- Does Launchioo replace dedicated database security tools?
- Launchioo focuses on application code in GitHub pull requests — how queries are built in your services. Database activity monitoring, WAF rules, and runtime protection address different layers. Use PR scanning to stop vulnerable query code from merging; use runtime controls as additional defence in depth.
See the product documentation and pricing for plan details.
Related Resources
- GitHub pull request security — complete guide
- What is SSRF? Detecting server-side request forgery in pull requests
- What is CSRF? Detecting CSRF vulnerabilities in pull requests
- How to detect hardcoded secrets in pull requests before production
See also: product documentation, pricing, and the setup guide.
Scan pull requests for SQL injection
Install Launchioo on your GitHub repositories and get findings on every pull request before code reaches your default branch.