circle-ir 4.16.1 → 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/advisories.d.ts +186 -0
- package/dist/analysis/advisories.d.ts.map +1 -0
- package/dist/analysis/advisories.js +391 -0
- package/dist/analysis/advisories.js.map +1 -0
- package/dist/analysis/comment-lines.d.ts +14 -0
- package/dist/analysis/comment-lines.d.ts.map +1 -0
- package/dist/analysis/comment-lines.js +34 -0
- package/dist/analysis/comment-lines.js.map +1 -0
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +59 -5
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/constant-caller-gate.d.ts +2 -2
- package/dist/analysis/constant-caller-gate.d.ts.map +1 -1
- package/dist/analysis/constant-caller-gate.js +205 -29
- package/dist/analysis/constant-caller-gate.js.map +1 -1
- package/dist/analysis/constant-propagation/index.d.ts +2 -0
- package/dist/analysis/constant-propagation/index.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/index.js +10 -0
- package/dist/analysis/constant-propagation/index.js.map +1 -1
- package/dist/analysis/constant-propagation/propagator.d.ts +15 -0
- package/dist/analysis/constant-propagation/propagator.d.ts.map +1 -1
- package/dist/analysis/constant-propagation/propagator.js +224 -20
- package/dist/analysis/constant-propagation/propagator.js.map +1 -1
- package/dist/analysis/constant-propagation/types.d.ts +2 -0
- package/dist/analysis/constant-propagation/types.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.d.ts +1 -1
- package/dist/analysis/constant-propagation.d.ts.map +1 -1
- package/dist/analysis/constant-propagation.js +1 -1
- package/dist/analysis/constant-propagation.js.map +1 -1
- package/dist/analysis/entry-point-detection.d.ts.map +1 -1
- package/dist/analysis/entry-point-detection.js +72 -0
- package/dist/analysis/entry-point-detection.js.map +1 -1
- package/dist/analysis/interprocedural.d.ts.map +1 -1
- package/dist/analysis/interprocedural.js +75 -8
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analysis/maven-declared.d.ts +72 -0
- package/dist/analysis/maven-declared.d.ts.map +1 -0
- package/dist/analysis/maven-declared.js +216 -0
- package/dist/analysis/maven-declared.js.map +1 -0
- package/dist/analysis/maven-version.d.ts +28 -0
- package/dist/analysis/maven-version.d.ts.map +1 -0
- package/dist/analysis/maven-version.js +172 -0
- package/dist/analysis/maven-version.js.map +1 -0
- package/dist/analysis/passes/constant-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/constant-propagation-pass.js +29 -1
- package/dist/analysis/passes/constant-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +84 -2
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts +7 -0
- package/dist/analysis/passes/csharp-sanitizer-credit.d.ts.map +1 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js +44 -1
- package/dist/analysis/passes/csharp-sanitizer-credit.js.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.d.ts +2 -0
- package/dist/analysis/passes/insecure-cookie-pass.d.ts.map +1 -1
- package/dist/analysis/passes/insecure-cookie-pass.js +25 -3
- package/dist/analysis/passes/insecure-cookie-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts +11 -14
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +562 -0
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/language-sources-pass.d.ts +18 -1
- package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
- package/dist/analysis/passes/language-sources-pass.js +1079 -14
- package/dist/analysis/passes/language-sources-pass.js.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.d.ts.map +1 -1
- package/dist/analysis/passes/param-source-flow-gate-pass.js +82 -3
- package/dist/analysis/passes/param-source-flow-gate-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts +44 -2
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +690 -12
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +298 -19
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts +18 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.d.ts.map +1 -1
- package/dist/analysis/passes/tls-verify-disabled-pass.js +161 -0
- package/dist/analysis/passes/tls-verify-disabled-pass.js.map +1 -1
- package/dist/analysis/passes/weak-random-pass.d.ts +6 -0
- package/dist/analysis/passes/weak-random-pass.d.ts.map +1 -1
- package/dist/analysis/passes/weak-random-pass.js +29 -0
- package/dist/analysis/passes/weak-random-pass.js.map +1 -1
- package/dist/analysis/promote-cross-file-flows.d.ts +17 -0
- package/dist/analysis/promote-cross-file-flows.d.ts.map +1 -0
- package/dist/analysis/promote-cross-file-flows.js +32 -0
- package/dist/analysis/promote-cross-file-flows.js.map +1 -0
- package/dist/analysis/rust-literal-guard.d.ts.map +1 -1
- package/dist/analysis/rust-literal-guard.js +100 -1
- package/dist/analysis/rust-literal-guard.js.map +1 -1
- package/dist/analysis/sbom.d.ts +100 -0
- package/dist/analysis/sbom.d.ts.map +1 -1
- package/dist/analysis/sbom.js +514 -3
- package/dist/analysis/sbom.js.map +1 -1
- package/dist/analysis/semver-version.d.ts +30 -0
- package/dist/analysis/semver-version.d.ts.map +1 -0
- package/dist/analysis/semver-version.js +76 -0
- package/dist/analysis/semver-version.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +5 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +226 -4
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +57 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +69 -6
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +3360 -314
- package/dist/core/circle-ir-core.cjs +783 -220
- package/dist/core/circle-ir-core.js +783 -220
- package/dist/core/extractors/dfg.js +205 -2
- package/dist/core/extractors/dfg.js.map +1 -1
- package/dist/core/extractors/ts-declaration-detail.d.ts +39 -0
- package/dist/core/extractors/ts-declaration-detail.d.ts.map +1 -0
- package/dist/core/extractors/ts-declaration-detail.js +266 -0
- package/dist/core/extractors/ts-declaration-detail.js.map +1 -0
- package/dist/core/extractors/types.js +53 -9
- package/dist/core/extractors/types.js.map +1 -1
- package/dist/graph/analysis-pass.d.ts +4 -1
- package/dist/graph/analysis-pass.d.ts.map +1 -1
- package/dist/graph/analysis-pass.js +4 -1
- package/dist/graph/analysis-pass.js.map +1 -1
- package/dist/graph-facts/dead-code.d.ts +62 -0
- package/dist/graph-facts/dead-code.d.ts.map +1 -0
- package/dist/graph-facts/dead-code.js +90 -0
- package/dist/graph-facts/dead-code.js.map +1 -0
- package/dist/graph-facts/dependency-calls.d.ts +86 -0
- package/dist/graph-facts/dependency-calls.d.ts.map +1 -0
- package/dist/graph-facts/dependency-calls.js +93 -0
- package/dist/graph-facts/dependency-calls.js.map +1 -0
- package/dist/graph-facts/index.d.ts +10 -0
- package/dist/graph-facts/index.d.ts.map +1 -0
- package/dist/graph-facts/index.js +10 -0
- package/dist/graph-facts/index.js.map +1 -0
- package/dist/graph-facts/maven-packages.d.ts +42 -0
- package/dist/graph-facts/maven-packages.d.ts.map +1 -0
- package/dist/graph-facts/maven-packages.js +133 -0
- package/dist/graph-facts/maven-packages.js.map +1 -0
- package/dist/graph-facts/reach.d.ts +203 -0
- package/dist/graph-facts/reach.d.ts.map +1 -0
- package/dist/graph-facts/reach.js +219 -0
- package/dist/graph-facts/reach.js.map +1 -0
- package/dist/graph-facts/removal-reach.d.ts +73 -0
- package/dist/graph-facts/removal-reach.d.ts.map +1 -0
- package/dist/graph-facts/removal-reach.js +64 -0
- package/dist/graph-facts/removal-reach.js.map +1 -0
- package/dist/index.d.ts +21 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/navigation/go-graph.d.ts +68 -0
- package/dist/navigation/go-graph.d.ts.map +1 -0
- package/dist/navigation/go-graph.js +753 -0
- package/dist/navigation/go-graph.js.map +1 -0
- package/dist/navigation/index.d.ts +7 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +6 -0
- package/dist/navigation/index.js.map +1 -1
- package/dist/navigation/java-entry-tables.d.ts +43 -0
- package/dist/navigation/java-entry-tables.d.ts.map +1 -0
- package/dist/navigation/java-entry-tables.js +56 -0
- package/dist/navigation/java-entry-tables.js.map +1 -0
- package/dist/navigation/java-graph.d.ts +64 -0
- package/dist/navigation/java-graph.d.ts.map +1 -0
- package/dist/navigation/java-graph.js +466 -0
- package/dist/navigation/java-graph.js.map +1 -0
- package/dist/navigation/navigation-index.d.ts +91 -2
- package/dist/navigation/navigation-index.d.ts.map +1 -1
- package/dist/navigation/navigation-index.js +372 -45
- package/dist/navigation/navigation-index.js.map +1 -1
- package/dist/navigation/python-graph.d.ts +73 -0
- package/dist/navigation/python-graph.d.ts.map +1 -0
- package/dist/navigation/python-graph.js +867 -0
- package/dist/navigation/python-graph.js.map +1 -0
- package/dist/navigation/ts-call-edges.d.ts +193 -0
- package/dist/navigation/ts-call-edges.d.ts.map +1 -0
- package/dist/navigation/ts-call-edges.js +2089 -0
- package/dist/navigation/ts-call-edges.js.map +1 -0
- package/dist/navigation/ts-entry-tables.d.ts +88 -0
- package/dist/navigation/ts-entry-tables.d.ts.map +1 -0
- package/dist/navigation/ts-entry-tables.js +50 -0
- package/dist/navigation/ts-entry-tables.js.map +1 -0
- package/dist/path-set/branches.d.ts +52 -0
- package/dist/path-set/branches.d.ts.map +1 -0
- package/dist/path-set/branches.js +124 -0
- package/dist/path-set/branches.js.map +1 -0
- package/dist/path-set/finding-diff.d.ts +63 -0
- package/dist/path-set/finding-diff.d.ts.map +1 -0
- package/dist/path-set/finding-diff.js +150 -0
- package/dist/path-set/finding-diff.js.map +1 -0
- package/dist/path-set/index.d.ts +18 -0
- package/dist/path-set/index.d.ts.map +1 -0
- package/dist/path-set/index.js +12 -0
- package/dist/path-set/index.js.map +1 -0
- package/dist/path-set/line-diff.d.ts +19 -0
- package/dist/path-set/line-diff.d.ts.map +1 -0
- package/dist/path-set/line-diff.js +98 -0
- package/dist/path-set/line-diff.js.map +1 -0
- package/dist/path-set/path-set.d.ts +88 -0
- package/dist/path-set/path-set.d.ts.map +1 -0
- package/dist/path-set/path-set.js +716 -0
- package/dist/path-set/path-set.js.map +1 -0
- package/dist/path-set/templates.d.ts +42 -0
- package/dist/path-set/templates.d.ts.map +1 -0
- package/dist/path-set/templates.js +119 -0
- package/dist/path-set/templates.js.map +1 -0
- package/dist/path-set/types.d.ts +195 -0
- package/dist/path-set/types.d.ts.map +1 -0
- package/dist/path-set/types.js +8 -0
- package/dist/path-set/types.js.map +1 -0
- package/dist/remedy-class/index.d.ts +12 -0
- package/dist/remedy-class/index.d.ts.map +1 -0
- package/dist/remedy-class/index.js +9 -0
- package/dist/remedy-class/index.js.map +1 -0
- package/dist/remedy-class/match.d.ts +165 -0
- package/dist/remedy-class/match.d.ts.map +1 -0
- package/dist/remedy-class/match.js +705 -0
- package/dist/remedy-class/match.js.map +1 -0
- package/dist/remedy-class/output-context.d.ts +25 -0
- package/dist/remedy-class/output-context.d.ts.map +1 -0
- package/dist/remedy-class/output-context.js +150 -0
- package/dist/remedy-class/output-context.js.map +1 -0
- package/dist/remedy-class/sha256.d.ts +18 -0
- package/dist/remedy-class/sha256.d.ts.map +1 -0
- package/dist/remedy-class/sha256.js +89 -0
- package/dist/remedy-class/sha256.js.map +1 -0
- package/dist/remedy-class/table.d.ts +130 -0
- package/dist/remedy-class/table.d.ts.map +1 -0
- package/dist/remedy-class/table.js +457 -0
- package/dist/remedy-class/table.js.map +1 -0
- package/dist/resolution/cross-file.d.ts +18 -0
- package/dist/resolution/cross-file.d.ts.map +1 -1
- package/dist/resolution/cross-file.js +194 -5
- package/dist/resolution/cross-file.js.map +1 -1
- package/dist/stable-ids/builder.d.ts +20 -0
- package/dist/stable-ids/builder.d.ts.map +1 -0
- package/dist/stable-ids/builder.js +65 -0
- package/dist/stable-ids/builder.js.map +1 -0
- package/dist/stable-ids/diff.d.ts +18 -0
- package/dist/stable-ids/diff.d.ts.map +1 -0
- package/dist/stable-ids/diff.js +37 -0
- package/dist/stable-ids/diff.js.map +1 -0
- package/dist/stable-ids/go.d.ts +28 -0
- package/dist/stable-ids/go.d.ts.map +1 -0
- package/dist/stable-ids/go.js +160 -0
- package/dist/stable-ids/go.js.map +1 -0
- package/dist/stable-ids/hash.d.ts +22 -0
- package/dist/stable-ids/hash.d.ts.map +1 -0
- package/dist/stable-ids/hash.js +167 -0
- package/dist/stable-ids/hash.js.map +1 -0
- package/dist/stable-ids/index.d.ts +30 -0
- package/dist/stable-ids/index.d.ts.map +1 -0
- package/dist/stable-ids/index.js +59 -0
- package/dist/stable-ids/index.js.map +1 -0
- package/dist/stable-ids/java.d.ts +28 -0
- package/dist/stable-ids/java.d.ts.map +1 -0
- package/dist/stable-ids/java.js +224 -0
- package/dist/stable-ids/java.js.map +1 -0
- package/dist/stable-ids/moves.d.ts +87 -0
- package/dist/stable-ids/moves.d.ts.map +1 -0
- package/dist/stable-ids/moves.js +222 -0
- package/dist/stable-ids/moves.js.map +1 -0
- package/dist/stable-ids/nodes.d.ts +32 -0
- package/dist/stable-ids/nodes.d.ts.map +1 -0
- package/dist/stable-ids/nodes.js +92 -0
- package/dist/stable-ids/nodes.js.map +1 -0
- package/dist/stable-ids/normalize.d.ts +35 -0
- package/dist/stable-ids/normalize.d.ts.map +1 -0
- package/dist/stable-ids/normalize.js +174 -0
- package/dist/stable-ids/normalize.js.map +1 -0
- package/dist/stable-ids/parse-errors.d.ts +24 -0
- package/dist/stable-ids/parse-errors.d.ts.map +1 -0
- package/dist/stable-ids/parse-errors.js +46 -0
- package/dist/stable-ids/parse-errors.js.map +1 -0
- package/dist/stable-ids/python.d.ts +27 -0
- package/dist/stable-ids/python.d.ts.map +1 -0
- package/dist/stable-ids/python.js +212 -0
- package/dist/stable-ids/python.js.map +1 -0
- package/dist/stable-ids/ts-parse-errors.d.ts +27 -0
- package/dist/stable-ids/ts-parse-errors.d.ts.map +1 -0
- package/dist/stable-ids/ts-parse-errors.js +75 -0
- package/dist/stable-ids/ts-parse-errors.js.map +1 -0
- package/dist/stable-ids/types.d.ts +106 -0
- package/dist/stable-ids/types.d.ts.map +1 -0
- package/dist/stable-ids/types.js +16 -0
- package/dist/stable-ids/types.js.map +1 -0
- package/dist/stable-ids/typescript.d.ts +30 -0
- package/dist/stable-ids/typescript.d.ts.map +1 -0
- package/dist/stable-ids/typescript.js +362 -0
- package/dist/stable-ids/typescript.js.map +1 -0
- package/dist/types/config.d.ts +9 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/index.d.ts +53 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -20,6 +20,7 @@ import { javaCallArgument, javaCanonicalBaseLacksSeparator } from '../java-canon
|
|
|
20
20
|
import { findGoStringsReplacerSanitizers } from '../go-strings-replacer.js';
|
|
21
21
|
import { findRustLiteralEqualityGuardSanitizers } from '../rust-literal-guard.js';
|
|
22
22
|
import { constLookupTables, isConstTableLookup } from '../const-table-lookup.js';
|
|
23
|
+
import { jsReplaceChainVerdict } from '../js-crlf-strip.js';
|
|
23
24
|
// ---------------------------------------------------------------------------
|
|
24
25
|
// Pattern tables (moved from analyzer.ts)
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
@@ -298,6 +299,8 @@ export class LanguageSourcesPass {
|
|
|
298
299
|
}
|
|
299
300
|
additionalSanitizers.push(...findBashRegexAllowlistSanitizers(code));
|
|
300
301
|
additionalSanitizers.push(...findBashRealpathPrefixGuardSanitizers(code));
|
|
302
|
+
// #816 CVE-2026-8398: gpg/sha verify-then-install download scripts.
|
|
303
|
+
additionalSanitizers.push(...findBashVerifiedPackageDownloadSanitizers(code));
|
|
301
304
|
// #213 twelfth slice: printf '%q' + ${var@Q} shell-quote helpers.
|
|
302
305
|
additionalSanitizers.push(...findBashShellQuoteSanitizers(code));
|
|
303
306
|
}
|
|
@@ -417,8 +420,14 @@ export class LanguageSourcesPass {
|
|
|
417
420
|
}
|
|
418
421
|
// -- Rust: safe-handler sanitizer detectors (cognium-dev #115 Sprint 31) --
|
|
419
422
|
if (language === 'rust') {
|
|
423
|
+
additionalSanitizers.push(...findRustConstSliceMembershipGuardSanitizers(code));
|
|
420
424
|
additionalSanitizers.push(...findRustSetAllowlistGuardSanitizers(code));
|
|
421
425
|
additionalSanitizers.push(...findRustCanonicalizeGuardSanitizers(code));
|
|
426
|
+
// cognium-dev #816 — PAT-INPUT-01 rust safe mirror: parse + range guards
|
|
427
|
+
// before actix/axum response body (same credit model as JS twin).
|
|
428
|
+
additionalSanitizers.push(...findRustNumericRangeCheckGuardSanitizers(code));
|
|
429
|
+
// cognium-dev #816 — PAT-INFO-01: response echoes only query params, no env secrets.
|
|
430
|
+
additionalSanitizers.push(...findRustPatInfoHttpOnlyResponseSanitizers(code));
|
|
422
431
|
// cognium-dev#293: `if x != "a" && x != "b" { return Err(..) }` literal-equality guard.
|
|
423
432
|
additionalSanitizers.push(...findRustLiteralEqualityGuardSanitizers(code));
|
|
424
433
|
// Sprint 77a (#216 Pattern X): argv-form Command::new(literal).arg(...)
|
|
@@ -484,6 +493,9 @@ export class LanguageSourcesPass {
|
|
|
484
493
|
// #223 REG-144-17/18/19: JS/TS command allowlist guard
|
|
485
494
|
// (Set.has / Array.includes / indexOf) before execFile/spawn.
|
|
486
495
|
additionalSanitizers.push(...findJsCommandAllowlistGuardSanitizers(code));
|
|
496
|
+
// cognium-dev #816 — PAT-INPUT-01 js safe mirror: Number/parseInt
|
|
497
|
+
// coercion + finite/range guards before template response.
|
|
498
|
+
additionalSanitizers.push(...findJsNumericRangeCheckGuardSanitizers(code));
|
|
487
499
|
// Sprint 78 (#190): JS misconfig pattern findings — libxmljs noent:true.
|
|
488
500
|
for (const finding of findJsPatternFindings(code, graph.ir.meta.file)) {
|
|
489
501
|
ctx.addFinding(finding);
|
|
@@ -517,6 +529,9 @@ export class LanguageSourcesPass {
|
|
|
517
529
|
for (const finding of findJsLdapInjectionFindings(code, graph.ir.meta.file)) {
|
|
518
530
|
ctx.addFinding(finding);
|
|
519
531
|
}
|
|
532
|
+
for (const finding of findJsZipHeaderFailopenFindings(code, graph.ir.meta.file)) {
|
|
533
|
+
ctx.addFinding(finding);
|
|
534
|
+
}
|
|
520
535
|
// Sprint 89 (#189): JS insecure_deserialization —
|
|
521
536
|
// JSON.parse(req.body) on raw / text bodies.
|
|
522
537
|
for (const finding of findJsJsonParseBodyFindings(code, graph.ir.meta.file)) {
|
|
@@ -576,6 +591,9 @@ export class LanguageSourcesPass {
|
|
|
576
591
|
additionalSanitizers.push(...findJavaInlineCrlfStripLogSanitizers(code));
|
|
577
592
|
// cognium-dev#293: the same strip, but living in a user-defined helper.
|
|
578
593
|
additionalSanitizers.push(...findCrlfWrapperFunctionSanitizers(code, 'java'));
|
|
594
|
+
// cognium-dev#947: flow-sanitize ssti custom_wrapper — brace-stripping
|
|
595
|
+
// helper at Velocity.evaluate call site.
|
|
596
|
+
additionalSanitizers.push(...findJavaTemplateDelimiterWrapperSanitizers(code));
|
|
579
597
|
// Sprint 77a (#216 Pattern X): argv-form exec sanitizer.
|
|
580
598
|
additionalSanitizers.push(...findJavaArgvFormExecSanitizers(code));
|
|
581
599
|
// Sprint 78 (#190): Java misconfig pattern findings —
|
|
@@ -1701,6 +1719,9 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
1701
1719
|
// cognium-dev#638 step 3 — `box.value = req.QueryString["k"]` seeds the
|
|
1702
1720
|
// container local so cross-file calls that pass `box` carry taint.
|
|
1703
1721
|
const memberFieldAssignRe = /^\s*([A-Za-z_]\w*)\.([A-Za-z_]\w*)\s*=\s*(.+?);?\s*$/;
|
|
1722
|
+
// cognium-dev#638 step 5 — `list.Add(req.QueryString["k"])` seeds the list
|
|
1723
|
+
// local so cross-file calls that pass `list` carry taint into callee reads.
|
|
1724
|
+
const collectionAddRe = /^\s*([A-Za-z_]\w*)\s*\.\s*Add\s*\(\s*(.+?)\s*\)\s*;?\s*$/;
|
|
1704
1725
|
// `Request.*` is the inherited controller member. A parameter typed
|
|
1705
1726
|
// `HttpRequest` / `HttpRequestBase` is the same surface under whatever
|
|
1706
1727
|
// name the method uses (`req` in Juliet) — cognium-dev#501.
|
|
@@ -1869,6 +1890,25 @@ function findCSharpRequestSources(sourceCode, language) {
|
|
|
1869
1890
|
continue;
|
|
1870
1891
|
}
|
|
1871
1892
|
}
|
|
1893
|
+
const addEl = collectionAddRe.exec(stmt);
|
|
1894
|
+
if (addEl) {
|
|
1895
|
+
const [, baseName, rhs] = addEl;
|
|
1896
|
+
const type = classify(rhs);
|
|
1897
|
+
if (type && baseName) {
|
|
1898
|
+
const lineNumber = i + 1;
|
|
1899
|
+
if (!sources.some(s => s.line === lineNumber && s.variable === baseName)) {
|
|
1900
|
+
sources.push({
|
|
1901
|
+
type,
|
|
1902
|
+
location: `${baseName}.Add(${rhs.trim().substring(0, 50)}${rhs.length > 50 ? '...' : ''})`,
|
|
1903
|
+
severity: type === 'db_input' ? 'medium' : 'high',
|
|
1904
|
+
line: lineNumber,
|
|
1905
|
+
confidence: 1.0,
|
|
1906
|
+
variable: baseName,
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
continue;
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1872
1912
|
const m = assignRe.exec(stmt);
|
|
1873
1913
|
// cognium-dev#502 — `assignRe` is anchored and greedy, so on a one-line
|
|
1874
1914
|
// block header that also contains the real assignment
|
|
@@ -2829,6 +2869,171 @@ function computeDeadPythonBranchLines(lines) {
|
|
|
2829
2869
|
}
|
|
2830
2870
|
return dead;
|
|
2831
2871
|
}
|
|
2872
|
+
/**
|
|
2873
|
+
* Line indices (0-based) in unreachable `if`/`else` arms when the condition is
|
|
2874
|
+
* decidable from tracked integer locals (`int num = 86; if ((7*42)-num > 200) …`).
|
|
2875
|
+
*
|
|
2876
|
+
* Targets OWASP Benchmark shapes where the else arm assigns a parameter but
|
|
2877
|
+
* can never run (cognium-dev#913). Braced bodies defer to constant propagation;
|
|
2878
|
+
* this pass handles the common single-statement `if` / `else` lines.
|
|
2879
|
+
*/
|
|
2880
|
+
function parseJavaSwitchCaseLabel(label) {
|
|
2881
|
+
const t = label.trim();
|
|
2882
|
+
if (t === 'default')
|
|
2883
|
+
return 'default';
|
|
2884
|
+
const ch = t.match(/^'(\\.|[^'])'\s*$/);
|
|
2885
|
+
if (ch) {
|
|
2886
|
+
const inner = ch[1];
|
|
2887
|
+
if (inner.startsWith('\\')) {
|
|
2888
|
+
if (inner === "\\'")
|
|
2889
|
+
return 39;
|
|
2890
|
+
if (inner === '\\\\')
|
|
2891
|
+
return 92;
|
|
2892
|
+
if (inner === '\\n')
|
|
2893
|
+
return 10;
|
|
2894
|
+
return undefined;
|
|
2895
|
+
}
|
|
2896
|
+
return inner.charCodeAt(0);
|
|
2897
|
+
}
|
|
2898
|
+
const num = t.match(/^(-?\d+)\s*$/);
|
|
2899
|
+
if (num)
|
|
2900
|
+
return Number(num[1]);
|
|
2901
|
+
return undefined;
|
|
2902
|
+
}
|
|
2903
|
+
function parseJavaSwitchLabelLine(line) {
|
|
2904
|
+
const caseLine = line.match(/^\s*case\s+(.+?)\s*:\s*$/);
|
|
2905
|
+
if (caseLine)
|
|
2906
|
+
return parseJavaSwitchCaseLabel(caseLine[1]);
|
|
2907
|
+
if (/^\s*default\s*:\s*$/.test(line))
|
|
2908
|
+
return 'default';
|
|
2909
|
+
return undefined;
|
|
2910
|
+
}
|
|
2911
|
+
function markDeadJavaSwitchBodies(lines, switchLineIdx, switchValue, dead) {
|
|
2912
|
+
let depth = 0;
|
|
2913
|
+
let started = false;
|
|
2914
|
+
let groupLabels = [];
|
|
2915
|
+
let bodyStart = -1;
|
|
2916
|
+
let explicitCaseMatched = false;
|
|
2917
|
+
const groupIsTaken = () => {
|
|
2918
|
+
if (groupLabels.length === 0)
|
|
2919
|
+
return false;
|
|
2920
|
+
const explicit = groupLabels.some(v => v !== 'default' && v === switchValue);
|
|
2921
|
+
if (explicit)
|
|
2922
|
+
return true;
|
|
2923
|
+
return groupLabels.includes('default') && !explicitCaseMatched;
|
|
2924
|
+
};
|
|
2925
|
+
const flushGroup = (bodyEnd) => {
|
|
2926
|
+
if (bodyStart < 0 || groupLabels.length === 0)
|
|
2927
|
+
return;
|
|
2928
|
+
const taken = groupIsTaken();
|
|
2929
|
+
if (groupLabels.some(v => v !== 'default' && v === switchValue)) {
|
|
2930
|
+
explicitCaseMatched = true;
|
|
2931
|
+
}
|
|
2932
|
+
if (!taken) {
|
|
2933
|
+
for (let k = bodyStart; k < bodyEnd; k++)
|
|
2934
|
+
dead.add(k);
|
|
2935
|
+
}
|
|
2936
|
+
groupLabels = [];
|
|
2937
|
+
bodyStart = -1;
|
|
2938
|
+
};
|
|
2939
|
+
for (let j = switchLineIdx; j < lines.length; j++) {
|
|
2940
|
+
const line = lines[j];
|
|
2941
|
+
for (const ch of line) {
|
|
2942
|
+
if (ch === '{') {
|
|
2943
|
+
depth++;
|
|
2944
|
+
started = true;
|
|
2945
|
+
}
|
|
2946
|
+
else if (ch === '}')
|
|
2947
|
+
depth--;
|
|
2948
|
+
}
|
|
2949
|
+
if (!started)
|
|
2950
|
+
continue;
|
|
2951
|
+
const label = parseJavaSwitchLabelLine(line);
|
|
2952
|
+
if (label !== undefined) {
|
|
2953
|
+
if (bodyStart >= 0 && j === bodyStart) {
|
|
2954
|
+
groupLabels.push(label);
|
|
2955
|
+
bodyStart = j + 1;
|
|
2956
|
+
}
|
|
2957
|
+
else {
|
|
2958
|
+
flushGroup(j);
|
|
2959
|
+
groupLabels = [label];
|
|
2960
|
+
bodyStart = j + 1;
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
if (started && depth === 0) {
|
|
2964
|
+
flushGroup(j);
|
|
2965
|
+
break;
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
export function computeDeadJavaBranchLines(lines) {
|
|
2970
|
+
const dead = new Set();
|
|
2971
|
+
const consts = new Map();
|
|
2972
|
+
const stringConsts = new Map();
|
|
2973
|
+
const charConsts = new Map();
|
|
2974
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2975
|
+
const line = lines[i];
|
|
2976
|
+
const stringAssign = line.match(/^\s*String\s+([A-Za-z_]\w*)\s*=\s*"([^"]*)"\s*;\s*$/);
|
|
2977
|
+
if (stringAssign) {
|
|
2978
|
+
stringConsts.set(stringAssign[1], stringAssign[2]);
|
|
2979
|
+
continue;
|
|
2980
|
+
}
|
|
2981
|
+
const charAtAssign = line.match(/^\s*char\s+([A-Za-z_]\w*)\s*=\s*([A-Za-z_]\w*)\.charAt\s*\(\s*(\d+)\s*\)\s*;\s*(?:\/\/.*)?$/);
|
|
2982
|
+
if (charAtAssign) {
|
|
2983
|
+
const str = stringConsts.get(charAtAssign[2]);
|
|
2984
|
+
const idx = Number(charAtAssign[3]);
|
|
2985
|
+
if (str !== undefined && idx >= 0 && idx < str.length) {
|
|
2986
|
+
charConsts.set(charAtAssign[1], str.charCodeAt(idx));
|
|
2987
|
+
}
|
|
2988
|
+
else {
|
|
2989
|
+
charConsts.delete(charAtAssign[1]);
|
|
2990
|
+
}
|
|
2991
|
+
continue;
|
|
2992
|
+
}
|
|
2993
|
+
const intAssign = line.match(/^\s*(?:int|long|short|byte)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/);
|
|
2994
|
+
if (intAssign) {
|
|
2995
|
+
const v = evaluatePythonConstExpression(intAssign[2].trim(), consts);
|
|
2996
|
+
if (typeof v === 'number')
|
|
2997
|
+
consts.set(intAssign[1], v);
|
|
2998
|
+
else
|
|
2999
|
+
consts.delete(intAssign[1]);
|
|
3000
|
+
continue;
|
|
3001
|
+
}
|
|
3002
|
+
const switchMatch = line.match(/^\s*switch\s*\(([^)]+)\)\s*\{?\s*$/);
|
|
3003
|
+
if (switchMatch) {
|
|
3004
|
+
const sel = switchMatch[1].trim();
|
|
3005
|
+
let switchVal = charConsts.get(sel);
|
|
3006
|
+
if (switchVal === undefined) {
|
|
3007
|
+
const n = evaluatePythonConstExpression(sel, consts);
|
|
3008
|
+
if (typeof n === 'number')
|
|
3009
|
+
switchVal = n;
|
|
3010
|
+
}
|
|
3011
|
+
if (switchVal !== undefined) {
|
|
3012
|
+
markDeadJavaSwitchBodies(lines, i, switchVal, dead);
|
|
3013
|
+
}
|
|
3014
|
+
continue;
|
|
3015
|
+
}
|
|
3016
|
+
const ifMatch = line.match(/^(\s*)if\s*\((.+)\)\s+(.+)$/);
|
|
3017
|
+
if (!ifMatch)
|
|
3018
|
+
continue;
|
|
3019
|
+
const decided = evaluatePythonConstExpression(ifMatch[2].trim(), consts);
|
|
3020
|
+
if (decided !== true && decided !== false)
|
|
3021
|
+
continue;
|
|
3022
|
+
const rest = ifMatch[3].trim();
|
|
3023
|
+
if (/\belse\b/.test(rest))
|
|
3024
|
+
continue;
|
|
3025
|
+
if (i + 1 < lines.length && /^\s*else\b/.test(lines[i + 1])) {
|
|
3026
|
+
if (decided === true)
|
|
3027
|
+
dead.add(i + 1);
|
|
3028
|
+
else
|
|
3029
|
+
dead.add(i);
|
|
3030
|
+
}
|
|
3031
|
+
else if (decided === false) {
|
|
3032
|
+
dead.add(i);
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
return dead;
|
|
3036
|
+
}
|
|
2832
3037
|
export function buildPythonTaintedVars(sourceCode) {
|
|
2833
3038
|
const tainted = new Map();
|
|
2834
3039
|
const containerTainted = new Map();
|
|
@@ -3894,6 +4099,113 @@ export function buildRustTaintedVars(sourceCode, seedVars) {
|
|
|
3894
4099
|
* on the way (a lambda, anonymous class, switch expression or block is not a
|
|
3895
4100
|
* plain wrapped value).
|
|
3896
4101
|
*/
|
|
4102
|
+
/** `code(); // note` → `code();` — a `//` inside a string or char literal is kept. */
|
|
4103
|
+
function stripJavaTrailingLineComment(line) {
|
|
4104
|
+
let quote = null;
|
|
4105
|
+
for (let i = 0; i < line.length; i++) {
|
|
4106
|
+
const c = line[i];
|
|
4107
|
+
if (quote) {
|
|
4108
|
+
if (c === '\\')
|
|
4109
|
+
i++;
|
|
4110
|
+
else if (c === quote)
|
|
4111
|
+
quote = null;
|
|
4112
|
+
}
|
|
4113
|
+
else if (c === '"' || c === "'") {
|
|
4114
|
+
quote = c;
|
|
4115
|
+
}
|
|
4116
|
+
else if (c === '/' && line[i + 1] === '/') {
|
|
4117
|
+
return line.slice(0, i).trimEnd();
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
return line;
|
|
4121
|
+
}
|
|
4122
|
+
/** Identifiers at code positions in a Java expression (string and char literal bodies dropped). */
|
|
4123
|
+
function javaExpressionIdents(expr) {
|
|
4124
|
+
const code = expr.replace(/"(?:[^"\\]|\\.)*"/g, ' ').replace(/'(?:[^'\\]|\\.)'/g, ' ');
|
|
4125
|
+
return code.match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
4126
|
+
}
|
|
4127
|
+
/**
|
|
4128
|
+
* `buildJavaTaintedVars` slot tracking (cognium-dev#913/#914/#916/#917). Records
|
|
4129
|
+
* `list.add(x)` / `list.remove(<int>)` / `map.put("<key>", x)` into the slot
|
|
4130
|
+
* tables and returns true for those statements. An `int` constant declaration
|
|
4131
|
+
* is recorded for ternary folding but returns false, so the line is still read
|
|
4132
|
+
* as an assignment.
|
|
4133
|
+
*/
|
|
4134
|
+
function recordJavaSlotStatement(line, listElems, mapSlots, intConsts) {
|
|
4135
|
+
const t = line.trimEnd().replace(/;\s*\/\/[^"]*$/, ';');
|
|
4136
|
+
const intDecl = /^\s*(?:final\s+)?(?:int|long|short|byte)\s+([A-Za-z_]\w*)\s*=\s*([^;]+);\s*$/.exec(t);
|
|
4137
|
+
if (intDecl) {
|
|
4138
|
+
const v = evaluatePythonConstExpression(intDecl[2].trim(), intConsts);
|
|
4139
|
+
if (typeof v === 'number')
|
|
4140
|
+
intConsts.set(intDecl[1], v);
|
|
4141
|
+
else
|
|
4142
|
+
intConsts.delete(intDecl[1]);
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
const add = /^\s*([A-Za-z_]\w*)\.add\s*\(\s*([A-Za-z_]\w*|"(?:[^"\\]|\\.)*")\s*\)\s*;\s*$/.exec(t);
|
|
4146
|
+
if (add) {
|
|
4147
|
+
const elems = listElems.get(add[1]) ?? [];
|
|
4148
|
+
elems.push(add[2].startsWith('"') ? null : add[2]);
|
|
4149
|
+
listElems.set(add[1], elems);
|
|
4150
|
+
return true;
|
|
4151
|
+
}
|
|
4152
|
+
const remove = /^\s*([A-Za-z_]\w*)\.remove\s*\(\s*(\d+)\s*\)\s*;\s*$/.exec(t);
|
|
4153
|
+
if (remove && listElems.has(remove[1])) {
|
|
4154
|
+
const elems = listElems.get(remove[1]);
|
|
4155
|
+
const idx = Number(remove[2]);
|
|
4156
|
+
if (idx < elems.length)
|
|
4157
|
+
elems.splice(idx, 1);
|
|
4158
|
+
else
|
|
4159
|
+
listElems.delete(remove[1]); // an index we did not see added: stop trusting the slots
|
|
4160
|
+
return true;
|
|
4161
|
+
}
|
|
4162
|
+
const put = /^\s*([A-Za-z_]\w*)\.put\s*\(\s*"((?:[^"\\]|\\.)*)"\s*,\s*(.+?)\s*\)\s*;\s*$/.exec(t);
|
|
4163
|
+
if (put) {
|
|
4164
|
+
const keys = mapSlots.get(put[1]) ?? new Map();
|
|
4165
|
+
keys.set(put[2], javaExpressionIdents(put[3]));
|
|
4166
|
+
mapSlots.set(put[1], keys);
|
|
4167
|
+
return true;
|
|
4168
|
+
}
|
|
4169
|
+
// Any other use of a tracked collection that is not a plain read (passed to a
|
|
4170
|
+
// call, `addAll`, `set`, `clear`, a non-literal `put`, re-declared …) may change
|
|
4171
|
+
// its contents in a way the tables do not model: stop trusting its slots.
|
|
4172
|
+
for (const table of [listElems, mapSlots]) {
|
|
4173
|
+
for (const name of [...table.keys()]) {
|
|
4174
|
+
const mention = new RegExp(`(?<![\\w.])${name}(?!\\w)`);
|
|
4175
|
+
if (!mention.test(t))
|
|
4176
|
+
continue;
|
|
4177
|
+
const reads = new RegExp(`(?<![\\w.])${name}\\.(?:get|size|isEmpty|contains\\w*)\\s*\\(`, 'g');
|
|
4178
|
+
if (mention.test(t.replace(reads, ' ')))
|
|
4179
|
+
table.delete(name);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
return false;
|
|
4183
|
+
}
|
|
4184
|
+
/**
|
|
4185
|
+
* The identifiers a `list.get(<int>)` / `map.get("<key>")` read (optionally
|
|
4186
|
+
* cast) can carry, from the slot tables; `undefined` when the RHS is not such a
|
|
4187
|
+
* read or the slots are unknown, so the caller falls back to whole-RHS matching.
|
|
4188
|
+
*/
|
|
4189
|
+
function javaSlotRead(rhs, listElems, mapSlots) {
|
|
4190
|
+
const m = /^(?:\(\s*[A-Za-z_][\w.<>]*\s*\)\s*)?([A-Za-z_]\w*)\.get\s*\(\s*(\d+|"(?:[^"\\]|\\.)*")\s*\)$/.exec(rhs.trim());
|
|
4191
|
+
if (!m)
|
|
4192
|
+
return undefined;
|
|
4193
|
+
const [, recv, arg] = m;
|
|
4194
|
+
if (arg.startsWith('"')) {
|
|
4195
|
+
const keys = mapSlots.get(recv);
|
|
4196
|
+
if (!keys)
|
|
4197
|
+
return undefined;
|
|
4198
|
+
return keys.get(arg.slice(1, -1)) ?? [];
|
|
4199
|
+
}
|
|
4200
|
+
const elems = listElems.get(recv);
|
|
4201
|
+
if (!elems)
|
|
4202
|
+
return undefined;
|
|
4203
|
+
const idx = Number(arg);
|
|
4204
|
+
if (idx >= elems.length)
|
|
4205
|
+
return undefined;
|
|
4206
|
+
const slot = elems[idx];
|
|
4207
|
+
return slot === null ? [] : [slot];
|
|
4208
|
+
}
|
|
3897
4209
|
function joinWrappedJavaStatement(lines, i) {
|
|
3898
4210
|
const strip = (l) => l.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)'/g, '""').replace(/\/\/.*$/, '');
|
|
3899
4211
|
const first = strip(lines[i]);
|
|
@@ -3940,10 +4252,30 @@ function joinWrappedJavaStatement(lines, i) {
|
|
|
3940
4252
|
* consumed by `detectExpressionScanFlows` in `taint-propagation-pass.ts`
|
|
3941
4253
|
* to synthesize a virtual source for each derived var.
|
|
3942
4254
|
*/
|
|
3943
|
-
export function buildJavaTaintedVars(sourceCode, seedVars
|
|
4255
|
+
export function buildJavaTaintedVars(sourceCode, seedVars, deadBranchLines,
|
|
4256
|
+
/**
|
|
4257
|
+
* `trackSlots` (cognium-dev#913/#914/#916/#917): follow which element of a list
|
|
4258
|
+
* and which literal key of a map holds taint, and pick the branch of a ternary
|
|
4259
|
+
* whose condition folds to a constant int comparison. Only the helper-return
|
|
4260
|
+
* gate in InterproceduralPass asks for it; every other caller keeps the
|
|
4261
|
+
* whole-collection behaviour.
|
|
4262
|
+
*/
|
|
4263
|
+
options) {
|
|
3944
4264
|
const derived = new Map();
|
|
3945
4265
|
const knownTainted = new Set(seedVars);
|
|
3946
|
-
const
|
|
4266
|
+
const trackSlots = options?.trackSlots === true;
|
|
4267
|
+
// Slot mode also reads `x = list.get(0); // note` as the statement it is: the
|
|
4268
|
+
// assignment patterns below are anchored at `;`.
|
|
4269
|
+
const lines = trackSlots
|
|
4270
|
+
? sourceCode.split('\n').map(stripJavaTrailingLineComment)
|
|
4271
|
+
: sourceCode.split('\n');
|
|
4272
|
+
const dead = deadBranchLines ?? new Set();
|
|
4273
|
+
/** list var → element slots: the added identifier, or `null` for a literal. */
|
|
4274
|
+
const listElems = new Map();
|
|
4275
|
+
/** map var → literal key → the identifiers its stored value mentions. */
|
|
4276
|
+
const mapSlots = new Map();
|
|
4277
|
+
/** int locals with a constant value, for folding a ternary condition. */
|
|
4278
|
+
const intConsts = new Map();
|
|
3947
4279
|
// Declaration: [modifiers] Type[<...>] [ ] lhs = rhs;
|
|
3948
4280
|
// Simple assignment: lhs = rhs;
|
|
3949
4281
|
// The declaration form is tried first because a Type token in front
|
|
@@ -3958,6 +4290,8 @@ export function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
3958
4290
|
// original var directly.
|
|
3959
4291
|
const declRe = /^\s*(?:public|private|protected|static|final|volatile|transient|\s)*\s*(?:[A-Za-z_][\w.]*(?:\s*<[^>]*>)?(?:\s*\[\s*\])*)\s+([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
3960
4292
|
const assignRe = /^\s*([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
4293
|
+
const thisFieldAssignRe = /^\s*this\.([A-Za-z_]\w*)\s*=\s*(.+?);\s*$/;
|
|
4294
|
+
const beanSetterCallRe = /\bset([A-Z]\w*)\s*\((.*)\)\s*;?\s*$/;
|
|
3961
4295
|
const JAVA_KEYWORDS = new Set([
|
|
3962
4296
|
'if', 'else', 'while', 'for', 'do', 'switch', 'case', 'return',
|
|
3963
4297
|
'throw', 'try', 'catch', 'finally', 'new', 'this', 'super',
|
|
@@ -3981,11 +4315,28 @@ export function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
3981
4315
|
while (changed && guard < lines.length + 2) {
|
|
3982
4316
|
changed = false;
|
|
3983
4317
|
guard++;
|
|
4318
|
+
// Slot state is rebuilt on every pass: it describes the code top to bottom,
|
|
4319
|
+
// while `knownTainted` only grows.
|
|
4320
|
+
listElems.clear();
|
|
4321
|
+
mapSlots.clear();
|
|
4322
|
+
intConsts.clear();
|
|
3984
4323
|
for (let i = 0; i < lines.length; i++) {
|
|
4324
|
+
if (deadBranchLines !== undefined && dead.has(i))
|
|
4325
|
+
continue;
|
|
3985
4326
|
const line = lines[i];
|
|
3986
4327
|
const trimmed = line.trimStart();
|
|
3987
4328
|
if (trimmed.startsWith('//') || trimmed.startsWith('*'))
|
|
3988
4329
|
continue;
|
|
4330
|
+
if (trackSlots && recordJavaSlotStatement(line, listElems, mapSlots, intConsts)) {
|
|
4331
|
+
// `list.add(x)` still taints the list as a whole, as below.
|
|
4332
|
+
const add = /^\s*([A-Za-z_]\w*)\.add\s*\(\s*([A-Za-z_]\w*)\s*\)\s*;/.exec(line);
|
|
4333
|
+
if (add && knownTainted.has(add[2]) && !knownTainted.has(add[1])) {
|
|
4334
|
+
derived.set(add[1], i + 1);
|
|
4335
|
+
knownTainted.add(add[1]);
|
|
4336
|
+
changed = true;
|
|
4337
|
+
}
|
|
4338
|
+
continue;
|
|
4339
|
+
}
|
|
3989
4340
|
// cognium-dev#476: non-assignment statements (must run before the #753
|
|
3990
4341
|
// wrap-join early-continue, which would skip them).
|
|
3991
4342
|
const arrayCopyRe = /\bSystem\.arraycopy\s*\(\s*([A-Za-z_]\w*)\s*,\s*[^,]+\s*,\s*([A-Za-z_]\w*)\s*,/;
|
|
@@ -4020,21 +4371,92 @@ export function buildJavaTaintedVars(sourceCode, seedVars) {
|
|
|
4020
4371
|
// statement whose call argument list wraps over several lines reads like
|
|
4021
4372
|
// the same statement on one line.
|
|
4022
4373
|
const joined = joinWrappedJavaStatement(lines, i);
|
|
4023
|
-
if (joined
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4374
|
+
if (joined !== null) {
|
|
4375
|
+
statement = joined;
|
|
4376
|
+
declMatch = declRe.exec(statement);
|
|
4377
|
+
assignMatch = !declMatch ? assignRe.exec(statement) : null;
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
const thisFieldMatch = thisFieldAssignRe.exec(statement.trim());
|
|
4381
|
+
if (thisFieldMatch) {
|
|
4382
|
+
const fieldName = thisFieldMatch[1];
|
|
4383
|
+
const rhs = thisFieldMatch[2];
|
|
4384
|
+
const rhsCode = rhs
|
|
4385
|
+
.replace(/\$@?"(?:[^"\\]|\\.)*"/g, lit => ' ' + [...lit.matchAll(/\{([^}]*)\}/g)].map(x => x[1]).join(' ') + ' ')
|
|
4386
|
+
.replace(/@?"(?:[^"\\]|\\.)*"/g, ' ')
|
|
4387
|
+
.replace(/'(?:[^'\\]|\\.)'/g, ' ');
|
|
4388
|
+
const rhsIdents = rhsCode.match(/[\p{L}\p{N}_]+/gu);
|
|
4389
|
+
const ref = (rhsIdents !== null && rhsIdents.some(t => knownTainted.has(t))) ||
|
|
4390
|
+
(complexTaintedRes.length > 0 && complexTaintedRes.some(re => re.test(rhsCode)));
|
|
4391
|
+
if (ref) {
|
|
4392
|
+
const thisField = `this.${fieldName}`;
|
|
4393
|
+
if (!knownTainted.has(thisField)) {
|
|
4394
|
+
derived.set(thisField, i + 1);
|
|
4395
|
+
knownTainted.add(thisField);
|
|
4396
|
+
changed = true;
|
|
4397
|
+
}
|
|
4398
|
+
if (!knownTainted.has(fieldName)) {
|
|
4399
|
+
derived.set(fieldName, i + 1);
|
|
4400
|
+
knownTainted.add(fieldName);
|
|
4401
|
+
changed = true;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
continue;
|
|
4405
|
+
}
|
|
4406
|
+
const setterCall = beanSetterCallRe.exec(statement);
|
|
4407
|
+
if (setterCall) {
|
|
4408
|
+
const fieldName = setterCall[1].charAt(0).toLowerCase() + setterCall[1].slice(1);
|
|
4409
|
+
const arg = setterCall[2];
|
|
4410
|
+
const rhsCode = arg
|
|
4411
|
+
.replace(/\$@?"(?:[^"\\]|\\.)*"/g, lit => ' ' + [...lit.matchAll(/\{([^}]*)\}/g)].map(x => x[1]).join(' ') + ' ')
|
|
4412
|
+
.replace(/@?"(?:[^"\\]|\\.)*"/g, ' ')
|
|
4413
|
+
.replace(/'(?:[^'\\]|\\.)'/g, ' ');
|
|
4414
|
+
const rhsIdents = rhsCode.match(/[\p{L}\p{N}_]+/gu);
|
|
4415
|
+
const ref = (rhsIdents !== null && rhsIdents.some(t => knownTainted.has(t))) ||
|
|
4416
|
+
(complexTaintedRes.length > 0 && complexTaintedRes.some(re => re.test(rhsCode)));
|
|
4417
|
+
if (ref) {
|
|
4418
|
+
const thisField = `this.${fieldName}`;
|
|
4419
|
+
if (!knownTainted.has(thisField)) {
|
|
4420
|
+
derived.set(thisField, i + 1);
|
|
4421
|
+
knownTainted.add(thisField);
|
|
4422
|
+
changed = true;
|
|
4423
|
+
}
|
|
4424
|
+
if (!knownTainted.has(fieldName)) {
|
|
4425
|
+
derived.set(fieldName, i + 1);
|
|
4426
|
+
knownTainted.add(fieldName);
|
|
4427
|
+
changed = true;
|
|
4428
|
+
}
|
|
4429
|
+
}
|
|
4430
|
+
continue;
|
|
4028
4431
|
}
|
|
4029
4432
|
const m = declMatch ?? assignMatch;
|
|
4030
4433
|
if (!m)
|
|
4031
4434
|
continue;
|
|
4032
4435
|
const lhs = m[1];
|
|
4033
|
-
|
|
4436
|
+
let rhs = m[2];
|
|
4034
4437
|
if (JAVA_KEYWORDS.has(lhs))
|
|
4035
4438
|
continue;
|
|
4036
4439
|
if (knownTainted.has(lhs))
|
|
4037
4440
|
continue;
|
|
4441
|
+
if (trackSlots) {
|
|
4442
|
+
const slot = javaSlotRead(rhs, listElems, mapSlots);
|
|
4443
|
+
if (slot !== undefined) {
|
|
4444
|
+
if (slot.some(t => knownTainted.has(t))) {
|
|
4445
|
+
derived.set(lhs, i + 1);
|
|
4446
|
+
knownTainted.add(lhs);
|
|
4447
|
+
changed = true;
|
|
4448
|
+
}
|
|
4449
|
+
continue;
|
|
4450
|
+
}
|
|
4451
|
+
const tern = /^([^?]+?)\s*\?\s*([^:]+?)\s*:\s*(.+)$/.exec(rhs.trim());
|
|
4452
|
+
if (tern) {
|
|
4453
|
+
const decided = evaluatePythonConstExpression(tern[1].trim().replace(/^\((.*)\)$/, '$1'), intConsts);
|
|
4454
|
+
if (decided === true)
|
|
4455
|
+
rhs = tern[2];
|
|
4456
|
+
else if (decided === false)
|
|
4457
|
+
rhs = tern[3];
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4038
4460
|
// Reduce the RHS to code positions before matching a tainted var name:
|
|
4039
4461
|
// keep `$"…{expr}"` interpolation contents, drop plain / verbatim string
|
|
4040
4462
|
// literal bodies and char literals. Otherwise a tainted var NAME that
|
|
@@ -4130,8 +4552,15 @@ function findBashTaintSources(sourceCode, dfg) {
|
|
|
4130
4552
|
// Suppressed inside function bodies (#73 part 1).
|
|
4131
4553
|
if (!insideFunction) {
|
|
4132
4554
|
const positionalRe = /\$([1-9@*])|\$\{([1-9@*])\}/g;
|
|
4555
|
+
// A one-line definition `name() { …; }` keeps its body on the header
|
|
4556
|
+
// line: only text before the opening brace is at the top level.
|
|
4557
|
+
const header = /^\s*(?:function\s+)?[A-Za-z_][\w-]*\s*(?:\(\s*\))?\s*\{/.exec(line);
|
|
4558
|
+
const isFnHeader = header !== null && (/\(\s*\)/.test(header[0]) || /^\s*function\s/.test(header[0]));
|
|
4559
|
+
// `$1` inside single quotes is literal text (an awk or sed program), not
|
|
4560
|
+
// a positional parameter.
|
|
4561
|
+
const topLevelText = (isFnHeader ? line.slice(0, header[0].length) : line).replace(/'[^']*'/g, "''");
|
|
4133
4562
|
let m;
|
|
4134
|
-
while ((m = positionalRe.exec(
|
|
4563
|
+
while ((m = positionalRe.exec(topLevelText)) !== null) {
|
|
4135
4564
|
const param = m[1] ?? m[2];
|
|
4136
4565
|
const alreadyExists = sources.some(s => s.line === lineNumber && s.variable === param);
|
|
4137
4566
|
if (!alreadyExists) {
|
|
@@ -4871,6 +5300,39 @@ function findBashRealpathPrefixGuardSanitizers(code) {
|
|
|
4871
5300
|
}
|
|
4872
5301
|
return sanitizers;
|
|
4873
5302
|
}
|
|
5303
|
+
/**
|
|
5304
|
+
* cognium-dev #816 (CVE-2026-8398 `daemon_pkg_install` safe mirror): when a
|
|
5305
|
+
* script downloads a package artifact with curl/wget and later verifies
|
|
5306
|
+
* integrity (gpg/sha) before `dpkg`/`rpm` install, credit the download
|
|
5307
|
+
* step's outbound fetch as gated — same whole-script idiom as
|
|
5308
|
+
* `hasIntegrityVerifierAnywhere` for `unverified-package-install`.
|
|
5309
|
+
*/
|
|
5310
|
+
function findBashVerifiedPackageDownloadSanitizers(code) {
|
|
5311
|
+
const lines = code.split('\n');
|
|
5312
|
+
if (!hasIntegrityVerifierAnywhere(lines)) {
|
|
5313
|
+
return [];
|
|
5314
|
+
}
|
|
5315
|
+
const hasDownload = lines.some((l) => /\b(curl|wget)\b/.test(l));
|
|
5316
|
+
const hasInstall = lines.some((l) => matchUnverifiedPackageInstall(l.trim()) !== null);
|
|
5317
|
+
if (!hasDownload || !hasInstall) {
|
|
5318
|
+
return [];
|
|
5319
|
+
}
|
|
5320
|
+
const sanitizers = [];
|
|
5321
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5322
|
+
const line = lines[i];
|
|
5323
|
+
if (line.trimStart().startsWith('#'))
|
|
5324
|
+
continue;
|
|
5325
|
+
if (!/\b(curl|wget)\b/.test(line))
|
|
5326
|
+
continue;
|
|
5327
|
+
sanitizers.push({
|
|
5328
|
+
type: 'verified_package_download',
|
|
5329
|
+
method: 'curl',
|
|
5330
|
+
line: i + 1,
|
|
5331
|
+
sanitizes: ['ssrf'],
|
|
5332
|
+
});
|
|
5333
|
+
}
|
|
5334
|
+
return sanitizers;
|
|
5335
|
+
}
|
|
4874
5336
|
// ---------------------------------------------------------------------------
|
|
4875
5337
|
// Go safe-handler sanitizer detectors (cognium-dev #102 Sprint 24)
|
|
4876
5338
|
// ---------------------------------------------------------------------------
|
|
@@ -5172,6 +5634,208 @@ function findPythonNetlocAllowlistGuardSanitizers(code) {
|
|
|
5172
5634
|
* or UPPER_SNAKE constants (e.g. MAX_QTY). The guard body must contain
|
|
5173
5635
|
* a terminator.
|
|
5174
5636
|
*/
|
|
5637
|
+
/**
|
|
5638
|
+
* JS/TS numeric range-check guards on Number/parseInt-coerced values
|
|
5639
|
+
* (cognium-dev #816, PAT-INPUT-01 safe mirror; Python twin #114).
|
|
5640
|
+
*
|
|
5641
|
+
* const qty = parseInt(req.query.qty, 10);
|
|
5642
|
+
* if (!Number.isFinite(qty) || qty <= 0) return res.status(400).end();
|
|
5643
|
+
* if (qty > MAX) return res.status(400).end();
|
|
5644
|
+
* res.end(`xfer ${qty}`);
|
|
5645
|
+
*
|
|
5646
|
+
* Coercion strips xss in DEFAULT_SANITIZERS but template interpolation
|
|
5647
|
+
* re-taints; a proven finite + bounded range guard shows the emitted
|
|
5648
|
+
* scalar cannot carry markup.
|
|
5649
|
+
*/
|
|
5650
|
+
function findJsNumericRangeCheckGuardSanitizers(code) {
|
|
5651
|
+
const sanitizers = [];
|
|
5652
|
+
const lines = code.split('\n');
|
|
5653
|
+
const coerceDecl = /^\s*(?:const|let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:Number|parseInt)\s*\(/;
|
|
5654
|
+
const lowerGuard = /\bif\s*\(\s*!(?:Number\.)?isFinite\s*\(\s*([A-Za-z_]\w*)\s*\)|\bif\s*\([^)]*\b([A-Za-z_]\w*)\s*<=\s*0/;
|
|
5655
|
+
const upperGuard = /\bif\s*\(\s*([A-Za-z_]\w*)\s*>\s*(?:\d+|[A-Z][A-Z0-9_]+)\s*\)/;
|
|
5656
|
+
const terminator = /\b(?:return|throw|res\s*\.\s*status\s*\([^)]*\)\s*\.\s*(?:send|end|json))/;
|
|
5657
|
+
const coerced = new Map();
|
|
5658
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5659
|
+
const m = coerceDecl.exec(lines[i]);
|
|
5660
|
+
if (m)
|
|
5661
|
+
coerced.set(m[1], i);
|
|
5662
|
+
}
|
|
5663
|
+
if (coerced.size === 0)
|
|
5664
|
+
return sanitizers;
|
|
5665
|
+
const state = new Map();
|
|
5666
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5667
|
+
const line = lines[i];
|
|
5668
|
+
let varName = null;
|
|
5669
|
+
let kind = null;
|
|
5670
|
+
const low = lowerGuard.exec(line);
|
|
5671
|
+
if (low) {
|
|
5672
|
+
varName = low[1] ?? low[2];
|
|
5673
|
+
kind = 'lower';
|
|
5674
|
+
}
|
|
5675
|
+
else {
|
|
5676
|
+
const up = upperGuard.exec(line);
|
|
5677
|
+
if (up) {
|
|
5678
|
+
varName = up[1];
|
|
5679
|
+
kind = 'upper';
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
if (!varName || !kind || !coerced.has(varName))
|
|
5683
|
+
continue;
|
|
5684
|
+
if (!terminator.test(line)) {
|
|
5685
|
+
let bodyHasTerminator = false;
|
|
5686
|
+
let blockEnd = i;
|
|
5687
|
+
let braceDepth = 0;
|
|
5688
|
+
let started = false;
|
|
5689
|
+
const maxScan = Math.min(lines.length, i + 26);
|
|
5690
|
+
for (let j = i; j < maxScan; j++) {
|
|
5691
|
+
const ln = lines[j];
|
|
5692
|
+
for (const ch of ln) {
|
|
5693
|
+
if (ch === '{') {
|
|
5694
|
+
braceDepth++;
|
|
5695
|
+
started = true;
|
|
5696
|
+
}
|
|
5697
|
+
else if (ch === '}') {
|
|
5698
|
+
braceDepth--;
|
|
5699
|
+
if (started && braceDepth === 0) {
|
|
5700
|
+
blockEnd = j;
|
|
5701
|
+
break;
|
|
5702
|
+
}
|
|
5703
|
+
}
|
|
5704
|
+
}
|
|
5705
|
+
if (j > i && terminator.test(ln))
|
|
5706
|
+
bodyHasTerminator = true;
|
|
5707
|
+
if (started && braceDepth === 0 && blockEnd !== i)
|
|
5708
|
+
break;
|
|
5709
|
+
}
|
|
5710
|
+
if (!bodyHasTerminator && !started)
|
|
5711
|
+
continue;
|
|
5712
|
+
if (!bodyHasTerminator)
|
|
5713
|
+
continue;
|
|
5714
|
+
}
|
|
5715
|
+
const st = state.get(varName) ?? { lower: false, upper: false, lastGuardEnd: i };
|
|
5716
|
+
if (kind === 'lower')
|
|
5717
|
+
st.lower = true;
|
|
5718
|
+
else
|
|
5719
|
+
st.upper = true;
|
|
5720
|
+
st.lastGuardEnd = i;
|
|
5721
|
+
state.set(varName, st);
|
|
5722
|
+
}
|
|
5723
|
+
for (const [varName, st] of state) {
|
|
5724
|
+
if (!st.lower || !st.upper)
|
|
5725
|
+
continue;
|
|
5726
|
+
const varRefRe = new RegExp(`\\b${varName}\\b`);
|
|
5727
|
+
for (let l = st.lastGuardEnd + 1; l < lines.length; l++) {
|
|
5728
|
+
if (!varRefRe.test(lines[l]))
|
|
5729
|
+
continue;
|
|
5730
|
+
sanitizers.push({
|
|
5731
|
+
type: 'js_numeric_range_check_guard',
|
|
5732
|
+
method: 'if',
|
|
5733
|
+
line: l + 1,
|
|
5734
|
+
sanitizes: ['xss', 'external_taint_escape'],
|
|
5735
|
+
});
|
|
5736
|
+
}
|
|
5737
|
+
}
|
|
5738
|
+
return sanitizers;
|
|
5739
|
+
}
|
|
5740
|
+
/**
|
|
5741
|
+
* Rust numeric range-check guards on `.parse()`-coerced values
|
|
5742
|
+
* (cognium-dev #816, PAT-INPUT-01 safe mirror).
|
|
5743
|
+
*
|
|
5744
|
+
* let amount: i32 = amount_str.parse().unwrap_or(0);
|
|
5745
|
+
* if amount <= 0 { return HttpResponse::BadRequest().finish(); }
|
|
5746
|
+
* if amount > MAX { return HttpResponse::BadRequest().finish(); }
|
|
5747
|
+
* HttpResponse::Ok().body(format!("xfer {}", amount))
|
|
5748
|
+
*/
|
|
5749
|
+
function findRustNumericRangeCheckGuardSanitizers(code) {
|
|
5750
|
+
const sanitizers = [];
|
|
5751
|
+
const lines = code.split('\n');
|
|
5752
|
+
const coerceDecl = /^\s*let\s+([A-Za-z_]\w*)\s*(?::\s*(?:i\d+|u\d+|isize|usize))?\s*=\s*[^;]*\.parse\s*(?:\(\s*\)|::<)/;
|
|
5753
|
+
const lowerGuard = /\bif\s+([A-Za-z_]\w*)\s*<=\s*0\b/;
|
|
5754
|
+
const upperGuard = /\bif\s+([A-Za-z_]\w*)\s*>\s*(?:\d+|[A-Z][A-Z0-9_]+)\b/;
|
|
5755
|
+
const terminator = /\b(?:return\s+HttpResponse::|return\s+StatusCode::|return\s+Err\s*\(|panic!\s*\()/;
|
|
5756
|
+
const coerced = new Map();
|
|
5757
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5758
|
+
const m = coerceDecl.exec(lines[i]);
|
|
5759
|
+
if (m)
|
|
5760
|
+
coerced.set(m[1], i);
|
|
5761
|
+
}
|
|
5762
|
+
if (coerced.size === 0)
|
|
5763
|
+
return sanitizers;
|
|
5764
|
+
const state = new Map();
|
|
5765
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5766
|
+
const line = lines[i];
|
|
5767
|
+
let varName = null;
|
|
5768
|
+
let kind = null;
|
|
5769
|
+
const low = lowerGuard.exec(line);
|
|
5770
|
+
if (low) {
|
|
5771
|
+
varName = low[1];
|
|
5772
|
+
kind = 'lower';
|
|
5773
|
+
}
|
|
5774
|
+
else {
|
|
5775
|
+
const up = upperGuard.exec(line);
|
|
5776
|
+
if (up) {
|
|
5777
|
+
varName = up[1];
|
|
5778
|
+
kind = 'upper';
|
|
5779
|
+
}
|
|
5780
|
+
}
|
|
5781
|
+
if (!varName || !kind || !coerced.has(varName))
|
|
5782
|
+
continue;
|
|
5783
|
+
if (!terminator.test(line)) {
|
|
5784
|
+
let bodyHasTerminator = false;
|
|
5785
|
+
let blockEnd = i;
|
|
5786
|
+
let braceDepth = 0;
|
|
5787
|
+
let started = false;
|
|
5788
|
+
const maxScan = Math.min(lines.length, i + 26);
|
|
5789
|
+
for (let j = i; j < maxScan; j++) {
|
|
5790
|
+
const ln = lines[j];
|
|
5791
|
+
for (const ch of ln) {
|
|
5792
|
+
if (ch === '{') {
|
|
5793
|
+
braceDepth++;
|
|
5794
|
+
started = true;
|
|
5795
|
+
}
|
|
5796
|
+
else if (ch === '}') {
|
|
5797
|
+
braceDepth--;
|
|
5798
|
+
if (started && braceDepth === 0) {
|
|
5799
|
+
blockEnd = j;
|
|
5800
|
+
break;
|
|
5801
|
+
}
|
|
5802
|
+
}
|
|
5803
|
+
}
|
|
5804
|
+
if (j > i && terminator.test(ln))
|
|
5805
|
+
bodyHasTerminator = true;
|
|
5806
|
+
if (started && braceDepth === 0 && blockEnd !== i)
|
|
5807
|
+
break;
|
|
5808
|
+
}
|
|
5809
|
+
if (!bodyHasTerminator && !started)
|
|
5810
|
+
continue;
|
|
5811
|
+
if (!bodyHasTerminator)
|
|
5812
|
+
continue;
|
|
5813
|
+
}
|
|
5814
|
+
const st = state.get(varName) ?? { lower: false, upper: false, lastGuardEnd: i };
|
|
5815
|
+
if (kind === 'lower')
|
|
5816
|
+
st.lower = true;
|
|
5817
|
+
else
|
|
5818
|
+
st.upper = true;
|
|
5819
|
+
st.lastGuardEnd = i;
|
|
5820
|
+
state.set(varName, st);
|
|
5821
|
+
}
|
|
5822
|
+
for (const [varName, st] of state) {
|
|
5823
|
+
if (!st.lower || !st.upper)
|
|
5824
|
+
continue;
|
|
5825
|
+
const varRefRe = new RegExp(`\\b${varName}\\b`);
|
|
5826
|
+
for (let l = st.lastGuardEnd + 1; l < lines.length; l++) {
|
|
5827
|
+
if (!varRefRe.test(lines[l]))
|
|
5828
|
+
continue;
|
|
5829
|
+
sanitizers.push({
|
|
5830
|
+
type: 'rust_numeric_range_check_guard',
|
|
5831
|
+
method: 'if',
|
|
5832
|
+
line: l + 1,
|
|
5833
|
+
sanitizes: ['xss', 'external_taint_escape'],
|
|
5834
|
+
});
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
return sanitizers;
|
|
5838
|
+
}
|
|
5175
5839
|
function findPythonRangeCheckGuardSanitizers(code) {
|
|
5176
5840
|
const sanitizers = [];
|
|
5177
5841
|
const lines = code.split('\n');
|
|
@@ -6040,6 +6704,161 @@ function findPythonDefusedXmlSanitizers(code) {
|
|
|
6040
6704
|
}
|
|
6041
6705
|
return sanitizers;
|
|
6042
6706
|
}
|
|
6707
|
+
/**
|
|
6708
|
+
* PAT-INFO-01 safe mirror (cognium-dev #816): `HttpResponse` body built with
|
|
6709
|
+
* `format!` interpolates only `web::Query` / `.get(...)` values — no `env::var`.
|
|
6710
|
+
* Credits the sink so CWE-200-style response leaks stay on env-backed TP controls.
|
|
6711
|
+
*/
|
|
6712
|
+
function findRustPatInfoHttpOnlyResponseSanitizers(code) {
|
|
6713
|
+
const sanitizers = [];
|
|
6714
|
+
const lines = code.split('\n');
|
|
6715
|
+
const bodyFormatRe = /HttpResponse::\w+\(\)\s*\.\s*body\s*\(\s*format!\s*\(/;
|
|
6716
|
+
const envBindRe = /(?:^|[^:])env::var\s*\(|std::env::/;
|
|
6717
|
+
const httpGetRe = /\.get\s*\(\s*"[^"]+"\s*\)/;
|
|
6718
|
+
const letVarRe = (v) => new RegExp(String.raw `^\s*let\s+(?:mut\s+)?${v}\s*=`);
|
|
6719
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6720
|
+
const sinkLine = lines[i];
|
|
6721
|
+
if (!bodyFormatRe.test(sinkLine))
|
|
6722
|
+
continue;
|
|
6723
|
+
if (envBindRe.test(sinkLine))
|
|
6724
|
+
continue;
|
|
6725
|
+
const varRefs = [
|
|
6726
|
+
...sinkLine.matchAll(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g),
|
|
6727
|
+
].map((m) => m[1]);
|
|
6728
|
+
if (varRefs.length === 0)
|
|
6729
|
+
continue;
|
|
6730
|
+
let allHttpOnly = true;
|
|
6731
|
+
for (const v of varRefs) {
|
|
6732
|
+
let sawBinding = false;
|
|
6733
|
+
let boundEnv = false;
|
|
6734
|
+
let boundHttp = false;
|
|
6735
|
+
for (let j = i - 1; j >= Math.max(0, i - 24); j--) {
|
|
6736
|
+
const line = lines[j];
|
|
6737
|
+
if (!letVarRe(v).test(line))
|
|
6738
|
+
continue;
|
|
6739
|
+
sawBinding = true;
|
|
6740
|
+
if (envBindRe.test(line))
|
|
6741
|
+
boundEnv = true;
|
|
6742
|
+
if (httpGetRe.test(line))
|
|
6743
|
+
boundHttp = true;
|
|
6744
|
+
break;
|
|
6745
|
+
}
|
|
6746
|
+
if (!sawBinding || boundEnv || !boundHttp) {
|
|
6747
|
+
allHttpOnly = false;
|
|
6748
|
+
break;
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6751
|
+
if (!allHttpOnly)
|
|
6752
|
+
continue;
|
|
6753
|
+
sanitizers.push({
|
|
6754
|
+
type: 'rust_pat_info_http_only_body',
|
|
6755
|
+
method: 'format',
|
|
6756
|
+
line: i + 1,
|
|
6757
|
+
sanitizes: [
|
|
6758
|
+
'xss',
|
|
6759
|
+
'external_taint_escape',
|
|
6760
|
+
'open_redirect',
|
|
6761
|
+
'log_injection',
|
|
6762
|
+
],
|
|
6763
|
+
});
|
|
6764
|
+
}
|
|
6765
|
+
return sanitizers;
|
|
6766
|
+
}
|
|
6767
|
+
/**
|
|
6768
|
+
* Rust `const SLICE: &[&str] = &["a", "b"]` membership reject guard
|
|
6769
|
+
* (cognium-dev #816 PAT-TRUST-01; Python twin #563).
|
|
6770
|
+
*
|
|
6771
|
+
* if !THEMES.contains(&theme.as_str()) {
|
|
6772
|
+
* return HttpResponse::BadRequest().finish();
|
|
6773
|
+
* }
|
|
6774
|
+
* session.insert("theme", theme)
|
|
6775
|
+
*/
|
|
6776
|
+
function collectRustConstStrSliceNames(code) {
|
|
6777
|
+
const names = new Set();
|
|
6778
|
+
const re = /^\s*const\s+([A-Z][A-Z0-9_]*)\s*:\s*&\s*\[\s*&\s*str\s*\]\s*=\s*&\s*\[([^\]]*)\]\s*;/;
|
|
6779
|
+
for (const line of code.split('\n')) {
|
|
6780
|
+
const m = re.exec(line);
|
|
6781
|
+
if (!m)
|
|
6782
|
+
continue;
|
|
6783
|
+
const inner = m[2].trim();
|
|
6784
|
+
if (!inner) {
|
|
6785
|
+
names.add(m[1]);
|
|
6786
|
+
continue;
|
|
6787
|
+
}
|
|
6788
|
+
const elems = inner.split(',').map(s => s.trim()).filter(Boolean);
|
|
6789
|
+
if (elems.every(e => /^"(?:[^"\\]|\\.)*"$/.test(e)))
|
|
6790
|
+
names.add(m[1]);
|
|
6791
|
+
}
|
|
6792
|
+
return names;
|
|
6793
|
+
}
|
|
6794
|
+
function findRustConstSliceMembershipGuardSanitizers(code) {
|
|
6795
|
+
const sanitizers = [];
|
|
6796
|
+
const slices = collectRustConstStrSliceNames(code);
|
|
6797
|
+
if (slices.size === 0)
|
|
6798
|
+
return sanitizers;
|
|
6799
|
+
const lines = code.split('\n');
|
|
6800
|
+
const guardOpen = /^\s*if\s+!\s*([A-Z][A-Z0-9_]*)\s*\.\s*contains\s*\(\s*&\s*([A-Za-z_]\w*)(?:\s*\.\s*as_str\s*\(\s*\))?\s*\)\s*\{?\s*$/;
|
|
6801
|
+
const terminator = /\b(?:return\s+HttpResponse::|return\s+StatusCode::|return\s+Err\s*\(|panic!\s*\()/;
|
|
6802
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6803
|
+
const m = guardOpen.exec(lines[i]);
|
|
6804
|
+
if (!m)
|
|
6805
|
+
continue;
|
|
6806
|
+
const sliceName = m[1];
|
|
6807
|
+
const guardedVar = m[2];
|
|
6808
|
+
if (!slices.has(sliceName))
|
|
6809
|
+
continue;
|
|
6810
|
+
let depth = 0;
|
|
6811
|
+
for (const ch of lines[i]) {
|
|
6812
|
+
if (ch === '{')
|
|
6813
|
+
depth++;
|
|
6814
|
+
else if (ch === '}')
|
|
6815
|
+
depth--;
|
|
6816
|
+
}
|
|
6817
|
+
let closeLine = i;
|
|
6818
|
+
let bodyHasTerminator = terminator.test(lines[i]);
|
|
6819
|
+
if (depth > 0) {
|
|
6820
|
+
for (let j = i + 1; j < Math.min(lines.length, i + 26); j++) {
|
|
6821
|
+
if (terminator.test(lines[j]))
|
|
6822
|
+
bodyHasTerminator = true;
|
|
6823
|
+
for (const ch of lines[j]) {
|
|
6824
|
+
if (ch === '{')
|
|
6825
|
+
depth++;
|
|
6826
|
+
else if (ch === '}')
|
|
6827
|
+
depth--;
|
|
6828
|
+
}
|
|
6829
|
+
if (depth === 0) {
|
|
6830
|
+
closeLine = j;
|
|
6831
|
+
break;
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
}
|
|
6835
|
+
if (!bodyHasTerminator)
|
|
6836
|
+
continue;
|
|
6837
|
+
const varRe = new RegExp(String.raw `\b${guardedVar}\b`);
|
|
6838
|
+
const reassign = new RegExp(String.raw `^\s*(?:let\s+)?(?:mut\s+)?${guardedVar}\s*=(?!=)`);
|
|
6839
|
+
for (let l = closeLine + 2; l <= lines.length; l++) {
|
|
6840
|
+
const lineText = lines[l - 1];
|
|
6841
|
+
if (!lineText || !varRe.test(lineText))
|
|
6842
|
+
continue;
|
|
6843
|
+
if (reassign.test(lineText))
|
|
6844
|
+
break;
|
|
6845
|
+
sanitizers.push({
|
|
6846
|
+
type: 'rust_const_slice_membership_guard',
|
|
6847
|
+
method: 'if',
|
|
6848
|
+
line: l,
|
|
6849
|
+
sanitizes: [
|
|
6850
|
+
'trust_boundary',
|
|
6851
|
+
'external_taint_escape',
|
|
6852
|
+
'xss',
|
|
6853
|
+
'open_redirect',
|
|
6854
|
+
'ssrf',
|
|
6855
|
+
'command_injection',
|
|
6856
|
+
],
|
|
6857
|
+
});
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
return sanitizers;
|
|
6861
|
+
}
|
|
6043
6862
|
/**
|
|
6044
6863
|
* Detect Rust HashSet/HashMap host allow-list guards (cognium-dev #115 FP-23).
|
|
6045
6864
|
*
|
|
@@ -6311,6 +7130,16 @@ function findJsCsvFormulaPrefixSanitizers(code) {
|
|
|
6311
7130
|
* threat-set. Custom wrappers using different escape strategies (e.g.
|
|
6312
7131
|
* URL encode) are not yet covered.
|
|
6313
7132
|
*/
|
|
7133
|
+
/** Return expression of a small JS wrapper (function or arrow) for #562 chain verdict. */
|
|
7134
|
+
function jsWrapperBodyReplaceChainText(body) {
|
|
7135
|
+
const returnM = /\breturn\s+([^;}\n]+)/.exec(body);
|
|
7136
|
+
if (returnM)
|
|
7137
|
+
return returnM[1].trim();
|
|
7138
|
+
const arrowM = /=>\s*([^;}\n]+)/.exec(body);
|
|
7139
|
+
if (arrowM)
|
|
7140
|
+
return arrowM[1].trim();
|
|
7141
|
+
return null;
|
|
7142
|
+
}
|
|
6314
7143
|
function findJsWrapperFunctionSanitizers(code) {
|
|
6315
7144
|
const sanitizers = [];
|
|
6316
7145
|
const lines = code.split('\n');
|
|
@@ -6333,8 +7162,13 @@ function findJsWrapperFunctionSanitizers(code) {
|
|
|
6333
7162
|
kinds.add('external_taint_escape');
|
|
6334
7163
|
}
|
|
6335
7164
|
if (crlfCharClass.test(body)) {
|
|
6336
|
-
|
|
6337
|
-
|
|
7165
|
+
const chainText = jsWrapperBodyReplaceChainText(body);
|
|
7166
|
+
const verdict = chainText ? jsReplaceChainVerdict(chainText) : 'undecided';
|
|
7167
|
+
// #562 / #605: LF-only or CRLF-pair-only strips must not credit log_injection.
|
|
7168
|
+
if (verdict !== 'leaves-crlf') {
|
|
7169
|
+
kinds.add('log_injection');
|
|
7170
|
+
kinds.add('external_taint_escape');
|
|
7171
|
+
}
|
|
6338
7172
|
}
|
|
6339
7173
|
if (kinds.size > 0)
|
|
6340
7174
|
wrappers.set(name, kinds);
|
|
@@ -7743,6 +8577,87 @@ function javaBodyStripsCrAndLf(body) {
|
|
|
7743
8577
|
}
|
|
7744
8578
|
return cr && lf;
|
|
7745
8579
|
}
|
|
8580
|
+
/** True when the helper removes both `{` and `}` via replace/replaceAll (flow-sanitize ssti). */
|
|
8581
|
+
function javaBodyStripsTemplateDelimiters(body) {
|
|
8582
|
+
let open = false;
|
|
8583
|
+
let close = false;
|
|
8584
|
+
for (const m of body.matchAll(/\.\s*(replace|replaceAll)\s*\(\s*(?:"((?:\\.|[^"\\])*)"|'(\\.|[^'\\])')/g)) {
|
|
8585
|
+
const lit = m[2] ?? m[3] ?? '';
|
|
8586
|
+
if (lit === '{')
|
|
8587
|
+
open = true;
|
|
8588
|
+
if (lit === '}')
|
|
8589
|
+
close = true;
|
|
8590
|
+
}
|
|
8591
|
+
return open && close;
|
|
8592
|
+
}
|
|
8593
|
+
/**
|
|
8594
|
+
* Java user-defined wrapper that strips Velocity/Jinja-style `{` `}` delimiters.
|
|
8595
|
+
* Mirrors flow-sanitize `ssti` / `custom_wrapper` safe mirrors (cognium-dev#947).
|
|
8596
|
+
*/
|
|
8597
|
+
function findJavaTemplateDelimiterWrapperSanitizers(code) {
|
|
8598
|
+
const sanitizers = [];
|
|
8599
|
+
const lines = code.split('\n');
|
|
8600
|
+
const declRe = /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:String|CharSequence)\s+([A-Za-z_]\w*)\s*\(/;
|
|
8601
|
+
const bodyWindow = 10;
|
|
8602
|
+
const wrappers = new Set();
|
|
8603
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8604
|
+
const m = declRe.exec(lines[i]);
|
|
8605
|
+
if (!m)
|
|
8606
|
+
continue;
|
|
8607
|
+
let end = Math.min(lines.length, i + bodyWindow);
|
|
8608
|
+
for (let j = i + 1; j < end; j++) {
|
|
8609
|
+
if (declRe.test(lines[j])) {
|
|
8610
|
+
end = j;
|
|
8611
|
+
break;
|
|
8612
|
+
}
|
|
8613
|
+
}
|
|
8614
|
+
const body = lines.slice(i, end).join('\n');
|
|
8615
|
+
if (javaBodyStripsTemplateDelimiters(body))
|
|
8616
|
+
wrappers.add(m[1]);
|
|
8617
|
+
}
|
|
8618
|
+
if (wrappers.size === 0)
|
|
8619
|
+
return sanitizers;
|
|
8620
|
+
const kinds = ['code_injection', 'external_taint_escape'];
|
|
8621
|
+
for (const name of wrappers) {
|
|
8622
|
+
const assignedCallRe = new RegExp(`\\b([A-Za-z_][A-Za-z0-9_]*)\\s*=\\s*${name}\\s*\\(`);
|
|
8623
|
+
const bareCallRe = new RegExp(`\\b${name}\\s*\\(`);
|
|
8624
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8625
|
+
if (!bareCallRe.test(lines[i]))
|
|
8626
|
+
continue;
|
|
8627
|
+
if (declRe.test(lines[i]) && bareCallRe.test(lines[i]))
|
|
8628
|
+
continue;
|
|
8629
|
+
sanitizers.push({
|
|
8630
|
+
type: 'java_wrapper_template_delim_strip',
|
|
8631
|
+
method: name,
|
|
8632
|
+
line: i + 1,
|
|
8633
|
+
sanitizes: [...kinds],
|
|
8634
|
+
});
|
|
8635
|
+
const am = assignedCallRe.exec(lines[i]);
|
|
8636
|
+
if (!am)
|
|
8637
|
+
continue;
|
|
8638
|
+
const callIndent = lines[i].length - lines[i].trimStart().length;
|
|
8639
|
+
const validated = am[1];
|
|
8640
|
+
const validatedRe = new RegExp(`\\b${validated}\\b`);
|
|
8641
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
8642
|
+
const jline = lines[j];
|
|
8643
|
+
if (jline.trim() === '')
|
|
8644
|
+
continue;
|
|
8645
|
+
const jindent = jline.length - jline.trimStart().length;
|
|
8646
|
+
if (jindent < callIndent)
|
|
8647
|
+
break;
|
|
8648
|
+
if (validatedRe.test(jline)) {
|
|
8649
|
+
sanitizers.push({
|
|
8650
|
+
type: 'java_wrapper_template_delim_strip',
|
|
8651
|
+
method: name,
|
|
8652
|
+
line: j + 1,
|
|
8653
|
+
sanitizes: [...kinds],
|
|
8654
|
+
});
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
}
|
|
8658
|
+
}
|
|
8659
|
+
return sanitizers;
|
|
8660
|
+
}
|
|
7746
8661
|
function findCrlfWrapperFunctionSanitizers(code, language) {
|
|
7747
8662
|
const sanitizers = [];
|
|
7748
8663
|
const lines = code.split('\n');
|
|
@@ -11174,6 +12089,108 @@ export function findJsLdapInjectionFindings(code, file) {
|
|
|
11174
12089
|
snippet: line.trim(),
|
|
11175
12090
|
});
|
|
11176
12091
|
}
|
|
12092
|
+
// cognium-dev #899: ldapjs also accepts the filter as the 2nd positional
|
|
12093
|
+
// argument (`client.search(base, '(mail=' + email + ')', cb)`), not only
|
|
12094
|
+
// as `opts.filter`. Flag when that argument is built from tainted data.
|
|
12095
|
+
// Base DN is often comma-separated; only match a quoted first argument.
|
|
12096
|
+
const positionalSearchRe = /\.\s*search\s*\(\s*(?:'[^']*'|"[^"]*")\s*,\s*([^,{]+)/;
|
|
12097
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12098
|
+
const line = lines[i];
|
|
12099
|
+
const pm = line.match(positionalSearchRe);
|
|
12100
|
+
if (!pm)
|
|
12101
|
+
continue;
|
|
12102
|
+
const expr = pm[1].trim();
|
|
12103
|
+
if (expr.startsWith('{'))
|
|
12104
|
+
continue;
|
|
12105
|
+
let tainted = false;
|
|
12106
|
+
for (const v of taintedVars) {
|
|
12107
|
+
if (new RegExp(`\\b${v}\\b`).test(expr)) {
|
|
12108
|
+
tainted = true;
|
|
12109
|
+
break;
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12112
|
+
if (!tainted)
|
|
12113
|
+
continue;
|
|
12114
|
+
const lineNo = i + 1;
|
|
12115
|
+
if (seen.has(lineNo))
|
|
12116
|
+
continue;
|
|
12117
|
+
seen.add(lineNo);
|
|
12118
|
+
findings.push({
|
|
12119
|
+
id: `ldap_injection-${file}-${lineNo}-js-ldap-positional-filter`,
|
|
12120
|
+
pass: 'language-sources',
|
|
12121
|
+
category: 'security',
|
|
12122
|
+
rule_id: 'ldap_injection',
|
|
12123
|
+
cwe: 'CWE-90',
|
|
12124
|
+
severity: 'critical',
|
|
12125
|
+
level: 'error',
|
|
12126
|
+
message: 'LDAP injection: ldapjs/ldapts `client.search(base, <tainted-filter>, ...)` ' +
|
|
12127
|
+
'lets the attacker break out of the filter expression. Escape user input ' +
|
|
12128
|
+
'or use a structured filter builder.',
|
|
12129
|
+
file,
|
|
12130
|
+
line: lineNo,
|
|
12131
|
+
snippet: line.trim(),
|
|
12132
|
+
});
|
|
12133
|
+
}
|
|
12134
|
+
return findings;
|
|
12135
|
+
}
|
|
12136
|
+
/**
|
|
12137
|
+
* cognium-dev #897 — ZIP local-file-header validation that fail-opens: after
|
|
12138
|
+
* rejecting an invalid PK\\x03\\x04 signature, returning a slice of the caller
|
|
12139
|
+
* buffer instead of throwing lets untrusted bytes through (CWE-20).
|
|
12140
|
+
*/
|
|
12141
|
+
export function findJsZipHeaderFailopenFindings(code, file) {
|
|
12142
|
+
const findings = [];
|
|
12143
|
+
if (typeof code !== 'string' || code.length === 0)
|
|
12144
|
+
return findings;
|
|
12145
|
+
const lines = code.split('\n');
|
|
12146
|
+
const hasPkLocalSig = lines.some((l) => {
|
|
12147
|
+
const t = l.trim();
|
|
12148
|
+
if (t.startsWith('//'))
|
|
12149
|
+
return false;
|
|
12150
|
+
return (/0x50/.test(t) &&
|
|
12151
|
+
/0x4[bB]/.test(t) &&
|
|
12152
|
+
/0x03/.test(t) &&
|
|
12153
|
+
/0x04/.test(t));
|
|
12154
|
+
});
|
|
12155
|
+
if (!hasPkLocalSig)
|
|
12156
|
+
return findings;
|
|
12157
|
+
const seen = new Set();
|
|
12158
|
+
for (let i = 0; i < lines.length; i++) {
|
|
12159
|
+
const trimmed = lines[i].trim();
|
|
12160
|
+
if (trimmed.startsWith('//'))
|
|
12161
|
+
continue;
|
|
12162
|
+
const failBranch = trimmed.match(/^if\s*\(\s*!(\w+)\s*\)\s*(.+)$/);
|
|
12163
|
+
if (!failBranch)
|
|
12164
|
+
continue;
|
|
12165
|
+
const tail = failBranch[2];
|
|
12166
|
+
if (/\bthrow\b/.test(tail))
|
|
12167
|
+
continue;
|
|
12168
|
+
if (!/\breturn\b/.test(tail))
|
|
12169
|
+
continue;
|
|
12170
|
+
if (!/\.(subarray|slice)\s*\(/.test(tail) &&
|
|
12171
|
+
!/Buffer\.from\s*\(/.test(tail)) {
|
|
12172
|
+
continue;
|
|
12173
|
+
}
|
|
12174
|
+
const lineNo = i + 1;
|
|
12175
|
+
if (seen.has(lineNo))
|
|
12176
|
+
continue;
|
|
12177
|
+
seen.add(lineNo);
|
|
12178
|
+
findings.push({
|
|
12179
|
+
id: `input_validation-${file}-${lineNo}-js-zip-header-failopen`,
|
|
12180
|
+
pass: 'language-sources',
|
|
12181
|
+
category: 'security',
|
|
12182
|
+
rule_id: 'input_validation',
|
|
12183
|
+
cwe: 'CWE-20',
|
|
12184
|
+
severity: 'high',
|
|
12185
|
+
level: 'error',
|
|
12186
|
+
message: 'Input validation fail-open: an invalid ZIP local header check still ' +
|
|
12187
|
+
'returns a slice of the caller-controlled buffer. On parse failure, ' +
|
|
12188
|
+
'throw or return empty — do not pass through untrusted bytes.',
|
|
12189
|
+
file,
|
|
12190
|
+
line: lineNo,
|
|
12191
|
+
snippet: trimmed.substring(0, 120),
|
|
12192
|
+
});
|
|
12193
|
+
}
|
|
11177
12194
|
return findings;
|
|
11178
12195
|
}
|
|
11179
12196
|
/**
|
|
@@ -11823,6 +12840,53 @@ export function findGoGobDeserializationFindings(code, file) {
|
|
|
11823
12840
|
}
|
|
11824
12841
|
return findings;
|
|
11825
12842
|
}
|
|
12843
|
+
/**
|
|
12844
|
+
* Blank the contents of JS/TS string and template literals and comments,
|
|
12845
|
+
* keeping newlines and every offset, so a line scanner does not match code
|
|
12846
|
+
* that only appears as text (a message describing `JSON.parse(req.body)`).
|
|
12847
|
+
*/
|
|
12848
|
+
function maskJsLiteralsAndComments(code) {
|
|
12849
|
+
const out = code.split('');
|
|
12850
|
+
let i = 0;
|
|
12851
|
+
const blank = (from, to) => {
|
|
12852
|
+
for (let k = from; k < to; k++)
|
|
12853
|
+
if (out[k] !== '\n')
|
|
12854
|
+
out[k] = ' ';
|
|
12855
|
+
};
|
|
12856
|
+
while (i < code.length) {
|
|
12857
|
+
const c = code[i];
|
|
12858
|
+
const n = code[i + 1];
|
|
12859
|
+
if (c === '/' && n === '/') {
|
|
12860
|
+
let j = i;
|
|
12861
|
+
while (j < code.length && code[j] !== '\n')
|
|
12862
|
+
j++;
|
|
12863
|
+
blank(i, j);
|
|
12864
|
+
i = j;
|
|
12865
|
+
}
|
|
12866
|
+
else if (c === '/' && n === '*') {
|
|
12867
|
+
const end = code.indexOf('*/', i + 2);
|
|
12868
|
+
const j = end < 0 ? code.length : end + 2;
|
|
12869
|
+
blank(i, j);
|
|
12870
|
+
i = j;
|
|
12871
|
+
}
|
|
12872
|
+
else if (c === '"' || c === "'" || c === '`') {
|
|
12873
|
+
let j = i + 1;
|
|
12874
|
+
while (j < code.length && code[j] !== c) {
|
|
12875
|
+
if (code[j] === '\\')
|
|
12876
|
+
j++;
|
|
12877
|
+
else if (c !== '`' && code[j] === '\n')
|
|
12878
|
+
break;
|
|
12879
|
+
j++;
|
|
12880
|
+
}
|
|
12881
|
+
blank(i + 1, Math.min(j, code.length));
|
|
12882
|
+
i = j + 1;
|
|
12883
|
+
}
|
|
12884
|
+
else {
|
|
12885
|
+
i++;
|
|
12886
|
+
}
|
|
12887
|
+
}
|
|
12888
|
+
return out.join('');
|
|
12889
|
+
}
|
|
11826
12890
|
/**
|
|
11827
12891
|
* Sprint 89 detector B (#189) — JS insecure deserialization via
|
|
11828
12892
|
* `JSON.parse(req.body)`.
|
|
@@ -11878,13 +12942,14 @@ export function findJsJsonParseBodyFindings(code, file) {
|
|
|
11878
12942
|
}
|
|
11879
12943
|
const callRe = /\bJSON\s*\.\s*parse\s*\(\s*([^)]+?)\s*\)/g;
|
|
11880
12944
|
const seen = new Set();
|
|
12945
|
+
// Calls only: `JSON.parse(req.body)` inside a string or comment is text.
|
|
12946
|
+
const codeLines = maskJsLiteralsAndComments(code).split('\n');
|
|
11881
12947
|
for (let i = 0; i < lines.length; i++) {
|
|
11882
12948
|
const line = lines[i];
|
|
11883
|
-
|
|
11884
|
-
continue;
|
|
12949
|
+
const codeLine = codeLines[i] ?? '';
|
|
11885
12950
|
callRe.lastIndex = 0;
|
|
11886
12951
|
let m;
|
|
11887
|
-
while ((m = callRe.exec(
|
|
12952
|
+
while ((m = callRe.exec(codeLine)) !== null) {
|
|
11888
12953
|
const arg = m[1];
|
|
11889
12954
|
let tainted = reqBodyRe.test(arg);
|
|
11890
12955
|
if (!tainted) {
|