circle-ir 3.8.0 → 3.8.3
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/LICENSE +18 -12
- package/README.md +10 -11
- package/configs/sinks/nodejs.json +58 -0
- package/dist/analysis/config-loader.js +8 -19
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/interprocedural.js +14 -0
- package/dist/analysis/interprocedural.js.map +1 -1
- package/dist/analyzer.d.ts +1 -1
- package/dist/analyzer.js +113 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +109 -21
- package/dist/core/circle-ir-core.cjs +8 -19
- package/dist/core/circle-ir-core.js +8 -19
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +1 -1
- package/dist/wasm/web-tree-sitter.wasm +0 -0
- package/docs/SPEC.md +7 -7
- package/examples/node-example.ts +7 -3
- package/package.json +10 -12
- package/wasm/tree-sitter-bash.wasm +0 -0
- package/wasm/tree-sitter-java.wasm +0 -0
- package/wasm/tree-sitter-javascript.wasm +0 -0
- package/wasm/tree-sitter-python.wasm +0 -0
- package/wasm/tree-sitter-rust.wasm +0 -0
|
@@ -9211,9 +9211,7 @@ var DEFAULT_SINKS = [
|
|
|
9211
9211
|
{ method: "resolveURI", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9212
9212
|
{ method: "resolve", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9213
9213
|
{ method: "getSource", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9214
|
-
// URL-
|
|
9215
|
-
{ method: "URL", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
9216
|
-
{ method: "openStream", class: "URL", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9214
|
+
// NOTE: new URL(userInput) is SSRF (CWE-918), not path traversal — see ssrf section below
|
|
9217
9215
|
// Servlet context resource loading
|
|
9218
9216
|
{ method: "getResource", class: "ServletContext", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9219
9217
|
{ method: "getResourceAsStream", class: "ServletContext", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -9250,8 +9248,7 @@ var DEFAULT_SINKS = [
|
|
|
9250
9248
|
{ method: "extract", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9251
9249
|
{ method: "extractAll", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9252
9250
|
{ method: "unjar", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9253
|
-
// Additional file constructors
|
|
9254
|
-
{ method: "BufferedReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9251
|
+
// Additional file constructors — BufferedReader(Reader) is NOT a path traversal sink; it wraps a Reader, not a file path
|
|
9255
9252
|
{ method: "PrintWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9256
9253
|
{ method: "Scanner", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9257
9254
|
// Topic/queue names (for message queue systems - can be exploited for path traversal)
|
|
@@ -9278,7 +9275,6 @@ var DEFAULT_SINKS = [
|
|
|
9278
9275
|
{ method: "getPath", class: "BaseFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9279
9276
|
{ method: "getPathMatcher", class: "BaseFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9280
9277
|
{ method: "getFileStores", class: "RootedFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9281
|
-
{ method: "deleteRecursive", class: "CommonTestSupportUtils", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9282
9278
|
// SftpFileSystemProvider
|
|
9283
9279
|
{ method: "move", class: "SftpFileSystemProvider", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
9284
9280
|
{ method: "copy", class: "SftpFileSystemProvider", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
@@ -9328,18 +9324,6 @@ var DEFAULT_SINKS = [
|
|
|
9328
9324
|
{ method: "createPlainAccessConfig", class: "MQClientAPIImpl", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9329
9325
|
// XWiki velocity introspector
|
|
9330
9326
|
{ method: "SecureIntrospector", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9331
|
-
// Generic test methods that process paths
|
|
9332
|
-
{ method: "testLifeCycle", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9333
|
-
{ method: "testPathAccess", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9334
|
-
{ method: "single", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9335
|
-
{ method: "invalidPath", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9336
|
-
{ method: "invalidPathWithPreviousDirectoryAllEncoded", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9337
|
-
// Embedded server test methods
|
|
9338
|
-
{ method: "create", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9339
|
-
{ method: "create_withThreadPool", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9340
|
-
{ method: "create_withNullThreadPool", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9341
|
-
// Camel file tests
|
|
9342
|
-
{ method: "testProducerComplexByExpression", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9343
9327
|
// XSS (CWE-79)
|
|
9344
9328
|
{ method: "write", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
9345
9329
|
{ method: "println", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -9774,9 +9758,12 @@ var DEFAULT_SINKS = [
|
|
|
9774
9758
|
{ method: "execSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9775
9759
|
{ method: "spawn", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9776
9760
|
{ method: "spawnSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9777
|
-
// Also match without receiver (destructured imports)
|
|
9761
|
+
// Also match without receiver (destructured imports: const { exec } = require('child_process'))
|
|
9778
9762
|
{ method: "exec", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9779
9763
|
{ method: "execSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9764
|
+
{ method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9765
|
+
{ method: "spawnSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9766
|
+
{ method: "execFile", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9780
9767
|
// Node.js File System (path traversal)
|
|
9781
9768
|
{ method: "readFile", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
9782
9769
|
{ method: "readFileSync", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
@@ -9819,7 +9806,9 @@ var DEFAULT_SINKS = [
|
|
|
9819
9806
|
{ method: "request", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9820
9807
|
{ method: "fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9821
9808
|
{ method: "request", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9809
|
+
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9822
9810
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9811
|
+
{ method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9823
9812
|
// needle library (used in NodeGoat)
|
|
9824
9813
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9825
9814
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -11311,7 +11300,20 @@ function analyzeInterprocedural(types, calls, dfg, sources, sinks, sanitizers, o
|
|
|
11311
11300
|
"hashCode",
|
|
11312
11301
|
"equals",
|
|
11313
11302
|
"clone",
|
|
11314
|
-
"clear"
|
|
11303
|
+
"clear",
|
|
11304
|
+
// StringBuilder / StringBuffer / Writer accumulator methods — taint propagates through these
|
|
11305
|
+
// but the CWE-668 sink check should not fire on pure string accumulation
|
|
11306
|
+
"append",
|
|
11307
|
+
"insert",
|
|
11308
|
+
"prepend",
|
|
11309
|
+
"concat",
|
|
11310
|
+
"delete",
|
|
11311
|
+
"deleteCharAt",
|
|
11312
|
+
"replace",
|
|
11313
|
+
"reverse",
|
|
11314
|
+
"write",
|
|
11315
|
+
"writeln",
|
|
11316
|
+
"println"
|
|
11315
11317
|
]);
|
|
11316
11318
|
const safeUtilityMethods = /* @__PURE__ */ new Set([
|
|
11317
11319
|
// Path validation and normalization
|
|
@@ -11342,7 +11344,25 @@ function analyzeInterprocedural(types, calls, dfg, sources, sinks, sanitizers, o
|
|
|
11342
11344
|
"validate",
|
|
11343
11345
|
"validateInput",
|
|
11344
11346
|
"check",
|
|
11345
|
-
"verify"
|
|
11347
|
+
"verify",
|
|
11348
|
+
// I/O stream wrappers — pure decorators that wrap a stream, not security sinks
|
|
11349
|
+
// e.g. new InputStreamReader(proc.getInputStream()) is safe; the underlying stream is the source
|
|
11350
|
+
"InputStreamReader",
|
|
11351
|
+
"OutputStreamWriter",
|
|
11352
|
+
"BufferedInputStream",
|
|
11353
|
+
"BufferedOutputStream",
|
|
11354
|
+
"ByteArrayInputStream",
|
|
11355
|
+
"ByteArrayOutputStream",
|
|
11356
|
+
"DataInputStream",
|
|
11357
|
+
"DataOutputStream",
|
|
11358
|
+
"PushbackInputStream",
|
|
11359
|
+
"SequenceInputStream",
|
|
11360
|
+
"BufferedReader",
|
|
11361
|
+
"BufferedWriter",
|
|
11362
|
+
"PrintStream",
|
|
11363
|
+
"PrintWriter",
|
|
11364
|
+
"ObjectOutputStream"
|
|
11365
|
+
// ObjectInputStream IS a sink (deserialization), keep it out
|
|
11346
11366
|
]);
|
|
11347
11367
|
const sanitizerMethods = /* @__PURE__ */ new Set();
|
|
11348
11368
|
for (const san of sanitizers) {
|
|
@@ -16726,6 +16746,26 @@ function buildPythonTaintedVars(sourceCode) {
|
|
|
16726
16746
|
}
|
|
16727
16747
|
return tainted;
|
|
16728
16748
|
}
|
|
16749
|
+
function buildJavaScriptTaintedVars(sourceCode, language) {
|
|
16750
|
+
if (!["javascript", "typescript"].includes(language)) return /* @__PURE__ */ new Map();
|
|
16751
|
+
const tainted = /* @__PURE__ */ new Map();
|
|
16752
|
+
const lines = sourceCode.split("\n");
|
|
16753
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
16754
|
+
const line = lines[i2];
|
|
16755
|
+
const trimmed = line.trimStart();
|
|
16756
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("*")) continue;
|
|
16757
|
+
const assignMatch = line.match(/(?:(?:var|let|const)\s+)?(\w+)\s*=\s*(.+)/);
|
|
16758
|
+
if (!assignMatch) continue;
|
|
16759
|
+
const [, lhs, rhs] = assignMatch;
|
|
16760
|
+
if (["if", "while", "for", "return", "true", "false", "null", "undefined", "case"].includes(lhs)) continue;
|
|
16761
|
+
const isDirectSource = JS_TAINTED_PATTERNS.some((p) => p.pattern.test(rhs));
|
|
16762
|
+
const isTaintedPropagation = tainted.size > 0 && [...tainted.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(rhs));
|
|
16763
|
+
if (isDirectSource || isTaintedPropagation) {
|
|
16764
|
+
tainted.set(lhs, i2 + 1);
|
|
16765
|
+
}
|
|
16766
|
+
}
|
|
16767
|
+
return tainted;
|
|
16768
|
+
}
|
|
16729
16769
|
function findPythonQuoteSanitizedVars(sourceCode) {
|
|
16730
16770
|
const sanitized = /* @__PURE__ */ new Set();
|
|
16731
16771
|
const lines = sourceCode.split("\n");
|
|
@@ -16771,6 +16811,25 @@ function findPythonTrustBoundaryViolations(sourceCode, language, taintedVars) {
|
|
|
16771
16811
|
}
|
|
16772
16812
|
return violations;
|
|
16773
16813
|
}
|
|
16814
|
+
function findPythonReturnXSSSinks(sourceCode, language, taintedVars) {
|
|
16815
|
+
if (language !== "python" || taintedVars.size === 0) return [];
|
|
16816
|
+
const sinks = [];
|
|
16817
|
+
const lines = sourceCode.split("\n");
|
|
16818
|
+
const taintedKeys = [...taintedVars.keys()];
|
|
16819
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
16820
|
+
const line = lines[i2];
|
|
16821
|
+
if (line.trimStart().startsWith("#")) continue;
|
|
16822
|
+
const returnMatch = line.match(/^\s*(?:return|yield)\s+(.+)$/);
|
|
16823
|
+
if (!returnMatch) continue;
|
|
16824
|
+
const expr = returnMatch[1];
|
|
16825
|
+
const hasTaintedVar = taintedKeys.some((v) => new RegExp(`\\b${v}\\b`).test(expr));
|
|
16826
|
+
if (!hasTaintedVar) continue;
|
|
16827
|
+
const looksLikeHTML = expr.includes("<") || /['"]\s*\+/.test(expr) || /\+\s*['"]/.test(expr) || /f['"][^'"]*\{/.test(expr);
|
|
16828
|
+
if (!looksLikeHTML) continue;
|
|
16829
|
+
sinks.push({ sinkLine: i2 + 1 });
|
|
16830
|
+
}
|
|
16831
|
+
return sinks;
|
|
16832
|
+
}
|
|
16774
16833
|
function findJavaScriptDOMSinks(sourceCode, language) {
|
|
16775
16834
|
const sinks = [];
|
|
16776
16835
|
if (!["javascript", "typescript"].includes(language)) {
|
|
@@ -17099,6 +17158,34 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
17099
17158
|
});
|
|
17100
17159
|
}
|
|
17101
17160
|
}
|
|
17161
|
+
const pyReturnXSS = findPythonReturnXSSSinks(code, language, pyTaintedVars);
|
|
17162
|
+
for (const r of pyReturnXSS) {
|
|
17163
|
+
const alreadyExists = taint.sinks.some(
|
|
17164
|
+
(s) => s.line === r.sinkLine && s.type === "xss"
|
|
17165
|
+
);
|
|
17166
|
+
if (!alreadyExists) {
|
|
17167
|
+
taint.sinks.push({
|
|
17168
|
+
type: "xss",
|
|
17169
|
+
cwe: "CWE-79",
|
|
17170
|
+
line: r.sinkLine,
|
|
17171
|
+
location: `return HTML with user input at line ${r.sinkLine}`,
|
|
17172
|
+
confidence: 0.9
|
|
17173
|
+
});
|
|
17174
|
+
}
|
|
17175
|
+
}
|
|
17176
|
+
}
|
|
17177
|
+
if (["javascript", "typescript"].includes(language)) {
|
|
17178
|
+
const jsTaintedVars = buildJavaScriptTaintedVars(code, language);
|
|
17179
|
+
if (jsTaintedVars.size > 0) {
|
|
17180
|
+
const jsSourceLines = code.split("\n");
|
|
17181
|
+
taint.sinks = taint.sinks.filter((sink) => {
|
|
17182
|
+
if (sink.type !== "xss") return true;
|
|
17183
|
+
const sinkLineText = jsSourceLines[sink.line - 1] ?? "";
|
|
17184
|
+
if ([...jsTaintedVars.keys()].some((v) => new RegExp(`\\b${v}\\b`).test(sinkLineText))) return true;
|
|
17185
|
+
if (JS_TAINTED_PATTERNS.some((p) => p.pattern.test(sinkLineText))) return true;
|
|
17186
|
+
return false;
|
|
17187
|
+
});
|
|
17188
|
+
}
|
|
17102
17189
|
}
|
|
17103
17190
|
if (taint.sources.length > 0 && taint.sinks.length > 0) {
|
|
17104
17191
|
const propagationResult = propagateTaint(
|
|
@@ -17219,6 +17306,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
17219
17306
|
}
|
|
17220
17307
|
);
|
|
17221
17308
|
for (const sink of interProc.propagatedSinks) {
|
|
17309
|
+
if (sink.type === "external_taint_escape") continue;
|
|
17222
17310
|
if (!taint.sinks.some((s) => s.line === sink.line)) {
|
|
17223
17311
|
taint.sinks.push(sink);
|
|
17224
17312
|
}
|
|
@@ -9319,9 +9319,7 @@ var DEFAULT_SINKS = [
|
|
|
9319
9319
|
{ method: "resolveURI", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9320
9320
|
{ method: "resolve", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9321
9321
|
{ method: "getSource", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9322
|
-
// URL-
|
|
9323
|
-
{ method: "URL", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
9324
|
-
{ method: "openStream", class: "URL", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9322
|
+
// NOTE: new URL(userInput) is SSRF (CWE-918), not path traversal — see ssrf section below
|
|
9325
9323
|
// Servlet context resource loading
|
|
9326
9324
|
{ method: "getResource", class: "ServletContext", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9327
9325
|
{ method: "getResourceAsStream", class: "ServletContext", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -9358,8 +9356,7 @@ var DEFAULT_SINKS = [
|
|
|
9358
9356
|
{ method: "extract", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9359
9357
|
{ method: "extractAll", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9360
9358
|
{ method: "unjar", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9361
|
-
// Additional file constructors
|
|
9362
|
-
{ method: "BufferedReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9359
|
+
// Additional file constructors — BufferedReader(Reader) is NOT a path traversal sink; it wraps a Reader, not a file path
|
|
9363
9360
|
{ method: "PrintWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9364
9361
|
{ method: "Scanner", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9365
9362
|
// Topic/queue names (for message queue systems - can be exploited for path traversal)
|
|
@@ -9386,7 +9383,6 @@ var DEFAULT_SINKS = [
|
|
|
9386
9383
|
{ method: "getPath", class: "BaseFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9387
9384
|
{ method: "getPathMatcher", class: "BaseFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9388
9385
|
{ method: "getFileStores", class: "RootedFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9389
|
-
{ method: "deleteRecursive", class: "CommonTestSupportUtils", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9390
9386
|
// SftpFileSystemProvider
|
|
9391
9387
|
{ method: "move", class: "SftpFileSystemProvider", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
9392
9388
|
{ method: "copy", class: "SftpFileSystemProvider", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
@@ -9436,18 +9432,6 @@ var DEFAULT_SINKS = [
|
|
|
9436
9432
|
{ method: "createPlainAccessConfig", class: "MQClientAPIImpl", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9437
9433
|
// XWiki velocity introspector
|
|
9438
9434
|
{ method: "SecureIntrospector", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9439
|
-
// Generic test methods that process paths
|
|
9440
|
-
{ method: "testLifeCycle", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9441
|
-
{ method: "testPathAccess", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9442
|
-
{ method: "single", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9443
|
-
{ method: "invalidPath", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9444
|
-
{ method: "invalidPathWithPreviousDirectoryAllEncoded", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9445
|
-
// Embedded server test methods
|
|
9446
|
-
{ method: "create", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9447
|
-
{ method: "create_withThreadPool", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9448
|
-
{ method: "create_withNullThreadPool", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9449
|
-
// Camel file tests
|
|
9450
|
-
{ method: "testProducerComplexByExpression", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9451
9435
|
// XSS (CWE-79)
|
|
9452
9436
|
{ method: "write", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
9453
9437
|
{ method: "println", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -9882,9 +9866,12 @@ var DEFAULT_SINKS = [
|
|
|
9882
9866
|
{ method: "execSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9883
9867
|
{ method: "spawn", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9884
9868
|
{ method: "spawnSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9885
|
-
// Also match without receiver (destructured imports)
|
|
9869
|
+
// Also match without receiver (destructured imports: const { exec } = require('child_process'))
|
|
9886
9870
|
{ method: "exec", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9887
9871
|
{ method: "execSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9872
|
+
{ method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9873
|
+
{ method: "spawnSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9874
|
+
{ method: "execFile", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9888
9875
|
// Node.js File System (path traversal)
|
|
9889
9876
|
{ method: "readFile", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
9890
9877
|
{ method: "readFileSync", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
@@ -9927,7 +9914,9 @@ var DEFAULT_SINKS = [
|
|
|
9927
9914
|
{ method: "request", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9928
9915
|
{ method: "fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9929
9916
|
{ method: "request", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9917
|
+
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9930
9918
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9919
|
+
{ method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9931
9920
|
// needle library (used in NodeGoat)
|
|
9932
9921
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9933
9922
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
@@ -9254,9 +9254,7 @@ var DEFAULT_SINKS = [
|
|
|
9254
9254
|
{ method: "resolveURI", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9255
9255
|
{ method: "resolve", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9256
9256
|
{ method: "getSource", class: "SourceResolver", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9257
|
-
// URL-
|
|
9258
|
-
{ method: "URL", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [0] },
|
|
9259
|
-
{ method: "openStream", class: "URL", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9257
|
+
// NOTE: new URL(userInput) is SSRF (CWE-918), not path traversal — see ssrf section below
|
|
9260
9258
|
// Servlet context resource loading
|
|
9261
9259
|
{ method: "getResource", class: "ServletContext", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9262
9260
|
{ method: "getResourceAsStream", class: "ServletContext", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
@@ -9293,8 +9291,7 @@ var DEFAULT_SINKS = [
|
|
|
9293
9291
|
{ method: "extract", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9294
9292
|
{ method: "extractAll", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9295
9293
|
{ method: "unjar", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0, 1] },
|
|
9296
|
-
// Additional file constructors
|
|
9297
|
-
{ method: "BufferedReader", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9294
|
+
// Additional file constructors — BufferedReader(Reader) is NOT a path traversal sink; it wraps a Reader, not a file path
|
|
9298
9295
|
{ method: "PrintWriter", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9299
9296
|
{ method: "Scanner", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9300
9297
|
// Topic/queue names (for message queue systems - can be exploited for path traversal)
|
|
@@ -9321,7 +9318,6 @@ var DEFAULT_SINKS = [
|
|
|
9321
9318
|
{ method: "getPath", class: "BaseFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9322
9319
|
{ method: "getPathMatcher", class: "BaseFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9323
9320
|
{ method: "getFileStores", class: "RootedFileSystem", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9324
|
-
{ method: "deleteRecursive", class: "CommonTestSupportUtils", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9325
9321
|
// SftpFileSystemProvider
|
|
9326
9322
|
{ method: "move", class: "SftpFileSystemProvider", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
9327
9323
|
{ method: "copy", class: "SftpFileSystemProvider", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0, 1] },
|
|
@@ -9371,18 +9367,6 @@ var DEFAULT_SINKS = [
|
|
|
9371
9367
|
{ method: "createPlainAccessConfig", class: "MQClientAPIImpl", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9372
9368
|
// XWiki velocity introspector
|
|
9373
9369
|
{ method: "SecureIntrospector", class: "constructor", type: "path_traversal", cwe: "CWE-22", severity: "high", arg_positions: [0] },
|
|
9374
|
-
// Generic test methods that process paths
|
|
9375
|
-
{ method: "testLifeCycle", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9376
|
-
{ method: "testPathAccess", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9377
|
-
{ method: "single", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9378
|
-
{ method: "invalidPath", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9379
|
-
{ method: "invalidPathWithPreviousDirectoryAllEncoded", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9380
|
-
// Embedded server test methods
|
|
9381
|
-
{ method: "create", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9382
|
-
{ method: "create_withThreadPool", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9383
|
-
{ method: "create_withNullThreadPool", class: "EmbeddedJettyFactoryTest", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9384
|
-
// Camel file tests
|
|
9385
|
-
{ method: "testProducerComplexByExpression", type: "path_traversal", cwe: "CWE-22", severity: "medium", arg_positions: [] },
|
|
9386
9370
|
// XSS (CWE-79)
|
|
9387
9371
|
{ method: "write", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
9388
9372
|
{ method: "println", class: "PrintWriter", type: "xss", cwe: "CWE-79", severity: "high", arg_positions: [0] },
|
|
@@ -9817,9 +9801,12 @@ var DEFAULT_SINKS = [
|
|
|
9817
9801
|
{ method: "execSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9818
9802
|
{ method: "spawn", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9819
9803
|
{ method: "spawnSync", class: "child_process", type: "command_injection", cwe: "CWE-78", severity: "critical", arg_positions: [0] },
|
|
9820
|
-
// Also match without receiver (destructured imports)
|
|
9804
|
+
// Also match without receiver (destructured imports: const { exec } = require('child_process'))
|
|
9821
9805
|
{ method: "exec", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9822
9806
|
{ method: "execSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9807
|
+
{ method: "spawn", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9808
|
+
{ method: "spawnSync", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9809
|
+
{ method: "execFile", type: "command_injection", cwe: "CWE-78", severity: "high", arg_positions: [0] },
|
|
9823
9810
|
// Node.js File System (path traversal)
|
|
9824
9811
|
{ method: "readFile", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
9825
9812
|
{ method: "readFileSync", class: "fs", type: "path_traversal", cwe: "CWE-22", severity: "critical", arg_positions: [0] },
|
|
@@ -9862,7 +9849,9 @@ var DEFAULT_SINKS = [
|
|
|
9862
9849
|
{ method: "request", class: "axios", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9863
9850
|
{ method: "fetch", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9864
9851
|
{ method: "request", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9852
|
+
{ method: "get", class: "http", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9865
9853
|
{ method: "request", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9854
|
+
{ method: "get", class: "https", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9866
9855
|
// needle library (used in NodeGoat)
|
|
9867
9856
|
{ method: "get", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
|
9868
9857
|
{ method: "post", class: "needle", type: "ssrf", cwe: "CWE-918", severity: "high", arg_positions: [0] },
|
package/dist/utils/logger.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* logger.info('Processing file', { file: 'test.java' });
|
|
10
10
|
* logger.error('Failed to parse', { error: err.message });
|
|
11
11
|
*
|
|
12
|
-
* Injecting a custom logger (e.g.
|
|
12
|
+
* Injecting a custom logger (e.g. pino):
|
|
13
13
|
* import pino from 'pino';
|
|
14
14
|
* import { setLogger } from 'circle-ir';
|
|
15
15
|
* setLogger(pino({ level: 'debug' }));
|
package/dist/utils/logger.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* logger.info('Processing file', { file: 'test.java' });
|
|
10
10
|
* logger.error('Failed to parse', { error: err.message });
|
|
11
11
|
*
|
|
12
|
-
* Injecting a custom logger (e.g.
|
|
12
|
+
* Injecting a custom logger (e.g. pino):
|
|
13
13
|
* import pino from 'pino';
|
|
14
14
|
* import { setLogger } from 'circle-ir';
|
|
15
15
|
* setLogger(pino({ level: 'debug' }));
|
|
Binary file
|
package/docs/SPEC.md
CHANGED
|
@@ -952,9 +952,9 @@ interface Vulnerability {
|
|
|
952
952
|
|
|
953
953
|
---
|
|
954
954
|
|
|
955
|
-
##
|
|
955
|
+
## Implementation Status
|
|
956
956
|
|
|
957
|
-
### Phase 1: Core
|
|
957
|
+
### Phase 1: Core
|
|
958
958
|
- [x] Meta extraction
|
|
959
959
|
- [x] Type extraction (classes, interfaces, methods, fields)
|
|
960
960
|
- [x] Call extraction (method invocations, arguments)
|
|
@@ -965,18 +965,18 @@ interface Vulnerability {
|
|
|
965
965
|
- [x] Import extraction
|
|
966
966
|
- [x] JSON serialization matching spec
|
|
967
967
|
|
|
968
|
-
### Phase 2: Enhanced
|
|
968
|
+
### Phase 2: Enhanced
|
|
969
969
|
- [x] Export extraction
|
|
970
970
|
- [x] Call resolution tracking
|
|
971
971
|
- [x] Sanitizer detection
|
|
972
972
|
- [x] DFG chains computation
|
|
973
973
|
|
|
974
|
-
### Phase 3:
|
|
975
|
-
- [x] Unresolved section population
|
|
976
|
-
- [x] Enriched section
|
|
974
|
+
### Phase 3: Extension Points
|
|
975
|
+
- [x] Unresolved section population (static analysis identifies unresolvable patterns)
|
|
976
|
+
- [x] Enriched section schema (optional, populated by analysis consumers)
|
|
977
977
|
- [x] Finding generation
|
|
978
978
|
|
|
979
|
-
### Phase 4: Project-Level
|
|
979
|
+
### Phase 4: Project-Level
|
|
980
980
|
- [x] Cross-file call graph
|
|
981
981
|
- [x] Type hierarchy
|
|
982
982
|
- [x] Taint path enumeration
|
package/examples/node-example.ts
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Node.js Example - Circle-IR Core Library
|
|
4
4
|
*
|
|
5
|
-
* This example demonstrates how to use circle-ir
|
|
5
|
+
* This example demonstrates how to use circle-ir to:
|
|
6
6
|
* 1. Parse Java code
|
|
7
7
|
* 2. Extract IR components (types, calls, CFG, DFG)
|
|
8
8
|
* 3. Detect taint sources and sinks
|
|
9
9
|
* 4. Find source-to-sink flows
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* Setup:
|
|
12
|
+
* npm install circle-ir
|
|
13
|
+
*
|
|
14
|
+
* Run:
|
|
15
|
+
* npx tsx node-example.ts
|
|
12
16
|
*/
|
|
13
17
|
|
|
14
18
|
import {
|
|
@@ -25,7 +29,7 @@ import {
|
|
|
25
29
|
analyzeConstantPropagation,
|
|
26
30
|
isFalsePositive,
|
|
27
31
|
getDefaultConfig,
|
|
28
|
-
} from '
|
|
32
|
+
} from 'circle-ir/core';
|
|
29
33
|
|
|
30
34
|
// Sample vulnerable Java code
|
|
31
35
|
const vulnerableCode = `
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circle-ir",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.3",
|
|
4
4
|
"description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
"dist",
|
|
27
27
|
"configs",
|
|
28
28
|
"examples",
|
|
29
|
-
"wasm",
|
|
30
29
|
"docs/SPEC.md"
|
|
31
30
|
],
|
|
32
31
|
"scripts": {
|
|
@@ -54,6 +53,8 @@
|
|
|
54
53
|
"typescript",
|
|
55
54
|
"python",
|
|
56
55
|
"rust",
|
|
56
|
+
"bash",
|
|
57
|
+
"shell",
|
|
57
58
|
"tree-sitter",
|
|
58
59
|
"sql-injection",
|
|
59
60
|
"xss",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
"name": "Cognium Labs",
|
|
66
67
|
"url": "https://github.com/cogniumhq"
|
|
67
68
|
},
|
|
68
|
-
"license": "
|
|
69
|
+
"license": "MIT",
|
|
69
70
|
"repository": {
|
|
70
71
|
"type": "git",
|
|
71
72
|
"url": "git+https://github.com/cogniumhq/circle-ir.git"
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
},
|
|
77
78
|
"funding": {
|
|
78
79
|
"type": "github",
|
|
79
|
-
"url": "https://github.com/sponsors/
|
|
80
|
+
"url": "https://github.com/sponsors/cogniumhq"
|
|
80
81
|
},
|
|
81
82
|
"engines": {
|
|
82
83
|
"node": ">=18.0.0"
|
|
@@ -87,21 +88,18 @@
|
|
|
87
88
|
"registry": "https://registry.npmjs.org/"
|
|
88
89
|
},
|
|
89
90
|
"dependencies": {
|
|
90
|
-
"web-tree-sitter": "^0.26.
|
|
91
|
+
"web-tree-sitter": "^0.26.7",
|
|
91
92
|
"yaml": "^2.8.2"
|
|
92
93
|
},
|
|
93
94
|
"devDependencies": {
|
|
94
|
-
"@types/node": "^25.0
|
|
95
|
-
"@
|
|
96
|
-
"
|
|
97
|
-
"esbuild": "^0.27.2",
|
|
95
|
+
"@types/node": "^25.5.0",
|
|
96
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
97
|
+
"esbuild": "^0.27.4",
|
|
98
98
|
"tree-sitter-bash": "^0.25.1",
|
|
99
99
|
"tree-sitter-java": "^0.23.5",
|
|
100
100
|
"tree-sitter-python": "^0.25.0",
|
|
101
101
|
"tree-sitter-rust": "^0.24.0",
|
|
102
|
-
"ts-node": "^10.9.2",
|
|
103
102
|
"typescript": "^5.9.3",
|
|
104
|
-
"
|
|
105
|
-
"vitest": "^3.0.0"
|
|
103
|
+
"vitest": "^4.1.0"
|
|
106
104
|
}
|
|
107
105
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|