SonarQube v2025.1 Not Detecting SQL Injection in TypeScript

What language is this for?

TypeScript

Which rule?

The relevant rule should be related to detecting SQL Injection vulnerabilities in database queries. I believe this falls under security hotspot or vulnerability detection related to dynamically constructed SQL queries.

Why do you believe it’s a false-positive/false-negative?

False-negative:
SonarQube should ideally detect and flag SQL injection in cases where untrusted input (e.g., workflowDefinitionId) is directly concatenated into the query string without input sanitization or parameterized queries. Here’s why I believe this is a false-negative:

  • The workflowDefinitionId comes directly from a function argument and is concatenated without validation or escaping.
  • The query is dynamically constructed, making it vulnerable to SQL injection.
  • This is a classic SQL injection pattern that is commonly flagged by static analyzers and security tools.

SonarQube Server

Version: SonarQube v2025.1 Developer Edition

How can we reproduce the problem?

You can reproduce this by scanning a TypeScript project containing the following function:

async getWorkflowDefinition(context: ContextModel, workflowDefinitionId: string): Promise<WorkflowDefinition> {
  const client = await getReadClient();
  const result = await client.query(`SELECT * FROM "${context.clientId}".workflow_definitions WHERE id = ${workflowDefinitionId}`);
  return toCamelCase(workflow.rows[0]);
}

Scanner context

Plugins:
Bundled analyzers:
  - JaCoCo 1.3.0.1538 (jacoco)
  - IaC Code Quality and Security 1.41.0.14206 (iacenterprise)
  - IaC Code Quality and Security 1.41.0.14206 (iac)
  - Text Code Quality and Security 2.20.0.5038 (textdeveloper)
  - Clean as You Code 2.4.0.2018 (cayc)
Global server settings:
  - sonar.abap.file.suffixes=.abap,.ab4,.flow,.asprog
  - sonar.auth.github.appId=238585
  - sonar.auth.github.enabled=true
  - sonar.auth.github.organizations=....
  - sonar.auth.saml.enabled=false
  - sonar.auth.saml.loginUrl=.......
  - sonar.azureresourcemanager.file.suffixes=.bicep
  - sonar.c.file.suffixes=.c,.h
  - sonar.core.id=.......
  - sonar.core.serverBaseURL=https://..../
  - sonar.core.startTime=2025-01-30T16:44:14+0000
  - sonar.cpp.file.suffixes=.cc,.cpp,.cxx,.c++,.hh,.hpp,.hxx,.h++,.ipp,.ixx,.mxx,.cppm,.ccm,.cxxm,.c++m
  - sonar.cs.file.suffixes=.cs,.razor
  - sonar.css.file.suffixes=.css,.less,.scss,.sass
  - sonar.dart.file.suffixes=.dart
  - sonar.dbcleaner.branchesToKeepWhenInactive=master,develop,rc/*
  - sonar.dbcleaner.daysBeforeDeletingAnticipatedTransitions=7
  - sonar.dbcleaner.daysBeforeDeletingClosedIssues=7
  - sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs=5
  - sonar.dbcleaner.weeksBeforeDeletingAllSnapshots=53
  - sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek=2
  - sonar.docker.file.patterns=Dockerfile,*.dockerfile
  - sonar.flex.file.suffixes=as
  - sonar.forceAuthentication=true
  - sonar.global.exclusions=/src/tests,**/*.spec.ts,@types/*,**/.storybook,public/*,uitests/*,**/*.html,**/*.css
  - sonar.global.test.exclusions=*.spec.ts,*.spec.js,**/tests/*,**/uitests/*
  - sonar.go.file.suffixes=.go
  - sonar.html.file.suffixes=.html,.xhtml,.cshtml,.vbhtml,.aspx,.ascx,.rhtml,.erb,.shtm,.shtml,.cmp,.twig
  - sonar.ipynb.file.suffixes=ipynb
  - sonar.java.file.suffixes=.java,.jav
  - sonar.java.jvmframeworkconfig.file.patterns=**/src/main/resources/**/*app*.properties,**/src/main/resources/**/*app*.yaml,**/src/main/resources/**/*app*.yml
  - sonar.javascript.file.suffixes=.js,.jsx,.cjs,.mjs,.vue
  - sonar.json.file.suffixes=.json
  - sonar.jsp.file.suffixes=.jsp,.jspf,.jspx
  - sonar.kotlin.file.suffixes=.kt,.kts
  - sonar.lf.enableGravatar=true
  - sonar.lf.logoUrl=https://...../images/...../u4027.png
  - sonar.multi-quality-mode.enabled=true
  - sonar.objc.file.suffixes=.m
  - sonar.php.file.suffixes=php,php3,php4,php5,phtml,inc
  - sonar.plsql.file.suffixes=sql,pks,pkb
  - sonar.plugins.risk.consent=ACCEPTED
  - sonar.projectCreation.mainBranchName=develop
  - sonar.python.file.suffixes=py
  - sonar.qualityProfiles.allowDisableInheritedRules=false
  - sonar.qualitygate.ignoreSmallChanges=false
  - sonar.ruby.file.suffixes=.rb
  - sonar.scala.file.suffixes=.scala
  - sonar.swift.file.suffixes=.swift
  - sonar.technicalDebt.developmentCost=15
  - sonar.terraform.file.suffixes=.tf
  - sonar.tsql.file.suffixes=.tsql
  - sonar.typescript.file.suffixes=.ts,.tsx,.cts,.mts
  - sonar.vbnet.file.suffixes=.vb
  - sonar.xml.file.suffixes=.xml,.xsd,.xsl,.config
  - sonar.yaml.file.suffixes=.yaml,.yml
