iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Static Analysis

Static analysis tools (SAST) scan your source code for SQL injection patterns without running it. CodeQL, Semgrep, Snyk Code, SonarQube, and language linters catch most query-concatenation bugs in CI, freeing review time for the harder problems.

CodeQL, Semgrep, custom rules, CI

EXAMPLE
// SCENARIO — wiring SQLi-focused SAST into CI to catch concatenation before it ships.
// We focus on configuration + defensive engineering.

// ─── 1) Semgrep — fast, language-agnostic, easy custom rules ──
//
// Install:    brew install semgrep
// Run rules:  semgrep --config p/owasp-top-ten .
//             semgrep --config p/r2c-security-audit .

// Custom rule — block string concat with SQL keywords (Node)
// .semgrep/sql-concat.yml
rules:
    - id: nodejs-sql-string-concat
      patterns:
          - pattern-either:
                - pattern: db.query(`...SELECT...${EXPR}...`)
                - pattern: db.query('...SELECT...' + EXPR + '...')
                - pattern: db.query(`...INSERT...${EXPR}...`)
                - pattern: db.execute(`...UPDATE...${EXPR}...`)
      message: Use parameterised queries — '?' placeholders, never string interpolation.
      languages: [javascript, typescript]
      severity: ERROR
      metadata:
          cwe: 'CWE-89'
          owasp: 'A03:2021 Injection'

// Custom rule — Python (sqlite3 / psycopg)
rules:
    - id: python-sql-fstring
      patterns:
          - pattern-either:
                - pattern: cursor.execute(f'...SELECT...{EXPR}...')
                - pattern: cursor.execute(f'...INSERT...{EXPR}...')
                - pattern: cursor.execute('...SELECT...' + EXPR + '...')
                - pattern: cursor.execute('...SELECT...%s...' % EXPR)
      message: Use parameter binding — pass values as a SECOND argument to execute().
      languages: [python]
      severity: ERROR

// Run in CI
// .github/workflows/semgrep.yml
name: semgrep
on: [push, pull_request]
jobs:
    scan:
        runs-on: ubuntu-latest
        container: returntocorp/semgrep
        steps:
            - uses: actions/checkout@v4
            - run: semgrep --config p/owasp-top-ten --config .semgrep/ --error

// ─── 2) CodeQL — GitHub's deep dataflow analysis ──────────────
// Tracks user input from a SOURCE (req.body, req.query) through to a SINK (db.query)
// and warns when no SANITIZER step intervened.

// .github/workflows/codeql.yml
name: codeql
on: [push, pull_request]
jobs:
    analyze:
        runs-on: ubuntu-latest
        permissions: { security-events: write }
        steps:
            - uses: actions/checkout@v4
            - uses: github/codeql-action/init@v3
              with: { languages: javascript, queries: 'security-and-quality' }
            - uses: github/codeql-action/analyze@v3

// CodeQL queries for SQLi:
//   js/sql-injection
//   py/sql-injection
//   java/sql-injection
//   csharp/sql-injection
//   go/sql-injection
//
// Findings show up in the GitHub Security tab.

// ─── 3) Custom CodeQL — find ALL execute() calls reaching string concat ──
// (Advanced; CodeQL has a steeper learning curve than Semgrep.)
import javascript
from CallExpr c, Expr e
where c.getCalleeName() = "execute" or c.getCalleeName() = "query"
     and e = c.getArgument(0)
     and e instanceof TemplateLiteral
select c, "Possible SQLi: template literal used as a query."

// ─── 4) ESLint plugins (JavaScript / TypeScript) ──────────────
// • eslint-plugin-security
// • @typescript-eslint with custom rules

// eslint.config.js
import security from 'eslint-plugin-security';
export default [
    { plugins: { security }, rules: security.configs.recommended.rules },
];

// Catches: 'detect-sql-literal-injection', 'detect-non-literal-fs-filename', etc.

// ─── 5) Snyk Code — proprietary SAST with strong dataflow ─────
// • Web UI + CLI: snyk code test
// • CI integration: snyk-cli action
// • Pricing: free tier for open source; paid for private repos

