Skip to content

feat: add JavaScript/TypeScript crypto detection for Node.js - #539

Closed
Siddhartha20242 wants to merge 20 commits into
cbomkit:mainfrom
Siddhartha20242:feature/javascript-crypto-support
Closed

Siddhartha20242 wants to merge 20 commits into
cbomkit:mainfrom
Siddhartha20242:feature/javascript-crypto-support

Conversation

@Siddhartha20242

Copy link
Copy Markdown

Summary

This PR adds (currently in the working phase) JavaScript and TypeScript support to the Sonar Cryptography plugin. It detects cryptographic API usage in Node.js projects and generates a CBOM.

Build status

  • mvn clean compile passes
  • All JavaScript tests passses

Reference: #521

@Siddhartha20242
Siddhartha20242 requested a review from a team as a code owner September 4, 2026 09:31
@Siddhartha20242
Siddhartha20242 marked this pull request as draft September 8, 2026 12:16
@Siddhartha20242
Siddhartha20242 force-pushed the feature/javascript-crypto-support branch from 824c228 to 201e343 Compare September 14, 2026 18:47
Adds JavaScript module with ESLint bridge for parsing JS/TS.

Detection rules cover:
- Hash (createHash, update, digest)
- HMAC (createHmac, update, digest)
- Ciphers (createCipheriv, createDecipheriv)
- Signatures (createSign, createVerify)
- RSA public/private key encryption
- KDF (PBKDF2, scrypt, HKDF)
- Key generation & DH/ECDH
- PRNG detection
- TLS protocol and cipher suite config

Includes CBOM translation layer, sensor integration, and test suite.

Reference of the Issue: cbomkit#521

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Before: generateKeyPairSync('ed25519', {
  publicKeyEncoding: {...}
}) lost the object argument because the runner parsed one line at a time.

After: statements spanning multiple lines are joined before regex parsing,
so all arguments are captured. Also tracks the start line for accurate
source locations.

Verified on chain-test: generateKeyPairSync now reports 2 args
['string', 'object'] instead of 1.

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
@Siddhartha20242
Siddhartha20242 force-pushed the feature/javascript-crypto-support branch from 6a06e7c to 43cd0d7 Compare September 15, 2026 06:59
@Siddhartha20242
Siddhartha20242 marked this pull request as ready for review September 15, 2026 07:00
@Siddhartha20242

Copy link
Copy Markdown
Author

Hi @san-zrl, this PR is ready for review whenever you have a moment, could you take a look?

@san-zrl

san-zrl commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Hi @Siddhartha20242 - Can you fix the build error before we proceed with this PR? Tx.

[ERROR] Failed to execute goal com.github.eirslett:frontend-maven-plugin:1.13.3:npm (npm install) on project javascript: Failed to run task: 'npm install --production' failed. org.apache.commons.exec.ExecuteException: Process exited with an error: 254 (Exit value: 254) -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR] 
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <args> -rf :javascript
Error: Process completed with exit code 1.

Standalone calls like crypto.createHash('sha256').digest() were dropped
by the detection engine. Now always wrap in CallExpressionWithBlockTree
so both assigned and standalone calls use the same code path.

Verified: FemtoCoin detections went from 1 to 5 assets.
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
@san-zrl

san-zrl commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Hi @Siddhartha20242 - This looks better, but now we have a test failure.

