@clear-capabilities/agentic-security-scanner 0.77.0 → 0.79.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.
Files changed (69) hide show
  1. package/bin/.agentic-security/findings.json +1907 -0
  2. package/bin/.agentic-security/last-scan.json +1907 -0
  3. package/bin/.agentic-security/last-scan.json.sig +1 -0
  4. package/bin/.agentic-security/scan-history.json +166 -0
  5. package/bin/.agentic-security/streak.json +20 -0
  6. package/bin/agentic-security.js +55 -9
  7. package/dist/178.index.js +1 -1
  8. package/dist/384.index.js +1 -1
  9. package/dist/476.index.js +5 -5
  10. package/dist/637.index.js +1 -1
  11. package/dist/700.index.js +138 -0
  12. package/dist/718.index.js +159 -0
  13. package/dist/824.index.js +126 -0
  14. package/dist/838.index.js +1 -1
  15. package/dist/985.index.js +5 -0
  16. package/dist/agentic-security.mjs +32 -32
  17. package/dist/agentic-security.mjs.sha256 +1 -1
  18. package/package.json +4 -4
  19. package/src/dataflow/async-sequencing.js +16 -7
  20. package/src/dataflow/builtin-summaries.js +131 -0
  21. package/src/dataflow/catalog.js +107 -0
  22. package/src/dataflow/cross-repo.js +75 -1
  23. package/src/dataflow/engine.js +181 -8
  24. package/src/dataflow/implicit-flow.js +24 -6
  25. package/src/dataflow/stub-aware-filter.js +69 -11
  26. package/src/dataflow/summaries.js +28 -3
  27. package/src/engine-parallel.js +70 -0
  28. package/src/engine.js +270 -19
  29. package/src/integrations/index.js +2 -1
  30. package/src/ir/callgraph.js +27 -7
  31. package/src/ir/index.js +22 -1
  32. package/src/ir/parser-go.js +403 -0
  33. package/src/ir/parser-js.js +2 -0
  34. package/src/ir/parser-php.js +330 -0
  35. package/src/ir/parser-py.helper.py +137 -11
  36. package/src/ir/parser-rb.js +309 -0
  37. package/src/llm-validator/index.js +7 -5
  38. package/src/mcp/audit.js +5 -0
  39. package/src/posture/calibration-drift.js +2 -1
  40. package/src/posture/calibration.js +16 -1
  41. package/src/posture/fix-history.js +8 -2
  42. package/src/posture/profile.js +4 -5
  43. package/src/posture/rule-overrides.js +2 -3
  44. package/src/posture/rule-pack-signing.js +2 -3
  45. package/src/posture/rule-synthesis.js +5 -6
  46. package/src/posture/security-trend.js +4 -7
  47. package/src/posture/state-dir.js +124 -0
  48. package/src/posture/streak.js +3 -0
  49. package/src/posture/suppressions.js +5 -8
  50. package/src/posture/triage.js +16 -5
  51. package/src/posture/validator-metrics.js +3 -6
  52. package/src/report/index.js +23 -2
  53. package/src/sast/cache-poisoning.js +77 -0
  54. package/src/sast/comparison-safety.js +73 -0
  55. package/src/sast/db-taint.js +78 -0
  56. package/src/sast/graphql.js +127 -0
  57. package/src/sast/llm-stored-prompt.js +57 -0
  58. package/src/sast/mutation-xss.js +43 -0
  59. package/src/sast/nosql-injection.js +5 -0
  60. package/src/sast/null-byte-injection.js +76 -0
  61. package/src/sast/redos-nfa.js +338 -0
  62. package/src/sast/rust.js +26 -0
  63. package/src/sast/sensitive-data-logging.js +73 -0
  64. package/src/sast/weak-password-hash.js +77 -0
  65. package/src/sast/weak-randomness.js +100 -0
  66. package/src/sca/binary-metadata.js +124 -0
  67. package/src/sca/llm-function-extract.js +107 -0
  68. package/src/sca/py-package-functions.js +118 -0
  69. package/src/sca/vendor-detect.js +144 -0
