@clear-capabilities/agentic-security-scanner 0.151.3 → 0.153.0
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/CHANGELOG.md +125 -0
- package/dist/1336.index.js +16010 -0
- package/dist/agentic-security.mjs +80 -80
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +7 -6
- package/src/dataflow/CLAUDE.md +1 -1
- package/src/dataflow/backward.js +104 -7
- package/src/dataflow/catalog.js +802 -13
- package/src/dataflow/cross-service-taint.js +154 -11
- package/src/dataflow/engine.js +875 -90
- package/src/dataflow/exploit-prover.js +111 -23
- package/src/dataflow/incremental.js +13 -0
- package/src/dataflow/index.js +29 -6
- package/src/dataflow/path-feasibility.js +86 -9
- package/src/dataflow/string-domain.js +168 -0
- package/src/dataflow/summaries.js +69 -8
- package/src/engine.js +118 -9
- package/src/ir/CLAUDE.md +2 -1
- package/src/ir/balanced-call.js +33 -1
- package/src/ir/callgraph.js +26 -2
- package/src/ir/class-hierarchy.js +261 -7
- package/src/ir/csharp-ir.js +97 -8
- package/src/ir/index.js +21 -1
- package/src/ir/parser-cs.js +706 -24
- package/src/ir/parser-java.js +498 -17
- package/src/ir/parser-js.js +22 -0
- package/src/ir/parser-php.js +446 -43
- package/src/ir/parser-rust.js +1338 -0
- package/src/ir/php-include-merge.js +190 -0
- package/src/lineage/sink-registry.js +18 -0
- package/src/lineage/source-registry.js +23 -0
- package/src/posture/csharp-analysis.js +341 -43
- package/src/posture/finding-defaults.js +5 -1
- package/src/posture/fix-coverage.js +1 -0
- package/src/posture/proof-coverage.js +1 -0
- package/src/report/index.js +12 -0
- package/src/sast/CLAUDE.md +1 -1
- package/src/sast/authz.js +144 -1
- package/src/sast/crypto-protocol.js +65 -7
- package/src/sast/csharp-cross-file.js +125 -0
- package/src/sast/csharp-structural.js +78 -2
- package/src/sast/csharp.js +574 -42
- package/src/sast/java-ast-folding.js +268 -11
- package/src/sast/java-bench-extras.js +530 -2
- package/src/sast/java-deserialization.js +31 -2
- package/src/sast/java-structural-cross-file.js +146 -0
- package/src/sast/java-structural.js +499 -6
- package/src/sast/ldap-injection.js +133 -3
- package/src/sast/rust.js +57 -0
- package/src/sast/xss-reflected-multilang.js +252 -3
- package/src/dataflow/smt-feasibility.js +0 -189
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// SARD_80_F1 W5.41/W5.42 — cross-file literal-resolution SUPPRESSION for
|
|
2
|
+
// this codebase's Java taint-independent structural detectors
|
|
3
|
+
// (java-structural.js's SQLi/cmdi rules, java-bench-extras.js's CWE-601
|
|
4
|
+
// open-redirect rule).
|
|
5
|
+
//
|
|
6
|
+
// W5.40 root-caused java-structural.js's own remaining CWE-89 false-
|
|
7
|
+
// positive bucket (175 of 186 fps, on the real corpus) to Juliet's Flow
|
|
8
|
+
// Variant 51+ idiom: "data passed as an argument from one method to
|
|
9
|
+
// another, in a DIFFERENT class" — confirmed via a direct fetch of the
|
|
10
|
+
// public mirror's own `CWE89_SQL_Injection__database_executeQuery_51a.java`
|
|
11
|
+
// / `_51b.java` pair. File `_51a`'s `goodG2B()` calls
|
|
12
|
+
// `(new ..._51b()).goodG2BSink("foo")` with a hardcoded literal; the sink
|
|
13
|
+
// itself lives in `_51b`'s `goodG2BSink(String data)`, where `data` is a
|
|
14
|
+
// bare method PARAMETER with no same-file assignment at all. Every real
|
|
15
|
+
// caller of `goodG2BSink` passes a literal — but that caller lives in a
|
|
16
|
+
// SIBLING FILE, invisible to the detector's own same-file-only literal-
|
|
17
|
+
// tracking helper. W5.42 found the SAME idiom manifests identically in
|
|
18
|
+
// java-bench-extras.js's independent CWE-601 `response.sendRedirect`
|
|
19
|
+
// detector (a separate, independently-tuned copy of the same "nearest
|
|
20
|
+
// assignment is a literal" heuristic).
|
|
21
|
+
//
|
|
22
|
+
// This module is a SEPARATE, project-wide post-loop pass — NOT a change to
|
|
23
|
+
// either detector's per-file call signature — deliberately mirroring
|
|
24
|
+
// `csharp-cross-file.js`'s own W5.31 design of a standalone project-wide
|
|
25
|
+
// pass rather than threading extra context through the per-file detector
|
|
26
|
+
// cascade. `engine.js`'s `_runFileCascade` is explicitly documented as
|
|
27
|
+
// self-contained so it can run inside a worker thread for deep-mode scans;
|
|
28
|
+
// threading sibling-file content through that interface would require
|
|
29
|
+
// verifying both the sync AND worker code paths carry it correctly, a
|
|
30
|
+
// substantially larger and riskier change than this suppression-only pass,
|
|
31
|
+
// which only ever needs read access to the whole project's file contents —
|
|
32
|
+
// already available at the SAME post-loop stage `scanCsharpCrossFile`
|
|
33
|
+
// itself runs at in engine.js.
|
|
34
|
+
//
|
|
35
|
+
// Unlike csharp-cross-file.js (which ADDS genuinely new findings from
|
|
36
|
+
// cross-file taint), this module only ever SUPPRESSES an already-emitted
|
|
37
|
+
// finding: it re-derives the exact same "is the concatenated/redirected
|
|
38
|
+
// variable provably a literal" question each detector already asks, using
|
|
39
|
+
// the SAME regexes and the SAME literal-tracking mechanism (imported, not
|
|
40
|
+
// duplicated, to avoid drift), extended with an optional sibling-file
|
|
41
|
+
// fallback — see `java-structural.js`'s own `_resolveParamLiteralViaAllCallSites`
|
|
42
|
+
// header comment for why the fallback ALSO requires matching the sink's own
|
|
43
|
+
// declared class name, not just its bare method name: Juliet reuses the
|
|
44
|
+
// SAME generic method names (bad/badSink/goodG2B/goodG2BSink/…) identically
|
|
45
|
+
// across thousands of otherwise-unrelated flow-variant files living in the
|
|
46
|
+
// SAME directory, and blind scrambling preserves that collision by design
|
|
47
|
+
// (the same original word always hashes to the same opaque token,
|
|
48
|
+
// everywhere) — confirmed directly against the real corpus during W5.41's
|
|
49
|
+
// own verification (a bare-name search across one directory's ~980 sibling
|
|
50
|
+
// files matched hundreds of unrelated classes' own same-named methods,
|
|
51
|
+
// causing an unrelated caller elsewhere in the directory to make the check
|
|
52
|
+
// fail closed even for the intended pair).
|
|
53
|
+
//
|
|
54
|
+
// It never invents a new detection capability and never touches any other
|
|
55
|
+
// detector's findings.
|
|
56
|
+
//
|
|
57
|
+
// Scope, deliberately conservative and matching every other interprocedural
|
|
58
|
+
// mechanism in this codebase: SAME DIRECTORY ONLY (Juliet's own convention
|
|
59
|
+
// — a flow variant's split-file siblings always live together), and a real,
|
|
60
|
+
// non-literal call site ANYWHERE (same file or any sibling, for the SAME
|
|
61
|
+
// class) still fails the check closed, exactly like the same-file-only
|
|
62
|
+
// version already does — this can only ever suppress a finding that a
|
|
63
|
+
// same-file-only reading of every real caller would ALSO have deemed safe,
|
|
64
|
+
// not guess from partial information.
|
|
65
|
+
import { blankComments } from './_comment-strip.js';
|
|
66
|
+
import { deadBranchRanges, isLineInDeadRange } from './java-ast-folding.js';
|
|
67
|
+
import { RE as STRUCTURAL_RE, _trailingIdentIsLiteral } from './java-structural.js';
|
|
68
|
+
import { SEND_REDIRECT_RE, _nearestAssignIsLiteral } from './java-bench-extras.js';
|
|
69
|
+
|
|
70
|
+
function _parseProject(fileContents) {
|
|
71
|
+
const javaFiles = Object.entries(fileContents || {}).filter(([p, raw]) => /\.java$/i.test(p) && raw);
|
|
72
|
+
const byDir = new Map();
|
|
73
|
+
const parsedByFile = new Map(); // file -> { raw, code, deadRanges }
|
|
74
|
+
for (const [file, raw] of javaFiles) {
|
|
75
|
+
if (raw.length > 500_000) continue;
|
|
76
|
+
const dir = file.slice(0, file.lastIndexOf('/'));
|
|
77
|
+
if (!byDir.has(dir)) byDir.set(dir, []);
|
|
78
|
+
byDir.get(dir).push(file);
|
|
79
|
+
let code;
|
|
80
|
+
try { code = blankComments(raw); } catch { continue; }
|
|
81
|
+
let ranges = [];
|
|
82
|
+
try { ranges = deadBranchRanges(raw); } catch { /* parse error → no AST info, same fallback as the detectors themselves use */ }
|
|
83
|
+
parsedByFile.set(file, { raw, code, deadRanges: ranges });
|
|
84
|
+
}
|
|
85
|
+
return { javaFiles, byDir, parsedByFile };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function _siblingsOf(file, byDir, parsedByFile) {
|
|
89
|
+
const dir = file.slice(0, file.lastIndexOf('/'));
|
|
90
|
+
return (byDir.get(dir) || [])
|
|
91
|
+
.filter((f) => f !== file)
|
|
92
|
+
.map((f) => parsedByFile.get(f))
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.map((s) => ({ code: s.code, deadRanges: s.deadRanges }));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function computeJavaStructuralCrossFileSuppressions(fileContents) {
|
|
98
|
+
const drop = new Set();
|
|
99
|
+
const { javaFiles, byDir, parsedByFile } = _parseProject(fileContents);
|
|
100
|
+
if (javaFiles.length < 2) return drop;
|
|
101
|
+
|
|
102
|
+
for (const [file] of javaFiles) {
|
|
103
|
+
const self = parsedByFile.get(file);
|
|
104
|
+
if (!self) continue;
|
|
105
|
+
const siblingFiles = _siblingsOf(file, byDir, parsedByFile);
|
|
106
|
+
if (!siblingFiles.length) continue;
|
|
107
|
+
|
|
108
|
+
// java-structural.js's own SQLi/cmdi structural detectors.
|
|
109
|
+
for (const re of Object.values(STRUCTURAL_RE)) {
|
|
110
|
+
const r = new RegExp(re.source, re.flags);
|
|
111
|
+
let m;
|
|
112
|
+
while ((m = r.exec(self.code))) {
|
|
113
|
+
const varName = m[1];
|
|
114
|
+
if (!varName) continue;
|
|
115
|
+
if (self.deadRanges.length) {
|
|
116
|
+
const line0 = self.code.substring(0, m.index).split('\n').length;
|
|
117
|
+
// A sink already inside a dead range never produced a finding in
|
|
118
|
+
// the first place — nothing to suppress; skip re-deriving it here.
|
|
119
|
+
if (isLineInDeadRange(line0, self.deadRanges)) continue;
|
|
120
|
+
}
|
|
121
|
+
const resolved = _trailingIdentIsLiteral(self.code, varName, m.index, self.deadRanges, 0, siblingFiles);
|
|
122
|
+
if (resolved) {
|
|
123
|
+
const line = self.code.substring(0, m.index).split('\n').length;
|
|
124
|
+
drop.add(`${file}:${line}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// java-bench-extras.js's own CWE-601 response.sendRedirect detector.
|
|
130
|
+
const redirectRe = new RegExp(SEND_REDIRECT_RE.source, SEND_REDIRECT_RE.flags);
|
|
131
|
+
let rm;
|
|
132
|
+
while ((rm = redirectRe.exec(self.code))) {
|
|
133
|
+
const arg = (rm[1] || '').trim();
|
|
134
|
+
if (!/^[A-Za-z_]\w*$/.test(arg)) continue; // only a bare identifier is a literal-tracking candidate
|
|
135
|
+
if (self.deadRanges.length) {
|
|
136
|
+
const line0 = self.code.substring(0, rm.index).split('\n').length;
|
|
137
|
+
if (isLineInDeadRange(line0, self.deadRanges)) continue;
|
|
138
|
+
}
|
|
139
|
+
if (_nearestAssignIsLiteral(self.code, arg, rm.index, self.deadRanges, 0, siblingFiles)) {
|
|
140
|
+
const line = self.code.substring(0, rm.index).split('\n').length;
|
|
141
|
+
drop.add(`${file}:${line}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return drop;
|
|
146
|
+
}
|