circle-ir 3.1.0 → 3.1.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/README.md +3 -0
- package/configs/sinks/javascript_dom_xss.yaml +131 -0
- package/configs/sources/javascript_http.yaml +296 -0
- package/configs/sources/python.json +78 -0
- package/dist/analysis/taint-matcher.js +35 -13
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.js +154 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +490 -16
- package/dist/core/circle-ir-core.cjs +29 -14
- package/dist/core/circle-ir-core.js +29 -14
- package/dist/languages/plugins/javascript.js +333 -1
- package/dist/languages/plugins/javascript.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -1
|
@@ -10175,15 +10175,16 @@ function findSources(calls, types, patterns) {
|
|
|
10175
10175
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo", "getDescription", "getVulnerabilityCount"];
|
|
10176
10176
|
if (skipMethods.includes(method.name)) continue;
|
|
10177
10177
|
for (const param of method.parameters) {
|
|
10178
|
-
|
|
10178
|
+
const isTaintable = param.type ? isInterproceduralTaintableType(param.type) : true;
|
|
10179
|
+
if (isTaintable) {
|
|
10179
10180
|
const paramLine = param.line ?? method.start_line;
|
|
10180
10181
|
sources.push({
|
|
10181
10182
|
type: "interprocedural_param",
|
|
10182
|
-
location: `${param.type} ${param.name} in ${method.name}`,
|
|
10183
|
+
location: `${param.type || "any"} ${param.name} in ${method.name}`,
|
|
10183
10184
|
severity: "medium",
|
|
10184
10185
|
line: paramLine,
|
|
10185
|
-
confidence: 0.7
|
|
10186
|
-
// Lower confidence
|
|
10186
|
+
confidence: param.type ? 0.7 : 0.5
|
|
10187
|
+
// Lower confidence for untyped params
|
|
10187
10188
|
});
|
|
10188
10189
|
}
|
|
10189
10190
|
}
|
|
@@ -10209,7 +10210,15 @@ function findSources(calls, types, patterns) {
|
|
|
10209
10210
|
}
|
|
10210
10211
|
}
|
|
10211
10212
|
}
|
|
10212
|
-
|
|
10213
|
+
const sourceMap = /* @__PURE__ */ new Map();
|
|
10214
|
+
for (const source of sources) {
|
|
10215
|
+
const key = `${source.line}:${source.type}`;
|
|
10216
|
+
const existing = sourceMap.get(key);
|
|
10217
|
+
if (!existing || source.confidence > existing.confidence) {
|
|
10218
|
+
sourceMap.set(key, source);
|
|
10219
|
+
}
|
|
10220
|
+
}
|
|
10221
|
+
return Array.from(sourceMap.values());
|
|
10213
10222
|
}
|
|
10214
10223
|
function isInterproceduralTaintableType(typeName) {
|
|
10215
10224
|
const baseType = typeName.split("<")[0].trim();
|
|
@@ -10293,21 +10302,27 @@ function isInterproceduralTaintableType(typeName) {
|
|
|
10293
10302
|
return false;
|
|
10294
10303
|
}
|
|
10295
10304
|
function findSinks(calls, patterns) {
|
|
10296
|
-
const
|
|
10305
|
+
const sinkMap = /* @__PURE__ */ new Map();
|
|
10297
10306
|
for (const call of calls) {
|
|
10298
10307
|
for (const pattern of patterns) {
|
|
10299
10308
|
if (matchesSinkPattern(call, pattern)) {
|
|
10300
|
-
|
|
10301
|
-
|
|
10302
|
-
|
|
10303
|
-
|
|
10304
|
-
|
|
10305
|
-
|
|
10306
|
-
|
|
10309
|
+
const location = formatCallLocation(call);
|
|
10310
|
+
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
10311
|
+
const confidence = calculateSinkConfidence(call, pattern);
|
|
10312
|
+
const existing = sinkMap.get(key);
|
|
10313
|
+
if (!existing || confidence > existing.confidence) {
|
|
10314
|
+
sinkMap.set(key, {
|
|
10315
|
+
type: pattern.type,
|
|
10316
|
+
cwe: pattern.cwe,
|
|
10317
|
+
location,
|
|
10318
|
+
line: call.location.line,
|
|
10319
|
+
confidence
|
|
10320
|
+
});
|
|
10321
|
+
}
|
|
10307
10322
|
}
|
|
10308
10323
|
}
|
|
10309
10324
|
}
|
|
10310
|
-
return
|
|
10325
|
+
return Array.from(sinkMap.values());
|
|
10311
10326
|
}
|
|
10312
10327
|
function matchesSourcePattern(call, pattern) {
|
|
10313
10328
|
if (pattern.method) {
|
|
@@ -14269,16 +14284,36 @@ var JavaScriptPlugin = class extends BaseLanguagePlugin {
|
|
|
14269
14284
|
confidence = Math.max(confidence, 0.95);
|
|
14270
14285
|
indicators.push(`import: ${path}`);
|
|
14271
14286
|
}
|
|
14272
|
-
if (path === "react" || path.startsWith("react/")) {
|
|
14287
|
+
if (path === "react" || path.startsWith("react/") || path === "react-dom") {
|
|
14273
14288
|
framework = framework || "react";
|
|
14274
14289
|
confidence = Math.max(confidence, 0.8);
|
|
14275
14290
|
indicators.push(`import: ${path}`);
|
|
14276
14291
|
}
|
|
14292
|
+
if (path === "react-native" || path.startsWith("react-native/") || path.startsWith("@react-native/")) {
|
|
14293
|
+
framework = "react-native";
|
|
14294
|
+
confidence = Math.max(confidence, 0.95);
|
|
14295
|
+
indicators.push(`import: ${path}`);
|
|
14296
|
+
}
|
|
14297
|
+
if (path.startsWith("@react-navigation/")) {
|
|
14298
|
+
framework = framework || "react-native";
|
|
14299
|
+
confidence = Math.max(confidence, 0.9);
|
|
14300
|
+
indicators.push(`import: ${path}`);
|
|
14301
|
+
}
|
|
14302
|
+
if (path === "react-router" || path === "react-router-dom" || path.startsWith("react-router/")) {
|
|
14303
|
+
framework = framework || "react";
|
|
14304
|
+
confidence = Math.max(confidence, 0.85);
|
|
14305
|
+
indicators.push(`import: ${path}`);
|
|
14306
|
+
}
|
|
14277
14307
|
if (path === "next" || path.startsWith("next/")) {
|
|
14278
14308
|
framework = "nextjs";
|
|
14279
14309
|
confidence = Math.max(confidence, 0.9);
|
|
14280
14310
|
indicators.push(`import: ${path}`);
|
|
14281
14311
|
}
|
|
14312
|
+
if (path === "expo" || path.startsWith("expo-")) {
|
|
14313
|
+
framework = framework || "react-native";
|
|
14314
|
+
confidence = Math.max(confidence, 0.85);
|
|
14315
|
+
indicators.push(`import: ${path}`);
|
|
14316
|
+
}
|
|
14282
14317
|
}
|
|
14283
14318
|
if (framework) {
|
|
14284
14319
|
return { name: framework, confidence, indicators };
|
|
@@ -14390,6 +14425,167 @@ var JavaScriptPlugin = class extends BaseLanguagePlugin {
|
|
|
14390
14425
|
severity: "medium",
|
|
14391
14426
|
confidence: 0.8,
|
|
14392
14427
|
returnTainted: true
|
|
14428
|
+
},
|
|
14429
|
+
// =========================================================
|
|
14430
|
+
// React Router Sources
|
|
14431
|
+
// =========================================================
|
|
14432
|
+
{
|
|
14433
|
+
method: "useParams",
|
|
14434
|
+
type: "http_path",
|
|
14435
|
+
severity: "high",
|
|
14436
|
+
confidence: 0.95,
|
|
14437
|
+
returnTainted: true
|
|
14438
|
+
},
|
|
14439
|
+
{
|
|
14440
|
+
method: "useSearchParams",
|
|
14441
|
+
type: "http_param",
|
|
14442
|
+
severity: "high",
|
|
14443
|
+
confidence: 0.95,
|
|
14444
|
+
returnTainted: true
|
|
14445
|
+
},
|
|
14446
|
+
{
|
|
14447
|
+
method: "useLocation",
|
|
14448
|
+
type: "url_param",
|
|
14449
|
+
severity: "high",
|
|
14450
|
+
confidence: 0.9,
|
|
14451
|
+
returnTainted: true
|
|
14452
|
+
},
|
|
14453
|
+
// =========================================================
|
|
14454
|
+
// Next.js Sources
|
|
14455
|
+
// =========================================================
|
|
14456
|
+
{
|
|
14457
|
+
method: "useRouter",
|
|
14458
|
+
type: "http_param",
|
|
14459
|
+
severity: "high",
|
|
14460
|
+
confidence: 0.9,
|
|
14461
|
+
returnTainted: true
|
|
14462
|
+
// router.query, router.asPath
|
|
14463
|
+
},
|
|
14464
|
+
{
|
|
14465
|
+
method: "useSearchParams",
|
|
14466
|
+
// Next.js App Router
|
|
14467
|
+
type: "http_param",
|
|
14468
|
+
severity: "high",
|
|
14469
|
+
confidence: 0.95,
|
|
14470
|
+
returnTainted: true
|
|
14471
|
+
},
|
|
14472
|
+
{
|
|
14473
|
+
method: "usePathname",
|
|
14474
|
+
type: "http_path",
|
|
14475
|
+
severity: "high",
|
|
14476
|
+
confidence: 0.9,
|
|
14477
|
+
returnTainted: true
|
|
14478
|
+
},
|
|
14479
|
+
{
|
|
14480
|
+
// getServerSideProps/getStaticProps context.params
|
|
14481
|
+
method: "params",
|
|
14482
|
+
type: "http_path",
|
|
14483
|
+
severity: "high",
|
|
14484
|
+
confidence: 0.85,
|
|
14485
|
+
returnTainted: true
|
|
14486
|
+
},
|
|
14487
|
+
// =========================================================
|
|
14488
|
+
// React Native Sources
|
|
14489
|
+
// =========================================================
|
|
14490
|
+
{
|
|
14491
|
+
// React Navigation route params
|
|
14492
|
+
method: "useRoute",
|
|
14493
|
+
type: "navigation_param",
|
|
14494
|
+
severity: "high",
|
|
14495
|
+
confidence: 0.9,
|
|
14496
|
+
returnTainted: true
|
|
14497
|
+
},
|
|
14498
|
+
{
|
|
14499
|
+
// Deep linking
|
|
14500
|
+
method: "getInitialURL",
|
|
14501
|
+
class: "Linking",
|
|
14502
|
+
type: "url_param",
|
|
14503
|
+
severity: "high",
|
|
14504
|
+
confidence: 0.95,
|
|
14505
|
+
returnTainted: true
|
|
14506
|
+
},
|
|
14507
|
+
{
|
|
14508
|
+
method: "addEventListener",
|
|
14509
|
+
class: "Linking",
|
|
14510
|
+
type: "url_param",
|
|
14511
|
+
severity: "high",
|
|
14512
|
+
confidence: 0.9,
|
|
14513
|
+
returnTainted: true
|
|
14514
|
+
},
|
|
14515
|
+
{
|
|
14516
|
+
method: "parse",
|
|
14517
|
+
class: "Linking",
|
|
14518
|
+
type: "url_param",
|
|
14519
|
+
severity: "high",
|
|
14520
|
+
confidence: 0.9,
|
|
14521
|
+
returnTainted: true
|
|
14522
|
+
},
|
|
14523
|
+
{
|
|
14524
|
+
// Clipboard content
|
|
14525
|
+
method: "getString",
|
|
14526
|
+
class: "Clipboard",
|
|
14527
|
+
type: "user_input",
|
|
14528
|
+
severity: "medium",
|
|
14529
|
+
confidence: 0.85,
|
|
14530
|
+
returnTainted: true
|
|
14531
|
+
},
|
|
14532
|
+
{
|
|
14533
|
+
method: "getStringAsync",
|
|
14534
|
+
class: "Clipboard",
|
|
14535
|
+
type: "user_input",
|
|
14536
|
+
severity: "medium",
|
|
14537
|
+
confidence: 0.85,
|
|
14538
|
+
returnTainted: true
|
|
14539
|
+
},
|
|
14540
|
+
{
|
|
14541
|
+
// AsyncStorage (may contain user data)
|
|
14542
|
+
method: "getItem",
|
|
14543
|
+
class: "AsyncStorage",
|
|
14544
|
+
type: "storage_input",
|
|
14545
|
+
severity: "medium",
|
|
14546
|
+
confidence: 0.7,
|
|
14547
|
+
returnTainted: true
|
|
14548
|
+
},
|
|
14549
|
+
{
|
|
14550
|
+
method: "multiGet",
|
|
14551
|
+
class: "AsyncStorage",
|
|
14552
|
+
type: "storage_input",
|
|
14553
|
+
severity: "medium",
|
|
14554
|
+
confidence: 0.7,
|
|
14555
|
+
returnTainted: true
|
|
14556
|
+
},
|
|
14557
|
+
{
|
|
14558
|
+
// SecureStore (Expo)
|
|
14559
|
+
method: "getItemAsync",
|
|
14560
|
+
class: "SecureStore",
|
|
14561
|
+
type: "storage_input",
|
|
14562
|
+
severity: "medium",
|
|
14563
|
+
confidence: 0.7,
|
|
14564
|
+
returnTainted: true
|
|
14565
|
+
},
|
|
14566
|
+
// =========================================================
|
|
14567
|
+
// Browser/DOM Sources (React web apps)
|
|
14568
|
+
// =========================================================
|
|
14569
|
+
{
|
|
14570
|
+
method: "localStorage.getItem",
|
|
14571
|
+
type: "storage_input",
|
|
14572
|
+
severity: "medium",
|
|
14573
|
+
confidence: 0.7,
|
|
14574
|
+
returnTainted: true
|
|
14575
|
+
},
|
|
14576
|
+
{
|
|
14577
|
+
method: "sessionStorage.getItem",
|
|
14578
|
+
type: "storage_input",
|
|
14579
|
+
severity: "medium",
|
|
14580
|
+
confidence: 0.7,
|
|
14581
|
+
returnTainted: true
|
|
14582
|
+
},
|
|
14583
|
+
{
|
|
14584
|
+
method: "postMessage",
|
|
14585
|
+
type: "message_input",
|
|
14586
|
+
severity: "high",
|
|
14587
|
+
confidence: 0.85,
|
|
14588
|
+
returnTainted: true
|
|
14393
14589
|
}
|
|
14394
14590
|
];
|
|
14395
14591
|
}
|
|
@@ -14569,6 +14765,158 @@ var JavaScriptPlugin = class extends BaseLanguagePlugin {
|
|
|
14569
14765
|
cwe: "CWE-1321",
|
|
14570
14766
|
severity: "high",
|
|
14571
14767
|
argPositions: [0, 1]
|
|
14768
|
+
},
|
|
14769
|
+
// =========================================================
|
|
14770
|
+
// React XSS Sinks
|
|
14771
|
+
// =========================================================
|
|
14772
|
+
{
|
|
14773
|
+
// Most common React XSS vector
|
|
14774
|
+
method: "dangerouslySetInnerHTML",
|
|
14775
|
+
type: "xss",
|
|
14776
|
+
cwe: "CWE-79",
|
|
14777
|
+
severity: "critical",
|
|
14778
|
+
argPositions: [0]
|
|
14779
|
+
// The __html property value
|
|
14780
|
+
},
|
|
14781
|
+
{
|
|
14782
|
+
// Rendering user-controlled href with javascript:
|
|
14783
|
+
method: "href",
|
|
14784
|
+
type: "xss",
|
|
14785
|
+
cwe: "CWE-79",
|
|
14786
|
+
severity: "high",
|
|
14787
|
+
argPositions: [0]
|
|
14788
|
+
},
|
|
14789
|
+
{
|
|
14790
|
+
// createRef().current.innerHTML
|
|
14791
|
+
method: "current.innerHTML",
|
|
14792
|
+
type: "xss",
|
|
14793
|
+
cwe: "CWE-79",
|
|
14794
|
+
severity: "high",
|
|
14795
|
+
argPositions: [0]
|
|
14796
|
+
},
|
|
14797
|
+
// =========================================================
|
|
14798
|
+
// React Native Sinks
|
|
14799
|
+
// =========================================================
|
|
14800
|
+
{
|
|
14801
|
+
// WebView with user-controlled source
|
|
14802
|
+
method: "source",
|
|
14803
|
+
class: "WebView",
|
|
14804
|
+
type: "xss",
|
|
14805
|
+
cwe: "CWE-79",
|
|
14806
|
+
severity: "critical",
|
|
14807
|
+
argPositions: [0]
|
|
14808
|
+
// { html: userInput } or { uri: userInput }
|
|
14809
|
+
},
|
|
14810
|
+
{
|
|
14811
|
+
// Open arbitrary URLs
|
|
14812
|
+
method: "openURL",
|
|
14813
|
+
class: "Linking",
|
|
14814
|
+
type: "open_redirect",
|
|
14815
|
+
cwe: "CWE-601",
|
|
14816
|
+
severity: "high",
|
|
14817
|
+
argPositions: [0]
|
|
14818
|
+
},
|
|
14819
|
+
{
|
|
14820
|
+
method: "canOpenURL",
|
|
14821
|
+
class: "Linking",
|
|
14822
|
+
type: "ssrf",
|
|
14823
|
+
cwe: "CWE-918",
|
|
14824
|
+
severity: "medium",
|
|
14825
|
+
argPositions: [0]
|
|
14826
|
+
},
|
|
14827
|
+
{
|
|
14828
|
+
// Expo WebBrowser
|
|
14829
|
+
method: "openBrowserAsync",
|
|
14830
|
+
class: "WebBrowser",
|
|
14831
|
+
type: "open_redirect",
|
|
14832
|
+
cwe: "CWE-601",
|
|
14833
|
+
severity: "high",
|
|
14834
|
+
argPositions: [0]
|
|
14835
|
+
},
|
|
14836
|
+
{
|
|
14837
|
+
method: "openAuthSessionAsync",
|
|
14838
|
+
class: "WebBrowser",
|
|
14839
|
+
type: "open_redirect",
|
|
14840
|
+
cwe: "CWE-601",
|
|
14841
|
+
severity: "high",
|
|
14842
|
+
argPositions: [0]
|
|
14843
|
+
},
|
|
14844
|
+
// =========================================================
|
|
14845
|
+
// Next.js Sinks
|
|
14846
|
+
// =========================================================
|
|
14847
|
+
{
|
|
14848
|
+
// Server-side redirect
|
|
14849
|
+
method: "redirect",
|
|
14850
|
+
type: "open_redirect",
|
|
14851
|
+
cwe: "CWE-601",
|
|
14852
|
+
severity: "high",
|
|
14853
|
+
argPositions: [0]
|
|
14854
|
+
},
|
|
14855
|
+
{
|
|
14856
|
+
// Router push with user-controlled URL
|
|
14857
|
+
method: "push",
|
|
14858
|
+
class: "router",
|
|
14859
|
+
type: "open_redirect",
|
|
14860
|
+
cwe: "CWE-601",
|
|
14861
|
+
severity: "medium",
|
|
14862
|
+
argPositions: [0]
|
|
14863
|
+
},
|
|
14864
|
+
{
|
|
14865
|
+
method: "replace",
|
|
14866
|
+
class: "router",
|
|
14867
|
+
type: "open_redirect",
|
|
14868
|
+
cwe: "CWE-601",
|
|
14869
|
+
severity: "medium",
|
|
14870
|
+
argPositions: [0]
|
|
14871
|
+
},
|
|
14872
|
+
// =========================================================
|
|
14873
|
+
// React/General JS Security Sinks
|
|
14874
|
+
// =========================================================
|
|
14875
|
+
{
|
|
14876
|
+
// Dynamic component loading
|
|
14877
|
+
method: "createElement",
|
|
14878
|
+
class: "React",
|
|
14879
|
+
type: "code_injection",
|
|
14880
|
+
cwe: "CWE-94",
|
|
14881
|
+
severity: "high",
|
|
14882
|
+
argPositions: [0]
|
|
14883
|
+
// When first arg is user-controlled string
|
|
14884
|
+
},
|
|
14885
|
+
{
|
|
14886
|
+
// Importing user-controlled modules
|
|
14887
|
+
method: "import",
|
|
14888
|
+
type: "code_injection",
|
|
14889
|
+
cwe: "CWE-94",
|
|
14890
|
+
severity: "critical",
|
|
14891
|
+
argPositions: [0]
|
|
14892
|
+
},
|
|
14893
|
+
{
|
|
14894
|
+
method: "require",
|
|
14895
|
+
type: "code_injection",
|
|
14896
|
+
cwe: "CWE-94",
|
|
14897
|
+
severity: "critical",
|
|
14898
|
+
argPositions: [0]
|
|
14899
|
+
},
|
|
14900
|
+
// =========================================================
|
|
14901
|
+
// Data Exposure Sinks (React Native)
|
|
14902
|
+
// =========================================================
|
|
14903
|
+
{
|
|
14904
|
+
// Logging sensitive data
|
|
14905
|
+
method: "log",
|
|
14906
|
+
class: "console",
|
|
14907
|
+
type: "information_exposure",
|
|
14908
|
+
cwe: "CWE-532",
|
|
14909
|
+
severity: "low",
|
|
14910
|
+
argPositions: [0]
|
|
14911
|
+
},
|
|
14912
|
+
{
|
|
14913
|
+
// Storing sensitive data insecurely
|
|
14914
|
+
method: "setItem",
|
|
14915
|
+
class: "AsyncStorage",
|
|
14916
|
+
type: "insecure_storage",
|
|
14917
|
+
cwe: "CWE-922",
|
|
14918
|
+
severity: "medium",
|
|
14919
|
+
argPositions: [1]
|
|
14572
14920
|
}
|
|
14573
14921
|
];
|
|
14574
14922
|
}
|
|
@@ -15650,6 +15998,113 @@ function findGetterSources(types, instanceFieldTaint, sourceCode) {
|
|
|
15650
15998
|
}
|
|
15651
15999
|
return sources;
|
|
15652
16000
|
}
|
|
16001
|
+
var JS_DOM_XSS_SINKS = [
|
|
16002
|
+
{ pattern: /\.innerHTML\s*=/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
16003
|
+
{ pattern: /\.outerHTML\s*=/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
16004
|
+
{ pattern: /document\.write\s*\(/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
16005
|
+
{ pattern: /document\.writeln\s*\(/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
16006
|
+
{ pattern: /\.insertAdjacentHTML\s*\(/, type: "xss", cwe: "CWE-79", severity: "critical" },
|
|
16007
|
+
{ pattern: /\.src\s*=/, type: "xss", cwe: "CWE-79", severity: "high" },
|
|
16008
|
+
{ pattern: /\.href\s*=/, type: "xss", cwe: "CWE-79", severity: "high" }
|
|
16009
|
+
];
|
|
16010
|
+
var JS_TAINTED_PATTERNS = [
|
|
16011
|
+
{ pattern: /\breq\.query\b/, type: "http_param" },
|
|
16012
|
+
{ pattern: /\breq\.params\b/, type: "http_param" },
|
|
16013
|
+
{ pattern: /\breq\.body\b/, type: "http_body" },
|
|
16014
|
+
{ pattern: /\breq\.headers\b/, type: "http_header" },
|
|
16015
|
+
{ pattern: /\breq\.cookies\b/, type: "http_cookie" },
|
|
16016
|
+
{ pattern: /\breq\.url\b/, type: "http_path" },
|
|
16017
|
+
{ pattern: /\breq\.path\b/, type: "http_path" },
|
|
16018
|
+
{ pattern: /\breq\.originalUrl\b/, type: "http_path" },
|
|
16019
|
+
{ pattern: /\breq\.files?\b/, type: "file_input" },
|
|
16020
|
+
{ pattern: /\brequest\.query\b/, type: "http_param" },
|
|
16021
|
+
{ pattern: /\brequest\.params\b/, type: "http_param" },
|
|
16022
|
+
{ pattern: /\brequest\.body\b/, type: "http_body" },
|
|
16023
|
+
{ pattern: /\brequest\.headers\b/, type: "http_header" },
|
|
16024
|
+
{ pattern: /\bctx\.query\b/, type: "http_param" },
|
|
16025
|
+
{ pattern: /\bctx\.params\b/, type: "http_param" },
|
|
16026
|
+
{ pattern: /\bctx\.request\b/, type: "http_body" },
|
|
16027
|
+
{ pattern: /\bprocess\.env\b/, type: "env_input" },
|
|
16028
|
+
{ pattern: /\bprocess\.argv\b/, type: "io_input" },
|
|
16029
|
+
{ pattern: /\blocation\.search\b/, type: "http_param" },
|
|
16030
|
+
{ pattern: /\blocation\.hash\b/, type: "http_param" },
|
|
16031
|
+
{ pattern: /\blocation\.href\b/, type: "http_path" },
|
|
16032
|
+
{ pattern: /\bdocument\.getElementById\b/, type: "dom_input" },
|
|
16033
|
+
{ pattern: /\bdocument\.querySelector\b/, type: "dom_input" },
|
|
16034
|
+
{ pattern: /\.value\b/, type: "dom_input" }
|
|
16035
|
+
];
|
|
16036
|
+
function findJavaScriptAssignmentSources(sourceCode, language) {
|
|
16037
|
+
const sources = [];
|
|
16038
|
+
if (!["javascript", "typescript"].includes(language)) {
|
|
16039
|
+
return sources;
|
|
16040
|
+
}
|
|
16041
|
+
const lines = sourceCode.split("\n");
|
|
16042
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
16043
|
+
const line = lines[lineNum];
|
|
16044
|
+
const lineNumber = lineNum + 1;
|
|
16045
|
+
const assignmentMatch = line.match(/(?:(?:var|let|const)\s+)?(\w+)\s*=\s*(.+)/);
|
|
16046
|
+
if (assignmentMatch) {
|
|
16047
|
+
const varName = assignmentMatch[1];
|
|
16048
|
+
const rhs = assignmentMatch[2];
|
|
16049
|
+
for (const { pattern, type } of JS_TAINTED_PATTERNS) {
|
|
16050
|
+
if (pattern.test(rhs)) {
|
|
16051
|
+
const alreadyExists = sources.some(
|
|
16052
|
+
(s) => s.line === lineNumber && s.type === type
|
|
16053
|
+
);
|
|
16054
|
+
if (!alreadyExists) {
|
|
16055
|
+
sources.push({
|
|
16056
|
+
type,
|
|
16057
|
+
location: `${varName} = ${rhs.trim().substring(0, 50)}${rhs.length > 50 ? "..." : ""}`,
|
|
16058
|
+
severity: "high",
|
|
16059
|
+
line: lineNumber,
|
|
16060
|
+
confidence: 1,
|
|
16061
|
+
variable: varName
|
|
16062
|
+
});
|
|
16063
|
+
}
|
|
16064
|
+
break;
|
|
16065
|
+
}
|
|
16066
|
+
}
|
|
16067
|
+
}
|
|
16068
|
+
}
|
|
16069
|
+
return sources;
|
|
16070
|
+
}
|
|
16071
|
+
function findJavaScriptDOMSinks(sourceCode, language) {
|
|
16072
|
+
const sinks = [];
|
|
16073
|
+
if (!["javascript", "typescript"].includes(language)) {
|
|
16074
|
+
return sinks;
|
|
16075
|
+
}
|
|
16076
|
+
const lines = sourceCode.split("\n");
|
|
16077
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
16078
|
+
const line = lines[lineNum];
|
|
16079
|
+
const lineNumber = lineNum + 1;
|
|
16080
|
+
for (const { pattern, type, cwe, severity } of JS_DOM_XSS_SINKS) {
|
|
16081
|
+
if (pattern.test(line)) {
|
|
16082
|
+
let method = "innerHTML";
|
|
16083
|
+
if (line.includes(".outerHTML")) method = "outerHTML";
|
|
16084
|
+
else if (line.includes("document.write(")) method = "document.write";
|
|
16085
|
+
else if (line.includes("document.writeln(")) method = "document.writeln";
|
|
16086
|
+
else if (line.includes(".insertAdjacentHTML")) method = "insertAdjacentHTML";
|
|
16087
|
+
else if (line.includes(".src")) method = "src";
|
|
16088
|
+
else if (line.includes(".href")) method = "href";
|
|
16089
|
+
const alreadyExists = sinks.some(
|
|
16090
|
+
(s) => s.line === lineNumber && s.cwe === cwe
|
|
16091
|
+
);
|
|
16092
|
+
if (!alreadyExists) {
|
|
16093
|
+
sinks.push({
|
|
16094
|
+
type,
|
|
16095
|
+
cwe,
|
|
16096
|
+
severity,
|
|
16097
|
+
line: lineNumber,
|
|
16098
|
+
location: line.trim().substring(0, 80),
|
|
16099
|
+
method
|
|
16100
|
+
});
|
|
16101
|
+
}
|
|
16102
|
+
break;
|
|
16103
|
+
}
|
|
16104
|
+
}
|
|
16105
|
+
}
|
|
16106
|
+
return sinks;
|
|
16107
|
+
}
|
|
15653
16108
|
var initialized = false;
|
|
15654
16109
|
async function initAnalyzer(options = {}) {
|
|
15655
16110
|
if (initialized) return;
|
|
@@ -15802,11 +16257,30 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
15802
16257
|
const taint = analyzeTaint(calls, types, baseConfig);
|
|
15803
16258
|
const getterSources = findGetterSources(types, constPropResult.instanceFieldTaint, code);
|
|
15804
16259
|
taint.sources.push(...getterSources);
|
|
16260
|
+
const jsAssignmentSources = findJavaScriptAssignmentSources(code, language);
|
|
16261
|
+
taint.sources.push(...jsAssignmentSources);
|
|
16262
|
+
const jsDOMSinks = findJavaScriptDOMSinks(code, language);
|
|
16263
|
+
for (const domSink of jsDOMSinks) {
|
|
16264
|
+
const alreadyExists = taint.sinks.some(
|
|
16265
|
+
(s) => s.line === domSink.line && s.cwe === domSink.cwe
|
|
16266
|
+
);
|
|
16267
|
+
if (!alreadyExists) {
|
|
16268
|
+
taint.sinks.push({
|
|
16269
|
+
type: "xss",
|
|
16270
|
+
cwe: domSink.cwe,
|
|
16271
|
+
line: domSink.line,
|
|
16272
|
+
location: domSink.location,
|
|
16273
|
+
method: domSink.method,
|
|
16274
|
+
confidence: 1
|
|
16275
|
+
});
|
|
16276
|
+
}
|
|
16277
|
+
}
|
|
15805
16278
|
logger.debug("Initial taint analysis", {
|
|
15806
16279
|
sources: taint.sources.length,
|
|
15807
16280
|
sinks: taint.sinks.length,
|
|
15808
16281
|
sanitizers: taint.sanitizers?.length ?? 0,
|
|
15809
|
-
getterSources: getterSources.length
|
|
16282
|
+
getterSources: getterSources.length,
|
|
16283
|
+
jsDOMSinks: jsDOMSinks.length
|
|
15810
16284
|
});
|
|
15811
16285
|
taint.sinks = taint.sinks.filter((sink) => !constPropResult.unreachableLines.has(sink.line));
|
|
15812
16286
|
taint.sinks = filterCleanArraySinks(
|
|
@@ -10283,15 +10283,16 @@ function findSources(calls, types, patterns) {
|
|
|
10283
10283
|
const skipMethods = ["toString", "hashCode", "equals", "compareTo", "getDescription", "getVulnerabilityCount"];
|
|
10284
10284
|
if (skipMethods.includes(method.name)) continue;
|
|
10285
10285
|
for (const param of method.parameters) {
|
|
10286
|
-
|
|
10286
|
+
const isTaintable = param.type ? isInterproceduralTaintableType(param.type) : true;
|
|
10287
|
+
if (isTaintable) {
|
|
10287
10288
|
const paramLine = param.line ?? method.start_line;
|
|
10288
10289
|
sources.push({
|
|
10289
10290
|
type: "interprocedural_param",
|
|
10290
|
-
location: `${param.type} ${param.name} in ${method.name}`,
|
|
10291
|
+
location: `${param.type || "any"} ${param.name} in ${method.name}`,
|
|
10291
10292
|
severity: "medium",
|
|
10292
10293
|
line: paramLine,
|
|
10293
|
-
confidence: 0.7
|
|
10294
|
-
// Lower confidence
|
|
10294
|
+
confidence: param.type ? 0.7 : 0.5
|
|
10295
|
+
// Lower confidence for untyped params
|
|
10295
10296
|
});
|
|
10296
10297
|
}
|
|
10297
10298
|
}
|
|
@@ -10317,7 +10318,15 @@ function findSources(calls, types, patterns) {
|
|
|
10317
10318
|
}
|
|
10318
10319
|
}
|
|
10319
10320
|
}
|
|
10320
|
-
|
|
10321
|
+
const sourceMap = /* @__PURE__ */ new Map();
|
|
10322
|
+
for (const source of sources) {
|
|
10323
|
+
const key = `${source.line}:${source.type}`;
|
|
10324
|
+
const existing = sourceMap.get(key);
|
|
10325
|
+
if (!existing || source.confidence > existing.confidence) {
|
|
10326
|
+
sourceMap.set(key, source);
|
|
10327
|
+
}
|
|
10328
|
+
}
|
|
10329
|
+
return Array.from(sourceMap.values());
|
|
10321
10330
|
}
|
|
10322
10331
|
function isInterproceduralTaintableType(typeName) {
|
|
10323
10332
|
const baseType = typeName.split("<")[0].trim();
|
|
@@ -10401,21 +10410,27 @@ function isInterproceduralTaintableType(typeName) {
|
|
|
10401
10410
|
return false;
|
|
10402
10411
|
}
|
|
10403
10412
|
function findSinks(calls, patterns) {
|
|
10404
|
-
const
|
|
10413
|
+
const sinkMap = /* @__PURE__ */ new Map();
|
|
10405
10414
|
for (const call of calls) {
|
|
10406
10415
|
for (const pattern of patterns) {
|
|
10407
10416
|
if (matchesSinkPattern(call, pattern)) {
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10414
|
-
|
|
10417
|
+
const location = formatCallLocation(call);
|
|
10418
|
+
const key = `${location}:${call.location.line}:${pattern.cwe}`;
|
|
10419
|
+
const confidence = calculateSinkConfidence(call, pattern);
|
|
10420
|
+
const existing = sinkMap.get(key);
|
|
10421
|
+
if (!existing || confidence > existing.confidence) {
|
|
10422
|
+
sinkMap.set(key, {
|
|
10423
|
+
type: pattern.type,
|
|
10424
|
+
cwe: pattern.cwe,
|
|
10425
|
+
location,
|
|
10426
|
+
line: call.location.line,
|
|
10427
|
+
confidence
|
|
10428
|
+
});
|
|
10429
|
+
}
|
|
10415
10430
|
}
|
|
10416
10431
|
}
|
|
10417
10432
|
}
|
|
10418
|
-
return
|
|
10433
|
+
return Array.from(sinkMap.values());
|
|
10419
10434
|
}
|
|
10420
10435
|
function matchesSourcePattern(call, pattern) {
|
|
10421
10436
|
if (pattern.method) {
|