// ─── 6) SonarQube / SonarCloud — multi-language, dashboards ──
// • Includes SQLi detection rules per language
// • Pull-request decoration with findings inline
// • Quality gate: 'no new vulnerabilities on changed lines'

// ─── 7) GitHub Advanced Security — Dependency + Code + Secret scanning ──
// Includes CodeQL + secret scanning + Dependabot. Highest signal for GitHub-hosted repos.

// ─── 8) Language-specific tools ───────────────────────────────
// Go:      gosec (pattern-based)
// Rust:    cargo-audit (deps); some Clippy lints; no widely-used SAST
// Java:    SpotBugs + FindSecBugs plugin; CodeQL
// PHP:     Psalm + psalm-plugin-laminas; PHPStan with extensions
// Ruby:    Brakeman (Rails-specific)
// .NET:    Roslyn analyzers + SecurityCodeScan

// ─── 9) Triaging SAST findings ────────────────────────────────
// Three buckets per finding:
//   • True positive — fix the code
//   • False positive — annotate to suppress + document why
//   • Won't fix — risk-accepted by security + engineering leadership
//
// Annotate suppressions inline so reviewers see them in diffs:
//   // semgrep-disable-next-line nodejs-sql-string-concat
//   // explanation: this string contains only schema metadata, no user input

// ─── 10) Pre-commit hooks — catch BEFORE CI ──────────────────
// .pre-commit-config.yaml
repos:
    - repo: https://github.com/returntocorp/semgrep
      rev: v1.45.0
      hooks:
          - id: semgrep
            args: ['--config', 'p/owasp-top-ten', '--error']

// Fast feedback; runs only on changed files.

// ─── 11) Combining with DAST ─────────────────────────────────
// SAST catches bugs at code level. DAST catches bugs at runtime. Use both:
//   • SAST in CI on every PR
//   • DAST nightly on staging
//   • IAST (runtime instrumentation) in pre-prod for the hardest-to-find bugs
//   • SCA (Dependabot, Snyk Open Source) for known-CVE deps

// ─── 12) Eliminate the bug CLASS, not just the bugs ──────────
// Best: design the system so concat-style SQLi is IMPOSSIBLE.
//   • Use a query builder or ORM that rejects raw strings
//   • Centralise the DB layer (one repository per aggregate)
//   • Lint for `db.query(`...${...}...`)` and disallow entirely
//   • Code-review checklist line: 'Is every dynamic value a bind parameter?'
//
// SAST then becomes a regression net, not the primary control.

// ─── 13) Reporting + metrics ─────────────────────────────────
// Track:
//   • Number of new SAST findings per PR (target: 0 criticals)
//   • Time-to-fix per severity
//   • False-positive rate (helps you tune rules)
//   • Rule coverage by language / repo
//   • Engineer awareness — train teams on the most common findings each quarter

// ─── 14) Common bugs / mistakes ──────────────────────────────
// • SAST disabled because of false positives — tune rules + suppress with explanations, don't disable
// • Rule coverage gaps — e.g. Semgrep config doesn't include the language your repo uses
// • Findings ignored because nobody owns them — assign by code ownership
// • SAST only on main branch — bugs already merged; run on PRs to catch BEFORE merge
// • Mixing SAST with linter exit codes — separate severities; lint warnings shouldn't fail CI on SAST criticals
// • SAST without DAST/SCA — half the picture; combine
// • Pure pattern matching missing dataflow — invest in CodeQL for high-value code paths

Why it matters

Wire Semgrep or CodeQL into CI on every PR, custom-tune rules to your codebase, and bake suppressions with explanations next to the line so reviewers see them. Eliminate the bug class — centralise DB access, lint for template-literal SQL — and treat SAST as the regression net once raw concat is structurally impossible.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Semgrep / CodeQL / SonarQube ship SQLi rules by default.
// Wire them into CI; fail the PR on new High findings.
Try it Yourself »

Discussion

Loading…