Skip to main content

Blog / SAST walkthroughs

TypeScript SAST walkthrough: review a real SQL injection finding

Follow a recorded SQL injection finding in OWASP Juice Shop from source evidence to a proposed fix and regression plan, with the scan limitations included.

Key takeaways

  • TypeScript types do not make request data safe to concatenate into SQL. Review the boundary between untrusted input and the database API.
  • Our October 23, 2026 evaluation recorded a critical CWE-89 finding at routes/login.ts:34 in a pinned OWASP Juice Shop revision.
  • The run had degraded SAST coverage. A finished scan and a finding count do not prove that every file was analyzed.
  • The fix below is an editorial proposal. The recorded evaluation does not contain a scan of that proposed fix or passing application tests.

What does TypeScript SAST check?

TypeScript SAST examines source code for security patterns without starting the application. For an Express service, useful review targets include request data flowing into a database query, a filesystem operation, an outbound request or HTML output. A typed request object describes how code is used; it does not establish that the value came from a trusted caller or that a database will interpret it only as data.

This walkthrough uses a real finding recorded by the CodeStax SAST Analyzer. It follows one TypeScript login route, explains why the source deserves attention, and lays out a fix-and-verify workflow. The example is OWASP Juice Shop, a deliberately vulnerable training application. It is public evaluation evidence, not a customer incident, a production breach or an independent measurement of product accuracy.

Inspect the recorded CodeStax evidence

Recorded TypeScript SAST finding
FieldRecorded value
Repositoryjuice-shop/juice-shop
Repository revision717f5efa886b0417eeecb52378b03cf10567b7e2
Recorded runOctober 23, 2026
Product revision0ef00eba5e5c1ee897f1b00e761b360fa13c0bf1
Locationroutes/login.ts:34
Severity / weaknessCritical / CWE-89: SQL injection
Analysis methodPattern
SAST coverageDegraded; 23 files could not be fully parsed and one rule timed out

Download the TypeScript finding evidence (JSON) for the full recorded rule identifier, message, analyzer version, artifact hashes and coverage warning. Open the pinned login route (opens in a new tab) to compare the location with the source. Pinning matters: the current default branch can change, so the same line number on a later revision may describe different code.

Read the TypeScript query at the reported line

The following line is from the pinned OWASP Juice Shop source, copyright Bjoern Kimminich and the OWASP Juice Shop contributors, licensed under MIT. It is intentionally vulnerable training code. The request email is inserted directly into the SQL text; the password expression does not protect the email expression next to it.

Recorded source excerpt: OWASP Juice Shop routes/login.ts, line 34
models.sequelize.query(`SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL`, { model: UserModel, plain: true })

Review three parts of that statement. The source is req.body.email, controlled by the HTTP caller. The transformation is string interpolation inside a quoted SQL literal. The sink is models.sequelize.query, which receives the resulting SQL statement. If request data changes the structure of that statement, the database can interpret data as query syntax. A TypeScript annotation on req or a cast of the email value would not change how the SQL is assembled.

CodeStax recorded this as critical with CWE-89 and recommended parameterized queries or prepared statements. Severity is the analyzer's assessment, so a reviewer should still inspect reachability, authentication behavior and the database context. The exact code provides stronger grounds for review than a severity label alone. In another repository, an upstream validator or a different query API could change the assessment; read the surrounding handler before deciding.

Propose a bound-parameter fix

The narrow SQL fix is to keep the statement constant and pass values separately. The Sequelize v6 bind-parameter documentation (opens in a new tab) describes this interface. The illustration below preserves the training route's existing password transformation to show the query change clearly. It is not a complete authentication redesign, a recommended password-storage scheme or an AI-generated patch captured in the scan.

Proposed remediation: validate input and bind values separately
const { email, password } = req.body
if (typeof email !== 'string' || typeof password !== 'string') {
  return res.status(400).json({ error: 'Invalid login input' })
}

const authenticatedUser = await models.sequelize.query(
  'SELECT * FROM Users WHERE email = $email AND password = $passwordHash AND deletedAt IS NULL',
  {
    bind: { email, passwordHash: security.hash(password) },
    model: UserModel,
    plain: true
  }
)

Integrate this change into the existing promise chain or an async handler with the application's normal error handling. Preserve the original two-factor and post-login checks rather than replacing the whole login route with this fragment. Validate acceptable input lengths and shapes at the API boundary, and keep authentication failures consistent. Input validation supports the design; it is the separate binding of query values that addresses this SQL construction problem.

Avoid fixing the finding by manually deleting apostrophes, adding a type assertion or escaping the entire query string. Those changes either alter legitimate user data or leave query construction dependent on custom string handling. Do not use bind parameters for dynamic table names or SQL keywords: use a fixed query or an explicit allowlist when identifiers need to vary.