[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.057 s <<< FAILURE! -- in com.ibm.plugin.ESLintBridgeTest
[ERROR] com.ibm.plugin.ESLintBridgeTest.analyzeFiles_extractsNodeCryptoCalls -- Time elapsed: 0.053 s <<< FAILURE!
org.opentest4j.AssertionFailedError: 

Expecting value to be true but was false
	at com.ibm.plugin.ESLintBridgeTest.analyzeFiles_extractsNodeCryptoCalls(ESLintBridgeTest.java:56)

[INFO] Running com.ibm.plugin.JavaScriptCbomEndToEndTest
...

[INFO] sonar-cryptography :: JavaScript ................... FAILURE [  8.644 s]
[INFO] sonar-cryptography-plugin .......................... SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:27 min
[INFO] Finished at: 2026-09-15T13:30:54Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-surefire-plugin:3.5.6:test (default-test) on project javascript: There are test failures.
[ERROR] 
[ERROR] See /home/runner/work/sonar-cryptography/sonar-cryptography/javascript/target/surefire-reports for the individual test results.
[ERROR] See dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [date].dumpstream.
[ERROR] -> [Help 1]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
[ERROR] 
[ERROR] After correcting the problems, you can resume the build with the command
[ERROR]   mvn <args> -rf :javascript
Error: Process completed with exit code 1.

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
@Siddhartha20242

Copy link
Copy Markdown
Author

Hi @san-zrl thanks for checking!

The screenshot shows my local run of the exact same test on the current HEAD (3a17e10): Tests run: 20, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS.

image

Please have a look when you get some time.

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
@san-zrl

san-zrl commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Hi @Siddhartha20242 - The build behavior is highly dependent on the environment. Are there dependencies on external tools that may differ between your system, my system and the CI VM? On my system it fails with

[INFO] Running com.ibm.plugin.PluginTest
15:49:34,849 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found logback-core version 1.5.35
15:49:34,849 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found logback-classic version 1.6.3
15:49:34,849 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Versions of logback-classic and 1.5.35 are different or unknown.
15:49:34,850 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - No custom configurators were discovered as a service.
15:49:34,850 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - Trying to configure with ch.qos.logback.classic.util.DefaultJoranConfigurator
15:49:34,850 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - Constructed configurator of type class ch.qos.logback.classic.util.DefaultJoranConfigurator
15:49:34,851 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
15:49:34,851 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml]
15:49:34,853 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - ch.qos.logback.classic.util.DefaultJoranConfigurator.configure() call lasted 1 milliseconds. ExecutionStatus=INVOKE_NEXT_IF_ANY
15:49:34,853 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - Trying to configure with ch.qos.logback.classic.BasicConfigurator
15:49:34,854 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - Constructed configurator of type class ch.qos.logback.classic.BasicConfigurator
15:49:34,854 |-INFO in ch.qos.logback.classic.BasicConfigurator@5d9b7a8a - Setting up default configuration.
15:49:34,863 |-INFO in ch.qos.logback.core.ConsoleAppender[console] - NOTE: Writing to the console can be slow. Try to avoid logging to the
15:49:34,863 |-INFO in ch.qos.logback.core.ConsoleAppender[console] - console in production environments, especially in high volume systems.
15:49:34,863 |-INFO in ch.qos.logback.core.ConsoleAppender[console] - See also https://logback.qos.ch/codes.html#slowConsole
15:49:34,863 |-INFO in ch.qos.logback.classic.util.ContextInitializer@77b14724 - ch.qos.logback.classic.BasicConfigurator.configure() call lasted 9 milliseconds. ExecutionStatus=NEUTRAL

15:49:34.868 [main] INFO com.ibm.plugin.CryptographyPlugin -- Sonar Cryptography initialized in context (SONARQUBE)
[ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.074 s <<< FAILURE! -- in com.ibm.plugin.PluginTest
[ERROR] com.ibm.plugin.PluginTest.testExtensions -- Time elapsed: 0.062 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <10> but was: <13>
	at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:158)
	at org.junit.jupiter.api.AssertionFailureBuilder.buildAndThrow(AssertionFailureBuilder.java:139)
	at org.junit.jupiter.api.AssertEquals.failNotEqual(AssertEquals.java:201)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:152)
	at org.junit.jupiter.api.AssertEquals.assertEquals(AssertEquals.java:147)
	at org.junit.jupiter.api.Assertions.assertEquals(Assertions.java:558)
	at com.ibm.plugin.PluginTest.testExtensions(PluginTest.java:42)

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
@Siddhartha20242

Copy link
Copy Markdown
Author

Hi @san-zrl , the JS module needs Node.js which is automatically downloaded by frontend-maven-plugin during build). The eslint-runner.js uses only built-in Node modules (fs, path) and there is no external npm packages. That's why I removed the failing npm install step: there was nothing to install.

Requirements: JDK 17 + internet on first build. No npm packages needed.

All checks are green on 787a3c5 which is ready for review when you have a moment.

image

@san-zrl

san-zrl commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Hi @Siddhartha20242 - I ran an automatic evaluation with our standard AI. No idea how good this really is but it gives a few hints that should be addressed. Please have a look and let me know your thoughts. We can do a deeper analysis later.


PR-539 Evaluation: JavaScript/TypeScript Crypto Detection for Node.js

Summary

This PR adds a new javascript Maven module that brings Node.js crypto and tls cryptographic API detection to the SonarQube plugin. It follows the same architectural pattern as the existing Java, Python, Go, and C# modules: a custom sensor, detection rules, a translation/reorganizer pipeline, and a CBOM aggregator. The change is large (7,771 insertions, 109 files, 9 commits).


