@clear-capabilities/agentic-security-scanner 0.153.0 → 0.153.2
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.
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
dce16625abaef7a52aad4a8d0ca0402d22ea681c9a1479f7378401458e38d55e agentic-security.mjs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clear-capabilities/agentic-security-scanner",
|
|
3
|
-
"version": "0.153.
|
|
3
|
+
"version": "0.153.2",
|
|
4
4
|
"description": "Scanner engine for the agentic-security Claude Code plugin — SAST, SCA (function-level reachability + CISA KEV), secrets, IaC, prompt-injection, MCP/agent-tool audit, auth/authZ deep analysis, attack chains, PoC generation, business logic, toxic-combinations scoring, SBOM, pipeline integrity, compliance attestation, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"z3-solver": "^5.2.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@types/node": "^26.
|
|
55
|
+
"@types/node": "^26.6.4",
|
|
56
56
|
"@vercel/ncc": "^0.45.0",
|
|
57
57
|
"java-parser": "^3.0.1"
|
|
58
58
|
},
|
package/src/engine.js
CHANGED
|
@@ -5700,19 +5700,29 @@ function _isLikelyUnsafeRegex(body){
|
|
|
5700
5700
|
// the first required literal). redos-nfa.js is consulted as a second
|
|
5701
5701
|
// opinion for the intrinsic case only, since it does not model the
|
|
5702
5702
|
// polynomial one.
|
|
5703
|
-
|
|
5703
|
+
const ambiguousAlt = _hasOverlappingQuantifiedAlternation(body);
|
|
5704
|
+
if (!_hasNestedQuantifier(body) && !_hasLeadingUnboundedQuantifier(body) && !ambiguousAlt) return false;
|
|
5704
5705
|
// FIX-DISCRIMINATION (GHSA-29g2-3rmr-qm68): an anchored pattern with no
|
|
5705
5706
|
// nested quantifier has neither failure mode — no start-offset multiplier,
|
|
5706
5707
|
// no intrinsic catastrophe. safe-regex's star-height heuristic cannot see
|
|
5707
5708
|
// anchoring at all and flagged the fixed revision identically to the
|
|
5708
5709
|
// vulnerable one, so the finding survived its own fix.
|
|
5709
|
-
if (_isAnchoredRegex(body) && !_hasNestedQuantifier(body)) return false;
|
|
5710
|
+
if (_isAnchoredRegex(body) && !_hasNestedQuantifier(body) && !ambiguousAlt) return false;
|
|
5710
5711
|
if (_safeRegex) {
|
|
5711
5712
|
try { if (!_safeRegex(body)) return true; } catch (_) { /* fall through */ }
|
|
5712
5713
|
}
|
|
5713
|
-
// safe-regex's star-height analysis misses `(a|aa)*`
|
|
5714
|
-
|
|
5715
|
-
|
|
5714
|
+
// safe-regex's star-height analysis misses `(a|aa)*` (see the helper below).
|
|
5715
|
+
return ambiguousAlt;
|
|
5716
|
+
}
|
|
5717
|
+
/**
|
|
5718
|
+
* `(X|Y…)[*+]` whose alternatives can start with the same character — `(a|aa)*`.
|
|
5719
|
+
* The overlap makes the engine fork on every iteration, which is catastrophic
|
|
5720
|
+
* backtracking even at star-height 1, and like a nested quantifier it is
|
|
5721
|
+
* intrinsic: anchoring does not cure it. A third mechanism alongside nested
|
|
5722
|
+
* quantifiers and scan-and-retry; the gate in _isLikelyUnsafeRegex must admit
|
|
5723
|
+
* it, or `/^(a|aa)*$/` is rejected before this check is ever reached.
|
|
5724
|
+
*/
|
|
5725
|
+
function _hasOverlappingQuantifiedAlternation(body){
|
|
5716
5726
|
const altQuantRe = /\((?:\?:)?([^()]+)\)[*+]/g;
|
|
5717
5727
|
let am;
|
|
5718
5728
|
while ((am = altQuantRe.exec(body)) !== null) {
|
|
@@ -123,6 +123,20 @@ function _lang(fp) {
|
|
|
123
123
|
return null;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// True when `idx` sits inside a '…' or "…" literal on its own line. A sink name
|
|
127
|
+
// quoted in a string (a rule library's `fix:`/`example:` text, a log message) is
|
|
128
|
+
// prose, not a call. Backticks are deliberately not tracked: a template literal's
|
|
129
|
+
// `${…}` holds real code, and erring toward "this is code" is the safe direction.
|
|
130
|
+
function _insideQuote(code, idx) {
|
|
131
|
+
let q = null;
|
|
132
|
+
for (let i = code.lastIndexOf('\n', idx - 1) + 1; i < idx; i++) {
|
|
133
|
+
const c = code[i];
|
|
134
|
+
if (q) { if (c === '\\') i++; else if (c === q) q = null; }
|
|
135
|
+
else if (c === '\'' || c === '"') q = c;
|
|
136
|
+
}
|
|
137
|
+
return q !== null;
|
|
138
|
+
}
|
|
139
|
+
|
|
126
140
|
export function scanCodeInjectionMultilang(fp, raw) {
|
|
127
141
|
if (!raw || raw.length > 500_000) return [];
|
|
128
142
|
const lang = _lang(fp);
|
|
@@ -137,6 +151,7 @@ export function scanCodeInjectionMultilang(fp, raw) {
|
|
|
137
151
|
const r = new RegExp(re.source, re.flags);
|
|
138
152
|
let m;
|
|
139
153
|
while ((m = r.exec(code))) {
|
|
154
|
+
if (_insideQuote(code, m.index + Math.max(0, m[0].search(/\w/)))) continue;
|
|
140
155
|
const line = lineOf(code, m.index);
|
|
141
156
|
const id = `code-injection-ml:${fp}:${line}:${key}`;
|
|
142
157
|
if (seen.has(id)) continue;
|
package/src/sast/go-extended.js
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
// - exec.Command shell-form exec.Command("sh", "-c", <var>) vs argv-form
|
|
6
6
|
// - http.Client custom-transport http.Get(<var>) or http.NewRequest with
|
|
7
7
|
// user-controlled URL and no allowlist
|
|
8
|
+
// - package-level http.Get/Post runs on http.DefaultClient, which has no
|
|
9
|
+
// timeout (DoS: one stalled upstream pins
|
|
10
|
+
// the goroutine and its connection forever)
|
|
8
11
|
//
|
|
9
12
|
// These patterns are narrow and complement the existing regex-based Go rules
|
|
10
13
|
// (GORM raw SQL, source patterns for net/http, Echo, Chi, Gin).
|
|
@@ -38,6 +41,14 @@ const FINDINGS = [
|
|
|
38
41
|
vuln: 'SSRF — http.Get/Post with variable URL',
|
|
39
42
|
remediation: 'Allowlist the destination host before any net/http call. Use `net/url.Parse(target)` then check `parsed.Host` against an explicit allowlist. Reject RFC1918 (10/8, 172.16/12, 192.168/16) and the cloud metadata addresses (169.254.169.254, fd00:ec2::254).',
|
|
40
43
|
},
|
|
44
|
+
{
|
|
45
|
+
id: 'go-http-no-timeout', severity: 'medium', cwe: 'CWE-400', family: 'dos-no-timeout',
|
|
46
|
+
// Only the package-level helpers. `client.Get(...)` is the caller's own
|
|
47
|
+
// client, which may well set Timeout, so it is deliberately not matched.
|
|
48
|
+
re: /\bhttp\.(?:Get|Post|Head|PostForm)\s*\(/g,
|
|
49
|
+
vuln: 'Missing Timeout on Outbound HTTP Request (DoS)',
|
|
50
|
+
remediation: 'http.Get/Post/Head/PostForm use http.DefaultClient, which never times out. Build a client instead: `c := &http.Client{Timeout: 10 * time.Second}` and call `c.Get(...)`, or pass a context with a deadline to http.NewRequestWithContext.',
|
|
51
|
+
},
|
|
41
52
|
{
|
|
42
53
|
id: 'go-newrequest-user-url', severity: 'high', cwe: 'CWE-918', family: 'ssrf',
|
|
43
54
|
re: /\bhttp\.NewRequest(?:WithContext)?\s*\(\s*[^,]+,\s*(?!"[^"]*"\s*[,)])[a-zA-Z_]\w*/g,
|