Project server settings:
  - sonar.abap.file.suffixes=.abap,.ab4,.flow,.asprog
  - sonar.azureresourcemanager.file.suffixes=.bicep
  - sonar.c.file.suffixes=.c,.h
  - sonar.cpp.file.suffixes=.cc,.cpp,.cxx,.c++,.hh,.hpp,.hxx,.h++,.ipp,.ixx,.mxx,.cppm,.ccm,.cxxm,.c++m
  - sonar.cs.file.suffixes=.cs,.razor
  - sonar.css.file.suffixes=.css,.less,.scss,.sass
  - sonar.dart.file.suffixes=.dart
  - sonar.docker.file.patterns=Dockerfile,*.dockerfile
  - sonar.flex.file.suffixes=as
  - sonar.go.file.suffixes=.go
  - sonar.html.file.suffixes=.html,.xhtml,.cshtml,.vbhtml,.aspx,.ascx,.rhtml,.erb,.shtm,.shtml,.cmp,.twig
  - sonar.ipynb.file.suffixes=ipynb
  - sonar.java.file.suffixes=.java,.jav
  - sonar.java.jvmframeworkconfig.file.patterns=**/src/main/resources/**/*app*.properties,**/src/main/resources/**/*app*.yaml,**/src/main/resources/**/*app*.yml
  - sonar.javascript.file.suffixes=.js,.jsx,.cjs,.mjs,.vue
  - sonar.json.file.suffixes=.json
  - sonar.jsp.file.suffixes=.jsp,.jspf,.jspx
  - sonar.kotlin.file.suffixes=.kt,.kts
  - sonar.objc.file.suffixes=.m
  - sonar.php.file.suffixes=php,php3,php4,php5,phtml,inc
  - sonar.plsql.file.suffixes=sql,pks,pkb
  - sonar.python.file.suffixes=py
  - sonar.ruby.file.suffixes=.rb
  - sonar.scala.file.suffixes=.scala
  - sonar.swift.file.suffixes=.swift
  - sonar.terraform.file.suffixes=.tf
  - sonar.tsql.file.suffixes=.tsql
  - sonar.typescript.file.suffixes=.ts,.tsx,.cts,.mts
  - sonar.vbnet.file.suffixes=.vb
  - sonar.xml.file.suffixes=.xml,.xsd,.xsl,.config
  - sonar.yaml.file.suffixes=.yaml,.yml
Project scanner properties:
  - sonar.coverage.exclusions=src/**/*.spec.ts,**/*.test.ts,node_modules/*,reports/*
  - sonar.eslint.eslintconfigpath=./.eslintrc.cjs
  - sonar.eslint.reportPaths=reports/eslint.json
  - sonar.exclusions=src/types/**/*,src/schema/**/*,src/seed/**/*,src/models/**/*,src/handlers/migrations/*,src/proxies/**/*.ts,src/docs.ts,src/app.ts,src/handlers/local-migrations.ts,src/handlers/**/*.ts,src/shared/**/*.ts,src/middleware/**/*.ts,src/utils/**/*.ts,src/helpers/**/*.ts,src/db/**/*.ts,src/config.ts,src/migrations.ts,src/services/events/**/*.ts
  - sonar.host.url=https://....../
  - sonar.javascript.file.suffixes=.js
  - sonar.language=ts
  - sonar.links.issue=https://github.com/......./issues
  - sonar.links.scm=https://github.com/......
  - sonar.organization=.....
  - sonar.projectBaseDir=/github/workspace
  - sonar.projectKey=workflow-triggers
  - sonar.pullrequest.github.summary_comment=true
  - sonar.qualitygate.wait=false
  - sonar.scanner.apiBaseUrl=https://....../api/v2
  - sonar.scanner.app=ScannerCLI
  - sonar.scanner.appVersion=6.2.1.4610
  - sonar.scanner.arch=x86_64
  - sonar.scanner.bootstrapStartTime=1738763331495
  - sonar.scanner.home=/opt/sonar-scanner
  - sonar.scanner.os=linux
  - sonar.scanner.wasEngineCacheHit=false
  - sonar.scanner.wasJreCacheHit=MISS
  - sonar.sourceEncoding=UTF-8
  - sonar.sources=./src
  - sonar.testExecutionReportPaths=./reports/test-report.xml
  - sonar.tests=test/unit
  - sonar.token=******
  - sonar.typescript.file.suffixes=.ts
  - sonar.typescript.lcov.reportPaths=./reports/coverage/lcov.info
  - sonar.typescript.tsconfigPath=tsconfig.json
  - sonar.userHome=/opt/sonar-scanner/.sonar
  - sonar.working.directory=/github/workspace/.scannerwork

Hello @altinukshini ,

in general, the taint-analyzer can detect such issues. It only raises an issue though if it detects user input that ends up in the sensitive function.

So unfortunately, the code snippet is not enough for me to tell why no issue is raised. I would need a minimal reproducer, e.g., a tiny Express.js application that passes user input to the sensitive function.