1. Architecture & Design

✅ What's well done

  • The overall structure mirrors established modules cleanly: sensor → ESLint bridge → detection engine → translator → reorganizer → aggregator.
  • JavaScriptDetectionRules aggregates all rule families via a single memoized entry point, consistent with other languages.
  • Memoize uses correct volatile double-checked locking — this is non-trivial to get right.
  • JavaScriptScanContext.IssueReporter isolates sensor APIs from detection logic, enabling clean unit tests.
  • Plugin registration in CryptographyPlugin and ScannerManager is minimal and correct.
  • The frontend-maven-plugin approach to bundling Node is a reasonable solution for embedded execution.

⚠️ Design concerns

The runner is a regex scanner, not an ESLint runner. Despite being called ESLintBridge/ESLintExecutor/eslint-runner.js, the Node script does not invoke ESLint or any JS parser. It is a multi-line regex scanner. This is a significant naming mismatch that will confuse future contributors and sets wrong expectations about accuracy. The filename and all Java bridge classes should be renamed to reflect that this is a custom scanner, not an ESLint integration.

minimizeJar changed from true to false in sonar-cryptography-plugin/pom.xml. This was presumably done because the maven-shade minimizer removes classes needed at runtime. This must be documented — the plugin JAR will now be substantially larger, and the root cause (what reflection or dynamic loading requires non-minimization) should be investigated and tracked.

Stale .gitignore entry. The directory javascript/src/main/resources/node/ is gitignored, but eslint-runner.js is tracked inside it. This is contradictory: the directory is excluded from git, yet the only source file in it is committed. This will break fresh clones or git clean -fdx + rebuild workflows if the runner is expected to come from git.

JavaScriptAggregator uses mutable static state without synchronization. detectedNodes is a plain ArrayList and javascriptLanguageSupport is reassigned in reset(). Concurrent scans would race on these fields, identical to the pre-existing pattern in other aggregators but worth noting.


2. The ESLint Runner (eslint-runner.js)

This file is the highest-risk component of the PR. Its fundamental approach has several classes of correctness problems:

False positives from method-name filtering

The cryptoMethods list includes generic method names — update, digest, final, connect, generateKeys — that are common on many non-crypto objects. The direct-call regex matches any object calling these methods:

logger.update('data');        // flagged
buffer.digest('hex');         // flagged
db.connect(options);          // flagged

There is no proof that the receiver is a crypto/tls object.

Broken string/bracket parsing