Run the workflow on your TypeScript repository

  1. Connect an enabled repository provider in Dashboard → Integrations, then import the repository you are authorized to scan. For GitHub setup and workflow findings, use the GitHub SAST walkthrough.
  2. Open Dashboard → Repositories, choose Scan and select the intended branch. Record its commit so reviewers can compare the result with the exact source revision.
  3. Choose the scan configuration for the review. SAST is part of repository scanning; additional engines provide different evidence. Inspect the actual configuration and analyzer coverage rather than treating a scan label as a promise of complete coverage.
  4. Open the scan detail and inspect the SAST finding location, rule, severity, message and analysis method. Keep SCA advisories and secret findings distinct from source-level SQL findings.
  5. Review the surrounding TypeScript handler, make a focused change in a branch, run application tests and rescan the changed revision. Retain the before-and-after evidence for review.

For a monorepo, choose representative server routes, shared database helpers and frontend entry points. A browser-only TypeScript package has different trust boundaries from an Express backend. Check exclusions, generated files and parse warnings before concluding that a folder is covered. The scanner coverage documentation (opens in a new tab) explains the product's analyzer coverage; it does not replace checking the coverage state of your own run.

Validate behavior before closing the finding

TypeScript SQL injection validation plan
CheckExpected evidence
Existing valid loginLegitimate credentials still follow the intended authentication flow
Unknown user or incorrect passwordConsistent authentication failure without revealing query details
Quotes and SQL-like punctuation in the email fieldInput remains a bound value and cannot change query structure
Missing, null, array or object inputInput validation rejects unsupported shapes before querying
Two-factor and deleted accountsExisting security checks remain effective
Rescan of the changed commitReview the original rule result together with coverage and any new findings

Write integration tests against the same database dialect used by the application. A unit test that only asserts the presence of a bind object can check an implementation detail while missing a broken login path. Include a valid account, an invalid account and unusual input shapes, then verify that the database and handler behave correctly. Keep these tests in an isolated test environment with synthetic accounts.

A disappeared warning is useful evidence only when the relevant analyzer ran and the file remained in scope. If the new scan skipped the handler, failed to parse it or timed out, mark the outcome unresolved and fix the coverage problem. A successful build and a green type check are useful checks, but they answer different questions from whether untrusted input changes a query.

Read the coverage warning with the result

The recorded Juice Shop run was degraded: 23 files could not be fully parsed and one rule timed out on a bundled JavaScript asset. That warning does not invalidate every emitted finding, but it limits conclusions about files and rules that were not fully analyzed. The downloadable extract includes the recorded warning so readers can inspect it beside the selected SQL result.

If your own TypeScript scan returns no findings, check that the intended branch, source files and language rules were included. Review unsupported syntax, parser errors, file filters and timeouts. Then examine a few known risky boundaries manually. A clean finding list should start a coverage review, not end a security review. SAST is one input alongside tests, design review and runtime testing.

Use this review pattern beyond SQL

The same source-to-sink review structure helps with filesystem paths, redirects and outbound requests: identify who controls the value, inspect transformations, and determine what the destination API does with it. Each boundary needs a different remediation. SQL binding does not solve SSRF, and a URL allowlist does not solve unsafe database construction. Use the finding's rule and CWE to choose an appropriate fix, then validate it in context.

For dependency vulnerabilities, use SCA and the SAST vs SCA guide. For a repository on GitHub, continue with the GitHub SAST walkthrough to connect the repository, inspect a real workflow warning and understand what is required for merge enforcement. Keep a small reviewed record of the source revision, decision, proposed change, test results and rescan coverage so the next reviewer can reconstruct the decision.

Common questions

Does TypeScript prevent SQL injection?

No. Type annotations do not stop request data from becoming SQL syntax when it is concatenated into a query. Validate input and pass values through the database library's supported parameter interface.

Is this a real CodeStax TypeScript finding?

Yes. It comes from the recorded October 23, 2026 evaluation of a pinned OWASP Juice Shop revision. The JSON extract includes artifact hashes, the exact rule and the degraded coverage warning. It is not a customer scan.

Did CodeStax verify the proposed fix?

The recorded artifacts contain the original finding, not a scan of the editorial fix shown here. Run application regression tests and rescan the changed commit before treating a remediation as verified.

Is a scan with no findings enough to approve a TypeScript change?

Review analyzer coverage, file scope and errors before interpreting a result. Missing analysis can produce an empty finding list, and static analysis does not replace application tests or human review.

Your next step

Review a TypeScript finding in your own code

Connect your repository, inspect SAST coverage and review a concrete finding before deciding on a fix.

Get started free