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
| Field | Recorded value |
|---|---|
| Repository | juice-shop/juice-shop |
| Repository revision | 717f5efa886b0417eeecb52378b03cf10567b7e2 |
| Recorded run | October 23, 2026 |
| Product revision | 0ef00eba5e5c1ee897f1b00e761b360fa13c0bf1 |
| Location | routes/login.ts:34 |
| Severity / weakness | Critical / CWE-89: SQL injection |
| Analysis method | Pattern |
| SAST coverage | Degraded; 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.
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.
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
- 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.
- Open Dashboard → Repositories, choose Scan and select the intended branch. Record its commit so reviewers can compare the result with the exact source revision.
- 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.
- 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.
- 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
| Check | Expected evidence |
|---|---|
| Existing valid login | Legitimate credentials still follow the intended authentication flow |
| Unknown user or incorrect password | Consistent authentication failure without revealing query details |
| Quotes and SQL-like punctuation in the email field | Input remains a bound value and cannot change query structure |
| Missing, null, array or object input | Input validation rejects unsupported shapes before querying |
| Two-factor and deleted accounts | Existing security checks remain effective |
| Rescan of the changed commit | Review 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.