The multi-line buffer accumulator counts (, {, [ characters without understanding strings, comments, regex literals, or template literals. A string containing "{" will corrupt the nesting depth and cause incorrect statement boundaries — this is a well-known hazard of regex-based JS parsing.

extractArgs has the same problem: template literals (backtick strings) are not recognized, and the escaped-quote check (line[i-1] !== '\\') fails for double-escapes like "\\'".

Module detection is too narrow

Import binding detection checks only for require("crypto") and require('crypto') with exact quoting. It misses:

  • ES module import crypto from 'node:crypto'
  • Destructuring: const { createHash } = require('crypto')
  • Whitespace variations: require( "crypto" )
  • Variable aliasing: const c = require('crypto'); c.createHash(...)

variableValues is never populated

The field is initialized as an empty object and returned, but the runner never writes into it. The Java-side variable resolution path (currentContext.variableValues().get(...)) is therefore always empty in production.

Unused imports

fs and path are imported but never used.


3. ESLintExecutor — Security & Reliability

No process timeout

ESLintExecutor.analyze() creates a DefaultExecutor with no timeout. A hung or looping Node process will block the SonarQube analysis indefinitely.

Path traversal in JAR extraction

In extractNodeResourcesFromJar(), archive entry names are resolved directly under tempDir with no containment check:

Path target = tempDir.resolve(relativePath);

A crafted JAR entry with a path like ../../bin/evil would write outside the temp directory. Even though this jar is first-party, defense-in-depth suggests adding a canonical-path check.

No integrity check on extracted Node binary

The Node binary is extracted and executed without verifying its hash or signature.

Temp files accumulate

Only the temp directory root has deleteOnExit(). Extracted files and subdirectories are not individually registered, and may accumulate across repeated analysis runs in long-lived JVM processes.


4. ESLintResultParser — Data Fidelity

  • Column is discarded end-to-end. JavaScriptScanContext.report() receives a column from the parser but calls inputFile.selectLine(line) only. All issues are located on the entire line, not the specific expression.
  • currentRule is ignored in reportIssue(). Every detection, regardless of which rule triggered it, is reported under the hard-coded INVENTORY_RULE_KEY. This means JavaScriptNoMD5UseRule and the inventory rule both report as the same Sonar rule.
  • Unknown argument kinds silently become a LiteralTree with type "any" — this hides unsupported syntax rather than reporting it.
  • A null entry in fileResult.calls will cause a NullPointerException.
  • Argument source locations are always line:0, column:0 — set by the runner, never corrected by the parser.

5. Detection Engine (JavaScriptDetectionEngine)

Scope context is overwritten, never restored

currentContext is set to the most recently entered BlockTree and never restored when exiting:

if (tree instanceof BlockTree blockTree) {
    currentContext = blockTree;
    for (Tree statement : blockTree.statements()) {
        run(traceSymbol, statement);
    }
}

In nested block scenarios, identifier resolution will use the wrong variable scope.

Complexity suppression

The class carries @SuppressWarnings("java:S3776") (cognitive complexity). This signals the traversal logic should be decomposed rather than suppressed.

Unsupported engine features

resolveMethodReturnValues() and resolveEnumValue() are no-ops, which is acceptable for a first version but limits what detection rules can match.


6. Translation Pipeline

Utils.printNodeTree called unconditionally

JavaScriptTranslationProcess calls Utils.printNodeTree(...) three times on every finding with no debug guard:

Utils.printNodeTree(" translated ", translatedValues);
Utils.printNodeTree("reorganised ", reorganizedValues);
Utils.printNodeTree("  enriched  ", enrichedValues);

This will produce verbose output on every analysis run and may expose detected cryptographic algorithm names in production logs. This must be removed or placed behind a debug flag before merging.


7. Detection Rules

generateKeyPair / generateKeyPairSync are disabled

In NodeCryptoKeyGen, both key-pair generation rules are defined but excluded from buildRules() with a TODO comment. The Javadoc still claims they are detected. This creates a silent gap in RSA/EC/Ed25519 key-pair inventory coverage — one of the most security-relevant APIs.

createSecretKey is classified as GENERATION

Importing key material is tagged with KeyAction.Action.GENERATION, yet the private-key context correctly stores "kind": "import". This inconsistency will produce inaccurate CBOM output for key import operations.

createCipher / createDecipher absent from runner

The Java detection rules include createCipher and createDecipher, but these are not listed in the runner's cryptoMethods array, so the runner will never emit them and the rules will never fire.

Scrypt options not modeled

NodeCryptoKDF treats scrypt's options object (containing cost parameters N, r, p) as ANY — making it impossible to detect weak scrypt configurations.

KeyDerivationFunctionContext inconsistency

PBKDF2 and HKDF supply a "kind" map; scrypt uses an empty context. Downstream reporting will be non-uniform.

TLS: rejectUnauthorized: false not detected

NodeCryptoTLS does not inspect certificate-validation options, so the most common TLS misconfiguration in Node.js will not be flagged.

Missing modern APIs

No coverage for Web Crypto (crypto.webcrypto.subtle.*), https, http2, tls.createConnection, or tls.TLSSocket.


8. Test Coverage

The test suite is present and the harness design (TestBase + JavaScriptVerifier) is reasonable. However coverage is thin:

Area Status
Happy-path single-file detection ✅
CBOM node type assertions (hash, HMAC, KDF, key agreement) ✅
Variable constant resolution ✅ (single level only)
node:crypto / ES module imports ❌
generateKeyPair / generateKeyPairSync ❌ (rules disabled)
Synchronous KDF variants ❌
TLS connect / rejectUnauthorized ❌
Dynamic algorithm expressions ❌
Weak algorithm detection ❌
Parser error recovery ❌
Multiple findings per file / deduplication ❌
Column accuracy in issue reporting ❌
JavaScriptNoMD5UseRule reporting correct rule key ❌

Most per-family tests (e.g., NodeCryptoCipherTest) extend TestBase but delegate to the shared verifier with a one-line body and minimal assertions. Finding order is assumed by positional findingId, making the tests fragile to traversal-order changes.


9. Checklist Items

  • DCO sign-off — not verified on all commits; should be checked before merge.
  • mvn clean package passes — the PR description checklist item is unchecked, suggesting CI may not be green.

Blockers (must fix before merge)

# Issue Location
B1 Utils.printNodeTree called unconditionally — leaks algorithm data to logs on every analysis JavaScriptTranslationProcess.java
B2 createCipher/createDecipher absent from runner's cryptoMethods — rules defined in Java will never fire eslint-runner.js
B3 currentRule ignored in reportIssue() — all issues reported under wrong rule key JavaScriptScanContext.java
B4 No process timeout on Node subprocess — can hang SonarQube analysis indefinitely ESLintExecutor.java
B5 .gitignore excludes javascript/src/main/resources/node/ but eslint-runner.js lives there .gitignore
B6 minimizeJar: false — unexplained regression in plugin JAR size; root cause must be documented sonar-cryptography-plugin/pom.xml

High-Priority Issues (should fix before merge)

# Issue Location
H1 generateKeyPair/generateKeyPairSync disabled with TODO; Javadoc claims they are detected NodeCryptoKeyGen.java
H2 False-positive risk: generic method names (update, digest, connect) matched on any object eslint-runner.js
H3 variableValues never populated by runner; variable resolution always empty eslint-runner.js
H4 currentContext overwritten on every block entry, never restored; scope resolution incorrect for nested blocks JavaScriptDetectionEngine.java
H5 createSecretKey tagged GENERATION but semantically is an import; CBOM will misreport NodeCryptoKeyGen.java
H6 Column discarded; all issues reported at line level only JavaScriptScanContext.java
H7 Bracket counting ignores string/comment/regex content — malformed statement buffering on real-world JS eslint-runner.js
H8 Path traversal in JAR extraction; no canonical-path containment check ESLintExecutor.java

Minor / Cleanup Issues

  • fs and path imported but unused in runner
  • LOGGER declared but unused in JavaScriptLanguageTranslation
  • Collections.unmodifiableList(enrichedValues.stream().toList()) — double-wrapping; stream().toList() is already unmodifiable
  • sonar-cryptography-plugin/pom.xml indentation of the <csharp> dependency was changed without reason (whitespace-only diff mixed with functional change)
  • JavaScriptAggregator.reset() reinitializes JavaScriptLanguageSupport — unlike other aggregators — which may have unintended side effects during tests
  • @SuppressWarnings("java:S3776") on JavaScriptDetectionEngine should prompt refactoring rather than suppression
  • Detection test methods use positional findingId — fragile to rule/traversal ordering changes

Overall Assessment

The PR establishes a solid structural foundation and successfully integrates JavaScript/TypeScript scanning into the plugin pipeline. The happy-path detection for createHash, createHmac, createCipheriv, KDF APIs, and TLS works end-to-end. However, there are 6 blockers that must be resolved before this is production-ready — most critically the unconditional debug logging of cryptographic data, the wrong rule key being reported, and the Node subprocess having no timeout. The regex-scanning approach in eslint-runner.js is an inherent limitation that should be clearly documented as a known gap (vs. a proper JS parser), and the false-positive risk from generic method names needs to be addressed.

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
- M1: remove unused fs/path requires from eslint-runner.js
- M2: remove unused LOGGER field + imports from JavaScriptLanguageTranslation
- M3: drop redundant Collections.unmodifiableList wrap (stream().toList()
  already returns unmodifiable); remove unused Collections import
- M4: restore csharp dependency indentation to match main

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
- H1: re-enable generateKeyPair / generateKeyPairSync
- H3: wire variableValues from runner; drop dead resolveConstant fallback
- H4: save/restore currentContext around BlockTree traversal
- H5: createSecretKey reports kind=import
- H6: report issues at (line, column)
- H7: replace regex scanner with ESLint/espree AST parser
- H8: block path traversal in JAR extraction; register files for deleteOnExit
- pom: restore npm install for eslint dep
- plugin pom: keep minimizeJar=true with comment

Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
Signed-off-by: Siddhartha bhat <bhattaraiankit2212@gmail.com>
@Siddhartha20242

Copy link
Copy Markdown
Author

Hi @san-zrl pushed all the fixes from the evaluation that you had gave me that are 6 blockers, H1–H8, and 7 minor items. The regex scanner is now a real ESLint/espree AST parser mvn clean package + mvn test are green locally (22 tests). The CI workflow appears to be awaiting approval so could you approve it so CI runs on the latest commits?

Two design notes from your original evaluation that are now resolved:
The ESLintBridge / ESLintExecutor / eslint-runner.js naming, now the runner now genuinely uses ESLint's Linter, so the names are accurate.
minimizeJar=true restored; the JS module's resources survive minimization.

Ready for re-review whenever you have a moment.

@san-zrl

san-zrl commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

Hi @Siddhartha20242 - CI build is green now. I'm currently on vacation; will do another review when I return next week.

@san-zrl san-zrl left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Siddhartha20242 - Your progress is good. Here's the promised review of the recent changes. Please have a look at the newly introduced issues and fix them. Biggest problem in my opinion is N2.

PR-539 Review (Updated)

All items from the previous review round are assessed, followed by new findings discovered in the updated code.


Previous Review Items: Resolution Status

Blockers

B1 — Unconditional Utils.printNodeTree debug logging ✅ Resolved
Removed entirely from JavaScriptTranslationProcess. The redundant Collections.unmodifiableList double-wrap was cleaned up at the same time.

B2 — createCipher/createDecipher absent from runner's method set ✅ Resolved
Both names are now in CRYPTO_MODULE_METHODS. FACTORY_RESULT_TYPES already had them; both maps are now consistent.

B3 — All issues reported under the wrong rule key ✅ Resolved
SensorIssueReporter.report() reads the @Rule(key=...) annotation from the concrete check class and falls back to INVENTORY_RULE_KEY only when none is present. The IssueReporter interface signature was updated to carry currentRule through.

B4 — No process timeout on Node subprocess ✅ Resolved
ESLintExecutor sets new ExecuteWatchdog(60_000L), documented with a comment.

B5 — .gitignore conflicted with tracked eslint-runner.js ✅ Resolved
Only the build-time-downloaded subdirectories (node/node/, node/node_modules/) are now excluded. Source files (eslint-runner.js, package.json, package-lock.json) are correctly tracked.

B6 — minimizeJar: false unexplained ✅ Resolved
minimizeJar is restored to true. The explanatory comment was added then removed by a cleanup commit, so there is again no explanation in the file. A one-line comment is still warranted.


High-Priority Items

H1 — generateKeyPair/generateKeyPairSync disabled ✅ Resolved
Both rules are back in NodeCryptoKeyGen.buildRules(). The test fixture now exercises both calls. Javadoc matches reality.

H2 — False positives from generic method names ✅ Resolved
The runner now uses ESLint/espree AST traversal. shouldEmit() gates every emission on both object type and method name. A dedicated bridge test asserts that logger.update(...) and db.connect(...) are not reported.

H3 — variableValues never populated ✅ Resolved
recordConstant() is invoked from VariableDeclarator and AssignmentExpression visitors. A bridge test asserts blockTree.variableValues().containsEntry("algorithm", "sha256") end-to-end.

H4 — currentContext overwritten and never restored ✅ Resolved
JavaScriptDetectionEngine.run() now saves and restores currentContext in a try/finally block around every BlockTree traversal.

H5 — createSecretKey tagged GENERATION despite being an import ⚠️ Partially resolved
buildForContext() now uses new SecretKeyContext(Map.of("kind", "import")), which is correct. However, the action factory is still new KeyActionFactory<>(KeyAction.Action.GENERATION). The action and the context metadata are contradictory — GENERATION implies a new key was produced, "kind": "import" says it was imported. The context fix is the right half; the action tag should use an import-appropriate value or the discrepancy must be documented as intentional.

H6 — Column discarded; issues reported at line level only ✅ Resolved
selectIssueRange() uses inputFile.newRange(safeLine, startOffset, safeLine, startOffset + 1) with clamping and a selectLine() fallback on IllegalArgumentException.

H7 — Regex scanner replaced with ESLint/espree AST parser ✅ Resolved (core issue)
The runner is now a proper ESLint rule using Linter and espree. String, template, and comment handling is correct. This is the most substantial improvement in the update.

H8 — Path traversal in JAR extraction ✅ Resolved
Both the JAR and filesystem extraction paths normalize the resolved target and throw IOException on escape. Files are individually registered with deleteOnExit().


Minor Cleanup Items (M1–M4)

All resolved: unused fs/path imports removed from the runner, Collections.unmodifiableList double-wrap removed, pom.xml indentation normalised.


New Findings in the Updated Code

These are issues that either did not exist before or were introduced by the fixes above.


🔴 Must Fix

N1 — context.getAncestors() is deprecated in ESLint 8 and removed in ESLint 9

// eslint-runner.js:292
const ancestors = context.getAncestors();

This API was deprecated in ESLint 8 and does not exist in ESLint 9. The runner pins eslint@8.57.1 so it works today, but will break silently the moment ESLint is bumped. The non-deprecated replacement is one line:

const ancestors = context.getSourceCode().getAncestors(node);

Fix this now to avoid a future broken upgrade.

N2 — TypeScript is listed as supported but espree cannot parse .ts files

CryptoJavaScriptSensor includes "ts" in SUPPORTED_LANGUAGES and JavaScriptRulesDefinition has a "typescript" entry in REPOSITORY_KEYS_BY_LANGUAGE. TypeScript syntax (type annotations, generics, interfaces) is not valid ES2022; espree will produce a fatal parse error and return an empty tree — silently, with no warning to the user. Net effect is that the current implementation does not support typescript at all in contradiction to the PR title.


🟡 Should Fix

N3 — generateKeyPair/generateKeyPairSync use KeyContext instead of typed key contexts

The re-enabled rules in NodeCryptoKeyGen use the base KeyContext() while the import rules use typed PublicKeyContext/PrivateKeyContext. Key-pair generation is the most security-relevant operation in this file — it should produce typed CBOM nodes. If KeyContext is intentional (e.g. because a single generation call produces both key types and the engine cannot split them), this must be documented with a comment.

N4 — createSecretKey action is GENERATION but context says "kind": "import"

As noted under H5: the context fix is correct but the KeyAction.Action.GENERATION tag on CREATE_SECRET_KEY still says the operation generates a key. Importing a key is not generation. This will produce inaccurate CBOM output for createSecretKey callers. Apply the same "kind": "import" rationale to the action.

N5 — Watchdog timeout kill is not distinguishable from other failures in the log

When the watchdog kills the Node process, Commons Exec throws ExecuteException with a non-zero exit value. The catch block logs stderr, which will be empty for a timeout kill. The message "ESLint runner failed" gives no indication that a 60-second timeout was the cause. Add a ExecuteWatchdog.killedProcess() check before logging:

} catch (ExecuteException e) {
    if (watchdog.killedProcess()) {
        LOG.warn("ESLint runner timed out after {}ms", WATCHDOG_TIMEOUT_MILLIS);
    } else {
        LOG.warn("ESLint runner failed: {}", stderr.toString(StandardCharsets.UTF_8));
    }
    throw new IOException("ESLint runner failed with exit code " + e.getExitValue(), e);
}

N6 — sourceType: 'module' rejects top-level return statements in CommonJS files

parserOptions: { ecmaVersion: 2022, sourceType: 'module', ... }

sourceType: 'module' is used for all files. Top-level return is a valid CommonJS pattern (e.g. in generated code or some bundler outputs) and is a syntax error under ES module semantics. Such files will hit the fatal parse-error path and return an empty tree. Consider retrying with sourceType: 'script' when the first parse returns a fatal error.


🔵 Nit / Low

N7 — Module-level current shared state lacks a thread-safety comment

let current = null;   // line 109

current is set before each linter.verify() call and captured immediately in the rule's create(). This is correct for the synchronous stdin model. A one-line comment explaining why this is safe would prevent a future reader from flagging it as a race condition or "fixing" it incorrectly.

N8 — NodeCryptoRuleTestBase only checks that detection fired, not what was detected

// NodeCryptoRuleTestBase.asserts()
if (!nodes.isEmpty()) { sawTranslatedNodes = true; }

The base class introduced for the new per-family tests only verifies that nodes are emitted — it does not assert algorithm values, context types, or node kinds. NodeCryptoKeyGenTest, NodeCryptoCipherTest, etc. all inherit this and make no additional assertions. This means a rule could fire with the wrong algorithm or wrong context type and the tests would still pass. At minimum, add assertions for the context type (DigestContext, KeyContext, etc.) in the base class or in each subclass.

N9 — minimizeJar: true has no explanatory comment after cleanup commit

The explanatory comment added in f810391b was removed by c5cd9cf6. Future contributors who hit a runtime class-not-found error may turn minimizeJar back to false without understanding why it was true. Restore the comment.


Summary

# Severity Item Status
B1–B6 Blocker All six blockers from round 1 ✅ Resolved
H1–H4, H6–H8 High Seven of eight high-priority items ✅ Resolved
H5 High createSecretKey action/context contradiction ⚠️ Half-fixed (context correct, action still wrong)
N1 🔴 Must fix context.getAncestors() deprecated, breaks on ESLint 9 New
N2 🔴 Must fix TypeScript listed as supported; silently returns empty results New
N3 🟡 Should fix generateKeyPair rules use KeyContext instead of typed contexts New
N4 🟡 Should fix createSecretKey GENERATION action contradicts "kind": "import" context Residual from H5
N5 🟡 Should fix Watchdog timeout kill logged as generic failure New
N6 🟡 Should fix sourceType: 'module' breaks CJS files with top-level return New
N7 🔵 Nit current shared state needs a thread-safety comment New
N8 🔵 Nit Rule test base class asserts detection fired but not what was detected New
N9 🔵 Nit minimizeJar comment removed; rationale is lost again New

Two items (N1, N2) must be fixed before merge. N1 is a one-line code change. N2 requires either removing "ts" from the supported-languages list or adding an explicit warning when TypeScript files fail to parse.

@n1ckl0sk0rtge

n1ckl0sk0rtge commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Hi @Siddhartha20242, thank you for the work you have put into this PR and for addressing the earlier review comments.

After reviewing the approach more closely, I am going to close this PR so we can settle the parser and engine integration before continuing with an implementation. Our other language modules use either an available Sonar language parser (Java, Python, Go) or ANTLR (C#). This PR uses ESLint to select crypto calls, then builds a reduced AST of its own. Although it invokes the shared DetectionExecutive and DetectionStore, that reduced tree loses structure needed by the engine's dependent-rule traversal. For example, ESLintResultParser.toBlockTree() attaches an empty block to each call, so the dependent rules declared for createHash cannot traverse the later update and digest calls. The current tests establish an initial finding, but do not verify that chain.

Could you first evaluate these two options and propose how you would implement JS and TS support?

  1. SonarJS: check whether there is a usable parser/AST integration point for a separate plugin. SonarQube's documentation says custom JS/TS rules are unsupported, so please spell out any API limitation you find.
  2. ANTLR JavaScript and TypeScript grammars: check whether they cover the syntax we need, including TypeScript, and how an adapter would preserve scopes, symbols, and call relationships.

The proposal should show how the parser feeds our existing ILanguageSupport/IDetectionEngine contract and reuses DetectionStore, call-stack and hook handling, and the CBOM pipeline. The first follow-up PR should be a small feasibility test: one detection rule and one source fixture, wired end to end through the chosen parser and shared engine to a CBOM finding. A typed TypeScript fixture using createHash, update, and digest would test the parser and the dependent-rule path together. We can expand coverage after that path is proven. If neither existing option works, we can discuss a custom bridge and its tradeoffs in #521 before writing another full implementation.

This closes the current approach, not the request for JavaScript and TypeScript support. Thanks again.

@Siddhartha20242

Copy link
Copy Markdown
Author

Hi @n1ckl0sk0rtge , thanks for the detailed feedback :)

SonarJs yes it's confirmed which i have mentioned in the meeting too. SonarQube's docs say custom rules aren't supported for JS/TS, and there's no API to hook into its parser or AST externally. The only path is importing ESLint issues, which skips our detection engine entirely.

ANTLR which leaves us only ANTLR which is same as the C# module. i have few assumptions going in:

  1. We'll likely need a custom Publisher class, since ANTLR won't give us a JavaFileScannerContext equivalent (similar to Go's GoScanContext).
  2. I em planning to map ANTLR's parse tree into new JS/TS specific Tree/Symbol types rather than reusing existing ones.
  3. I wiill start from the grammars-v4 TypeScript grammar.

This should fix the #539 issue to ---------- the toBlockTree() problem (an empty block on each call, so dependent rules couldn't traverse from createHash to update/digest) was a symptom of not having a real AST.
An ANTLR based tree should preserve that call chain properly.

Feasibility test plan i.e first will be checking the basic versions of the five engine interfaces, one rule for crypto.createHash(), and will be testted against a simple sequential case first, then the inline chained version, to confirm the dependent rule path actually traverses correctly.

@Siddhartha20242

Copy link
Copy Markdown
Author

Hi @n1ckl0sk0rtge, I posted the evaluation and proposal on #521: #521 (comment)

The first follow-up PR is the feasibility test you described: #564

It has one rule (createHash with update and digest) and one typed TypeScript fixture, run through the shared engine to a CBOM digest node. I'd like your view on the open questions in the #521 comment before I expand anything.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for JavaScript / Typescript in Sonar-Cryptography plugin

3 participants