@@ -0,0 +1,159 @@
1
+ export const id = 718;
2
+ export const ids = [718];
3
+ export const modules = {
4
+
5
+ /***/ 8718:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ detectVendoredLibraries: () => (/* binding */ detectVendoredLibraries)
10
+ /* harmony export */ });
11
+ // Vendored / copied library detection via version-string fingerprinting.
12
+ //
13
+ // Detects library code copied into src/ that bypasses SCA. Uses version
14
+ // string patterns (_.VERSION, jQuery.fn.jquery, etc.) and characteristic
15
+ // function signatures to identify vendored libraries.
16
+
17
+ const VERSION_FINGERPRINTS = [
18
+ { pkg: 'lodash', ecosystem: 'npm', patterns: [
19
+ { re: /\b(?:lodash|_)\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
20
+ { re: /\b__lodash_hash_undefined__\b/, version: null },
21
+ ]},
22
+ { pkg: 'jquery', ecosystem: 'npm', patterns: [
23
+ { re: /jQuery\.fn\.jquery\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
24
+ { re: /\bjQuery\.fn\.init\b/, version: null },
25
+ ]},
26
+ { pkg: 'underscore', ecosystem: 'npm', patterns: [
27
+ { re: /\b_\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
28
+ ]},
29
+ { pkg: 'moment', ecosystem: 'npm', patterns: [
30
+ { re: /\bmoment\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
31
+ { re: /\bmoment\.(?:utc|parseZone|duration|locale)\b/, version: null },
32
+ ]},
33
+ { pkg: 'handlebars', ecosystem: 'npm', patterns: [
34
+ { re: /\bHandlebars\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
35
+ ]},
36
+ { pkg: 'backbone', ecosystem: 'npm', patterns: [
37
+ { re: /\bBackbone\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
38
+ ]},
39
+ { pkg: 'angular', ecosystem: 'npm', patterns: [
40
+ { re: /\bangular\.version\s*=\s*\{[^}]*full\s*:\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
41
+ ]},
42
+ { pkg: 'vue', ecosystem: 'npm', patterns: [
43
+ { re: /\bVue\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
44
+ ]},
45
+ { pkg: 'react', ecosystem: 'npm', patterns: [
46
+ { re: /\bReactVersion\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
47
+ ]},
48
+ { pkg: 'dompurify', ecosystem: 'npm', patterns: [
49
+ { re: /\bDOMPurify\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
50
+ ]},
51
+ { pkg: 'marked', ecosystem: 'npm', patterns: [
52
+ { re: /\bmarked\.(?:defaults|setOptions|use|parse)\b[\s\S]{0,200}version\s*[:=]\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
53
+ ]},
54
+ { pkg: 'axios', ecosystem: 'npm', patterns: [
55
+ { re: /\baxios\.VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
56
+ ]},
57
+ { pkg: 'socket.io-client', ecosystem: 'npm', patterns: [
58
+ { re: /\bio\.protocol\s*=\s*(\d+)/, version: null },
59
+ ]},
60
+ { pkg: 'highlight.js', ecosystem: 'npm', patterns: [
61
+ { re: /\bhljs\.versionString\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
62
+ ]},
63
+ { pkg: 'chart.js', ecosystem: 'npm', patterns: [
64
+ { re: /\bChart\.version\s*=\s*['"](\d+\.\d+\.\d+)['"]/, versionGroup: 1 },
65
+ ]},
66
+ ];
67
+
68
+ const SKIP_DIRS = /(?:^|[/\\])(?:node_modules|vendor|dist|build|\.next|__pycache__|\.git)[/\\]/;
69
+
70
+ function detectVendoredLibraries(fileContents) {
71
+ if (!fileContents || typeof fileContents !== 'object') return [];
72
+ const detected = [];
73
+ const seen = new Set();
74
+
75
+ for (const [fp, content] of Object.entries(fileContents)) {
76
+ if (!content || typeof content !== 'string') continue;
77
+ if (SKIP_DIRS.test(fp)) continue;
78
+ if (content.length < 500) continue;
79
+
80
+ for (const lib of VERSION_FINGERPRINTS) {
81
+ for (const pat of lib.patterns) {
82
+ const m = content.match(pat.re);
83
+ if (!m) continue;
84
+ const version = pat.versionGroup ? m[pat.versionGroup] : null;
85
+ const key = `${lib.pkg}:${fp}`;
86
+ if (seen.has(key)) continue;
87
+ seen.add(key);
88
+ detected.push({
89
+ name: lib.pkg,
90
+ version: version || 'unknown',
91
+ ecosystem: lib.ecosystem,
92
+ file: fp,
93
+ scope: 'vendored',
94
+ isVendored: true,
95
+ });
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ // Pass 2: Function-body structural matching for minified/forked copies
101
+ const FUNCTION_BODY_SIGS = [
102
+ { pkg: 'lodash', ecosystem: 'npm', fn: 'merge', paramMin: 1,
103
+ bodyContains: ['assignValue', 'baseFor', 'isObject', 'baseMerge'] },
104
+ { pkg: 'lodash', ecosystem: 'npm', fn: 'template', paramMin: 1,
105
+ bodyContains: ['sourceURL', 'interpolate', 'evaluate', 'escape'] },
106
+ { pkg: 'lodash', ecosystem: 'npm', fn: 'defaultsDeep', paramMin: 1,
107
+ bodyContains: ['baseMerge', 'isMergeableObject', 'customDefaultsMerge'] },
108
+ { pkg: 'jquery', ecosystem: 'npm', fn: 'ajax', paramMin: 1,
109
+ bodyContains: ['XMLHttpRequest', 'ajaxSettings', 'crossDomain', 'responseFields'] },
110
+ { pkg: 'handlebars', ecosystem: 'npm', fn: 'compile', paramMin: 1,
111
+ bodyContains: ['templateSpec', 'container', 'invokePartial', 'blockParams'] },
112
+ { pkg: 'marked', ecosystem: 'npm', fn: 'parse', paramMin: 1,
113
+ bodyContains: ['Lexer', 'Parser', 'blockTokens', 'walkTokens'] },
114
+ { pkg: 'ejs', ecosystem: 'npm', fn: 'render', paramMin: 1,
115
+ bodyContains: ['includeFile', 'resolveInclude', 'rethrow', 'escapeFn'] },
116
+ { pkg: 'moment', ecosystem: 'npm', fn: 'format', paramMin: 0,
117
+ bodyContains: ['formatMoment', 'expandFormat', 'makeFormatFunction', 'localFormattingTokens'] },
118
+ { pkg: 'underscore', ecosystem: 'npm', fn: 'template', paramMin: 1,
119
+ bodyContains: ['interpolate', 'evaluate', 'escape', 'templateSettings'] },
120
+ { pkg: 'minimist', ecosystem: 'npm', fn: 'parse', paramMin: 1,
121
+ bodyContains: ['boolean', 'alias', 'default', 'stopEarly', 'unknown'] },
122
+ ];
123
+
124
+ for (const [fp, content] of Object.entries(fileContents)) {
125
+ if (!content || typeof content !== 'string') continue;
126
+ if (SKIP_DIRS.test(fp)) continue;
127
+ if (!/\.(?:js|mjs|cjs)$/i.test(fp)) continue;
128
+ if (content.length < 200 || content.length > 500_000) continue;
129
+
130
+ for (const sig of FUNCTION_BODY_SIGS) {
131
+ const key = `${sig.pkg}:${fp}`;
132
+ if (seen.has(key)) continue;
133
+ const fnRe = new RegExp(`(?:function\\s+${sig.fn}|(?:const|let|var)\\s+${sig.fn}\\s*=|${sig.fn}\\s*[:=]\\s*function)\\s*\\(`, 'g');
134
+ const m = fnRe.exec(content);
135
+ if (!m) continue;
136
+ const bodyWindow = content.slice(m.index, m.index + 2000);
137
+ const matchCount = sig.bodyContains.filter(kw => bodyWindow.includes(kw)).length;
138
+ if (matchCount < Math.ceil(sig.bodyContains.length * 0.6)) continue;
139
+ seen.add(key);
140
+ detected.push({
141
+ name: sig.pkg,
142
+ version: 'unknown',
143
+ ecosystem: sig.ecosystem,
144
+ file: fp,
145
+ scope: 'vendored',
146
+ isVendored: true,
147
+ _detectionMethod: 'function-body-signature',
148
+ _matchedKeywords: matchCount,
149
+ });
150
+ }
151
+ }
152
+
153
+ return detected;
154
+ }
155
+
156
+
157
+ /***/ })
158
+
159
+ };
@@ -0,0 +1,126 @@
1
+ export const id = 824;
2
+ export const ids = [824];
3
+ export const modules = {
4
+
5
+ /***/ 9824:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ extractVulnFunctionsViaLLM: () => (/* binding */ extractVulnFunctionsViaLLM)
10
+ /* harmony export */ });
11
+ /* unused harmony export isLlmScaEnabled */
12
+ /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(7598);
13
+ /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
14
+ /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
15
+ // LLM-assisted vulnerable function extraction for SCA findings.
16
+ //
17
+ // For CVEs without OSV ecosystem_specific data or GHSA fix commits,
18
+ // uses an LLM to extract vulnerable function names from the CVE description.
19
+ //
20
+ // Gated behind AGENTIC_SECURITY_LLM_SCA=1 (opt-in).
21
+ // Uses the same LLM endpoint config as the SAST validator.
22
+
23
+
24
+
25
+
26
+
27
+ const CACHE_DIR = process.env.XDG_CONFIG_HOME
28
+ ? node_path__WEBPACK_IMPORTED_MODULE_2__.join(process.env.XDG_CONFIG_HOME, 'agentic-security', 'llm-sca-cache')
29
+ : node_path__WEBPACK_IMPORTED_MODULE_2__.join(process.env.HOME || '/tmp', '.config', 'agentic-security', 'llm-sca-cache');
30
+
31
+ function _cacheKey(osvId) {
32
+ return node_crypto__WEBPACK_IMPORTED_MODULE_0__.createHash('sha256').update(`sca-fn:${osvId}`).digest('hex').slice(0, 16);
33
+ }
34
+
35
+ function _readCache(osvId) {
36
+ try {
37
+ const fp = node_path__WEBPACK_IMPORTED_MODULE_2__.join(CACHE_DIR, _cacheKey(osvId) + '.json');
38
+ return JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_1__.readFileSync(fp, 'utf8'));
39
+ } catch { return null; }
40
+ }
41
+
42
+ function _writeCache(osvId, data) {
43
+ try {
44
+ node_fs__WEBPACK_IMPORTED_MODULE_1__.mkdirSync(CACHE_DIR, { recursive: true });
45
+ node_fs__WEBPACK_IMPORTED_MODULE_1__.writeFileSync(node_path__WEBPACK_IMPORTED_MODULE_2__.join(CACHE_DIR, _cacheKey(osvId) + '.json'), JSON.stringify(data));
46
+ } catch { /* cache write failure is non-fatal */ }
47
+ }
48
+
49
+ function isLlmScaEnabled() {
50
+ return process.env.AGENTIC_SECURITY_LLM_SCA === '1';
51
+ }
52
+
53
+ function _endpointConfig() {
54
+ const endpoint = process.env.AGENTIC_SECURITY_LLM_ENDPOINT;
55
+ const apiKey = process.env.AGENTIC_SECURITY_LLM_API_KEY;
56
+ const model = process.env.AGENTIC_SECURITY_LLM_MODEL || 'unknown';
57
+ return endpoint ? { endpoint, apiKey, model } : null;
58
+ }
59
+
60
+ async function _askLlm(prompt, config) {
61
+ const headers = { 'Content-Type': 'application/json' };
62
+ if (config.apiKey) headers['Authorization'] = `Bearer ${config.apiKey}`;
63
+ const resp = await fetch(config.endpoint, {
64
+ method: 'POST',
65
+ headers,
66
+ body: JSON.stringify({ prompt, model: config.model }),
67
+ signal: AbortSignal.timeout(15000),
68
+ });
69
+ if (!resp.ok) return null;
70
+ const text = await resp.text();
71
+ const jsonMatch = text.match(/\{[^{}]*"functions"\s*:\s*\[[^\]]*\][^{}]*\}/);
72
+ if (!jsonMatch) return null;
73
+ try { return JSON.parse(jsonMatch[0]); } catch { return null; }
74
+ }
75
+
76
+ async function extractVulnFunctionsViaLLM(supplyChain, opts = {}) {
77
+ if (!isLlmScaEnabled()) return [];
78
+ const config = _endpointConfig();
79
+ if (!config) return [];
80
+
81
+ const enriched = [];
82
+ const candidates = (supplyChain || []).filter(sc =>
83
+ sc.type === 'vulnerable_dep' &&
84
+ (!sc.osvVulnFunctions || !sc.osvVulnFunctions.length) &&
85
+ sc.noKnownCallSite &&
86
+ sc.description
87
+ );
88
+
89
+ const BATCH_LIMIT = 20;
90
+ for (const sc of candidates.slice(0, BATCH_LIMIT)) {
91
+ const cached = _readCache(sc.osvId);
92
+ if (cached) {
93
+ if (cached.functions && cached.functions.length) {
94
+ sc.osvVulnFunctions = cached.functions;
95
+ sc._llmFunctionExtracted = true;
96
+ enriched.push(sc);
97
+ }
98
+ continue;
99
+ }
100
+
101
+ const prompt = `Given security advisory ${sc.osvId || ''} (${sc.cveAliases?.[0] || ''}) affecting npm package "${sc.name}" version ${sc.version}:\n\nDescription: ${sc.description.slice(0, 500)}\n\nWhat specific exported function(s) in this package are vulnerable? Return ONLY a JSON object: { "functions": ["functionName1", "functionName2"] }\n\nIf you cannot determine the specific functions, return: { "functions": [] }`;
102
+
103
+ try {
104
+ const result = await _askLlm(prompt, config);
105
+ if (result && Array.isArray(result.functions)) {
106
+ const fns = result.functions.filter(f => typeof f === 'string' && f.length > 0 && f.length < 100);
107
+ _writeCache(sc.osvId, { functions: fns, model: config.model, extractedAt: new Date().toISOString() });
108
+ if (fns.length) {
109
+ sc.osvVulnFunctions = fns;
110
+ sc._llmFunctionExtracted = true;
111
+ enriched.push(sc);
112
+ }
113
+ } else {
114
+ _writeCache(sc.osvId, { functions: [], model: config.model, extractedAt: new Date().toISOString() });
115
+ }
116
+ } catch {
117
+ // LLM call failure — skip, don't cache (may be transient)
118
+ }
119
+ }
120
+ return enriched;
121
+ }
122
+
123
+
124
+ /***/ })
125
+
126
+ };
package/dist/838.index.js CHANGED
@@ -14,7 +14,7 @@ __webpack_require__.r(__webpack_exports__);
14
14
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
15
15
  /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(3024);
16
16
  /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6760);
17
- /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(3291);
17
+ /* harmony import */ var _engine_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6048);
18
18
  // Closed-loop /fix verification (Sentinel-parity FR-L4-4, FR-L4-5).
19
19
  //
20
20
  // Given a candidate patch (the new file content + the finding stableId being
package/dist/985.index.js CHANGED
@@ -1465,6 +1465,11 @@ async function _postRemote(url, entry) {
1465
1465
  function auditCall({ sessionRoot, tool, args, outcome, reason }) {
1466
1466
  if (!sessionRoot) return;
1467
1467
  try {
1468
+ // Safety: only write audit log if sessionRoot looks like a project root
1469
+ const MARKERS = ['.git', 'package.json', 'pyproject.toml', 'go.mod', 'Cargo.toml', 'pom.xml', 'composer.json', 'Gemfile'];
1470
+ let hasMarker = false;
1471
+ for (const m of MARKERS) { try { if (external_node_fs_.existsSync(external_node_path_.join(sessionRoot, m))) { hasMarker = true; break; } } catch {} }
1472
+ if (!hasMarker) return;
1468
1473
  const dir = external_node_path_.join(sessionRoot, '.agentic-security');
1469
1474
  external_node_fs_.mkdirSync(dir, { recursive: true });
1470
1475
  const logFile = external_node_path_.join(dir, 'mcp-audit.log');