@clear-capabilities/agentic-security-scanner 0.77.0 → 0.78.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 (83) 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 +115 -0
  5. package/bin/.agentic-security/streak.json +20 -0
  6. package/bin/agentic-security.js +33 -2
  7. package/dist/178.index.js +1 -1
  8. package/dist/384.index.js +1 -1
  9. package/dist/637.index.js +1 -1
  10. package/dist/718.index.js +106 -0
  11. package/dist/824.index.js +126 -0
  12. package/dist/838.index.js +1 -1
  13. package/dist/agentic-security.mjs +32 -32
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/package.json +3 -3
  16. package/src/.agentic-security/findings.json +82642 -0
  17. package/src/.agentic-security/last-scan.json +82642 -0
  18. package/src/.agentic-security/last-scan.json.sig +1 -0
  19. package/src/.agentic-security/scan-history.json +10054 -0
  20. package/src/.agentic-security/streak.json +21 -0
  21. package/src/dataflow/.agentic-security/findings.json +3515 -0
  22. package/src/dataflow/.agentic-security/last-scan.json +3515 -0
  23. package/src/dataflow/.agentic-security/last-scan.json.sig +1 -0
  24. package/src/dataflow/.agentic-security/scan-history.json +702 -0
  25. package/src/dataflow/.agentic-security/streak.json +22 -0
  26. package/src/dataflow/async-sequencing.js +16 -7
  27. package/src/dataflow/builtin-summaries.js +131 -0
  28. package/src/dataflow/catalog.js +107 -0
  29. package/src/dataflow/cross-repo.js +75 -1
  30. package/src/dataflow/engine.js +129 -0
  31. package/src/dataflow/implicit-flow.js +24 -6
  32. package/src/dataflow/stub-aware-filter.js +69 -11
  33. package/src/dataflow/summaries.js +28 -3
  34. package/src/engine-parallel.js +70 -0
  35. package/src/engine.js +165 -15
  36. package/src/ir/.agentic-security/findings.json +3777 -0
  37. package/src/ir/.agentic-security/last-scan.json +3777 -0
  38. package/src/ir/.agentic-security/last-scan.json.sig +1 -0
  39. package/src/ir/.agentic-security/scan-history.json +771 -0
  40. package/src/ir/.agentic-security/streak.json +21 -0
  41. package/src/ir/index.js +22 -1
  42. package/src/ir/parser-go.js +403 -0
  43. package/src/ir/parser-js.js +2 -0
  44. package/src/ir/parser-php.js +330 -0
  45. package/src/ir/parser-py.helper.py +137 -11
  46. package/src/ir/parser-rb.js +309 -0
  47. package/src/posture/.agentic-security/findings.json +51562 -0
  48. package/src/posture/.agentic-security/last-scan.json +51562 -0
  49. package/src/posture/.agentic-security/last-scan.json.sig +1 -0
  50. package/src/posture/.agentic-security/scan-history.json +650 -0
  51. package/src/posture/.agentic-security/streak.json +20 -0
  52. package/src/posture/calibration.js +14 -0
  53. package/src/posture/triage.js +13 -0
  54. package/src/report/.agentic-security/findings.json +80 -0
  55. package/src/report/.agentic-security/last-scan.json +80 -0
  56. package/src/report/.agentic-security/last-scan.json.sig +1 -0
  57. package/src/report/.agentic-security/scan-history.json +35 -0
  58. package/src/report/.agentic-security/streak.json +22 -0
  59. package/src/report/index.js +23 -2
  60. package/src/sast/.agentic-security/findings.json +5190 -0
  61. package/src/sast/.agentic-security/last-scan.json +5190 -0
  62. package/src/sast/.agentic-security/last-scan.json.sig +1 -0
  63. package/src/sast/.agentic-security/scan-history.json +408 -0
  64. package/src/sast/.agentic-security/streak.json +20 -0
  65. package/src/sast/cache-poisoning.js +77 -0
  66. package/src/sast/comparison-safety.js +73 -0
  67. package/src/sast/db-taint.js +54 -0
  68. package/src/sast/graphql.js +127 -0
  69. package/src/sast/llm-stored-prompt.js +57 -0
  70. package/src/sast/mutation-xss.js +43 -0
  71. package/src/sast/nosql-injection.js +5 -0
  72. package/src/sast/null-byte-injection.js +76 -0
  73. package/src/sast/redos-nfa.js +338 -0
  74. package/src/sast/sensitive-data-logging.js +73 -0
  75. package/src/sast/weak-password-hash.js +77 -0
  76. package/src/sast/weak-randomness.js +100 -0
  77. package/src/sca/.agentic-security/findings.json +1587 -0
  78. package/src/sca/.agentic-security/last-scan.json +1587 -0
  79. package/src/sca/.agentic-security/last-scan.json.sig +1 -0
  80. package/src/sca/.agentic-security/scan-history.json +36 -0
  81. package/src/sca/.agentic-security/streak.json +21 -0
  82. package/src/sca/llm-function-extract.js +107 -0
  83. package/src/sca/vendor-detect.js +91 -0
@@ -0,0 +1,73 @@
1
+ // Sensitive data logging detector.
2
+ //
3
+ // Flags PII-named variables sent to logging sinks without sanitization.
4
+ // Covers JS (console/winston/pino/bunyan), Python (logging/print),
5
+ // Go (log/fmt.Printf).
6
+
7
+ const PII_CONTEXT = /\b(email|ssn|password|phone|dob|date_of_birth|credit_card|card_number|social_security|passport|medical_record|ip_address|first_name|last_name|address|zip_code|bank_account)\b/i;
8
+
9
+ function _line(raw, idx) { return raw.slice(0, idx).split('\n').length; }
10
+
11
+ const LANG_SINKS = {
12
+ js: {
13
+ ext: /\.(?:js|jsx|ts|tsx|mjs|cjs)$/i,
14
+ sinks: [
15
+ /\bconsole\.(?:log|warn|error|info|debug|trace)\s*\(/g,
16
+ /\blogger\.(?:log|info|warn|error|debug|trace|fatal)\s*\(/g,
17
+ /\b(?:winston|pino|bunyan|log4js)(?:\.\w+)*\.(?:info|warn|error|debug|log)\s*\(/g,
18
+ ],
19
+ },
20
+ py: {
21
+ ext: /\.py$/i,
22
+ sinks: [
23
+ /\blogging\.(?:info|warning|error|debug|critical)\s*\(/g,
24
+ /\blogger\.(?:info|warning|error|debug|critical)\s*\(/g,
25
+ /\bprint\s*\(/g,
26
+ ],
27
+ },
28
+ go: {
29
+ ext: /\.go$/i,
30
+ sinks: [
31
+ /\blog\.(?:Printf|Println|Print|Fatalf|Fatal)\s*\(/g,
32
+ /\bfmt\.(?:Printf|Println|Print|Fprintf)\s*\(/g,
33
+ ],
34
+ },
35
+ };
36
+
37
+ export function scanSensitiveDataLogging(fp, raw) {
38
+ if (!fp || !raw || typeof raw !== 'string') return [];
39
+ if (raw.length > 500_000) return [];
40
+
41
+ const findings = [];
42
+ let lang = null;
43
+ for (const v of Object.values(LANG_SINKS)) {
44
+ if (v.ext.test(fp)) { lang = v; break; }
45
+ }
46
+ if (!lang) return [];
47
+
48
+ for (const sinkRe of lang.sinks) {
49
+ sinkRe.lastIndex = 0;
50
+ for (const m of raw.matchAll(sinkRe)) {
51
+ const lineNum = _line(raw, m.index);
52
+ const lineEnd = raw.indexOf('\n', m.index);
53
+ const lineText = raw.slice(m.index, lineEnd > 0 ? lineEnd : m.index + 200);
54
+ if (!PII_CONTEXT.test(lineText)) continue;
55
+ // Skip if the line contains redaction/masking
56
+ if (/\b(?:redact|mask|sanitize|censor|scrub|\*{3,}|\.{3}|slice\s*\(\s*0\s*,\s*\d\s*\))\b/i.test(lineText)) continue;
57
+ findings.push({
58
+ id: `sensitive-log:${fp}:${lineNum}`,
59
+ file: fp, line: lineNum,
60
+ vuln: 'Sensitive Data Logged — PII-named variable sent to logger without sanitization',
61
+ severity: 'medium',
62
+ family: 'sensitive-data-logging',
63
+ cwe: 'CWE-532',
64
+ parser: 'PII-LOG',
65
+ confidence: 0.70,
66
+ description: 'A variable with a PII-related name (email, password, ssn, etc.) is logged without redaction. Log aggregators, crash reporters, and stdout can expose this data.',
67
+ remediation: 'Redact sensitive fields before logging: logger.info("Login", { email: email.slice(0, 3) + "***" }). Never log passwords, SSNs, or credit card numbers.',
68
+ snippet: lineText.trim().slice(0, 100),
69
+ });
70
+ }
71
+ }
72
+ return findings;
73
+ }
@@ -0,0 +1,77 @@
1
+ // Weak password hashing detector.
2
+ //
3
+ // Context-gated: only fires when the hash input variable is named
4
+ // password/secret/credential or the enclosing function is password-related.
5
+ // Detects MD5/SHA1 without salt for password storage.
6
+
7
+ const PASSWORD_CONTEXT = /\b(password|passwd|pwd|secret|credential|passphrase|pass_hash|user_pass)\b/i;
8
+ const PASSWORD_FUNC = /\b(hashPassword|encryptPassword|checkPassword|verifyPassword|createHash|setPassword|validatePassword|hash_password|check_password)\b/i;
9
+
10
+ function _line(raw, idx) { return raw.slice(0, idx).split('\n').length; }
11
+
12
+ const PATTERNS = {
13
+ js: {
14
+ ext: /\.(?:js|jsx|ts|tsx|mjs|cjs)$/i,
15
+ rules: [
16
+ { re: /\bcreateHash\s*\(\s*['"](?:md5|sha1|sha-1|md4)['"]\s*\)[\s\S]{0,60}\.update\s*\(\s*(\w+)/g, label: 'createHash with weak algorithm' },
17
+ { re: /\bmd5\s*\(\s*(\w+)/g, label: 'md5() function call' },
18
+ { re: /\bsha1\s*\(\s*(\w+)/g, label: 'sha1() function call' },
19
+ ],
20
+ },
21
+ py: {
22
+ ext: /\.py$/i,
23
+ rules: [
24
+ { re: /\bhashlib\.(?:md5|sha1)\s*\(\s*(\w+)/g, label: 'hashlib.md5/sha1' },
25
+ { re: /\bhashlib\.new\s*\(\s*['"](?:md5|sha1)['"]\s*\)[\s\S]{0,40}\.update\s*\(\s*(\w+)/g, label: 'hashlib.new with weak algorithm' },
26
+ ],
27
+ },
28
+ go: {
29
+ ext: /\.go$/i,
30
+ rules: [
31
+ { re: /\bmd5\.(?:Sum|New)\s*\(\s*(?:\[\]byte\s*\(\s*)?(\w+)/g, label: 'md5.Sum/New' },
32
+ { re: /\bsha1\.(?:Sum|New)\s*\(\s*(?:\[\]byte\s*\(\s*)?(\w+)/g, label: 'sha1.Sum/New' },
33
+ ],
34
+ },
35
+ };
36
+
37
+ export function scanWeakPasswordHash(fp, raw) {
38
+ if (!fp || !raw || typeof raw !== 'string') return [];
39
+ if (raw.length > 500_000) return [];
40
+
41
+ const findings = [];
42
+ let lang = null;
43
+ for (const v of Object.values(PATTERNS)) {
44
+ if (v.ext.test(fp)) { lang = v; break; }
45
+ }
46
+ if (!lang) return [];
47
+
48
+ for (const { re, label } of lang.rules) {
49
+ re.lastIndex = 0;
50
+ for (const m of raw.matchAll(re)) {
51
+ const inputVar = m[1] || '';
52
+ const line = _line(raw, m.index);
53
+ // Check context: password-named variable or password-related function
54
+ const funcStart = raw.lastIndexOf('\n', Math.max(0, m.index - 500));
55
+ const context = raw.slice(funcStart, m.index + m[0].length + 100);
56
+ if (!PASSWORD_CONTEXT.test(inputVar) && !PASSWORD_CONTEXT.test(context) && !PASSWORD_FUNC.test(context)) continue;
57
+ // Check for salt within 10 lines before
58
+ const before = raw.slice(Math.max(0, m.index - 400), m.index);
59
+ const hasSalt = /\b(salt|randomBytes|urandom|os\.urandom|crypto\.randomBytes|bcrypt|argon2|scrypt|pbkdf2)\b/i.test(before);
60
+ if (hasSalt) continue;
61
+
62
+ findings.push({
63
+ id: `weak-pw-hash:${fp}:${line}`,
64
+ file: fp, line,
65
+ vuln: `Weak Password Hashing — ${label} for password without salt`,
66
+ severity: 'critical',
67
+ family: 'weak-password-hash',
68
+ cwe: 'CWE-916',
69
+ parser: 'WEAK-PW-HASH',
70
+ confidence: 0.80,
71
+ description: `${label} used on a password-context variable without salt. MD5/SHA1 are fast hashes trivially reversed via rainbow tables. Unsalted hashes are cracked in seconds.`,
72
+ remediation: 'Use bcrypt (cost ≥ 12), argon2id, or scrypt. Never use MD5/SHA1/SHA256 for password storage.',
73
+ });
74
+ }
75
+ }
76
+ return findings;
77
+ }
@@ -0,0 +1,100 @@
1
+ // Cross-language insecure randomness detector.
2
+ //
3
+ // Flags usage of non-cryptographic PRNGs when the result is assigned to a
4
+ // security-sensitive variable (token, session, nonce, key, secret, etc.).
5
+ //
6
+ // Coverage:
7
+ // JS/TS: Math.random()
8
+ // Python: random.random(), random.randint(), random.choice(), random.uniform()
9
+ // Go: rand.Intn(), rand.Int(), rand.Float64(), rand.Int31(), rand.Int63()
10
+ // Ruby: rand(), Random.rand, Random.new.rand
11
+ // PHP: rand(), mt_rand(), array_rand(), shuffle()
12
+
13
+ const SECURITY_CONTEXT = /\b(token|session|nonce|key|secret|password|otp|csrf|salt|code|pin|auth|reset|verify|captcha|challenge|ticket)\b/i;
14
+
15
+ function _line(raw, idx) {
16
+ return raw.slice(0, idx).split('\n').length;
17
+ }
18
+
19
+ const LANG_PATTERNS = {
20
+ js: {
21
+ ext: /\.(?:js|jsx|ts|tsx|mjs|cjs)$/i,
22
+ patterns: [
23
+ { re: /\bMath\.random\s*\(\s*\)/g, label: 'Math.random()' },
24
+ ],
25
+ },
26
+ py: {
27
+ ext: /\.py$/i,
28
+ patterns: [
29
+ { re: /\brandom\.(?:random|randint|choice|uniform|randrange|sample|getrandbits)\s*\(/g, label: 'random module (non-crypto)' },
30
+ ],
31
+ },
32
+ go: {
33
+ ext: /\.go$/i,
34
+ patterns: [
35
+ { re: /\brand\.(?:Intn|Int|Float64|Float32|Int31|Int63|Int31n|Int63n|Uint32|Uint64)\s*\(/g, label: 'math/rand (non-crypto)' },
36
+ ],
37
+ },
38
+ rb: {
39
+ ext: /\.rb$/i,
40
+ patterns: [
41
+ { re: /\b(?:rand\s*\(|Random\.(?:rand|new\.rand)\s*\()/g, label: 'Kernel.rand / Random.rand' },
42
+ ],
43
+ },
44
+ php: {
45
+ ext: /\.(?:php|phtml)$/i,
46
+ patterns: [
47
+ { re: /\b(?:rand|mt_rand|array_rand)\s*\(/g, label: 'rand() / mt_rand()' },
48
+ ],
49
+ },
50
+ };
51
+
52
+ export function scanWeakRandomness(fp, raw) {
53
+ if (!fp || !raw || typeof raw !== 'string') return [];
54
+ if (raw.length > 500_000) return [];
55
+
56
+ const findings = [];
57
+ let lang = null;
58
+ for (const [k, v] of Object.entries(LANG_PATTERNS)) {
59
+ if (v.ext.test(fp)) { lang = v; break; }
60
+ }
61
+ if (!lang) return [];
62
+
63
+ for (const { re, label } of lang.patterns) {
64
+ re.lastIndex = 0;
65
+ for (const m of raw.matchAll(re)) {
66
+ const line = _line(raw, m.index);
67
+ const lineStart = raw.lastIndexOf('\n', m.index) + 1;
68
+ const lineEnd = raw.indexOf('\n', m.index);
69
+ const lineText = raw.slice(lineStart, lineEnd > 0 ? lineEnd : raw.length);
70
+ if (!SECURITY_CONTEXT.test(lineText)) {
71
+ const prevLineStart = raw.lastIndexOf('\n', lineStart - 2) + 1;
72
+ const prevLine = raw.slice(prevLineStart, lineStart - 1);
73
+ if (!SECURITY_CONTEXT.test(prevLine)) continue;
74
+ }
75
+ findings.push({
76
+ id: `weak-rng:${fp}:${line}`,
77
+ file: fp,
78
+ line,
79
+ vuln: `Insecure Randomness — ${label} used for security-sensitive value`,
80
+ severity: 'high',
81
+ family: 'weak-rng',
82
+ cwe: 'CWE-330',
83
+ parser: 'WEAK-RNG',
84
+ confidence: 0.80,
85
+ description: `${label} is not cryptographically secure. An attacker can predict the output and forge tokens, bypass OTP, or guess session identifiers.`,
86
+ remediation: _remediation(fp),
87
+ snippet: lineText.trim().slice(0, 80),
88
+ });
89
+ }
90
+ }
91
+ return findings;
92
+ }
93
+
94
+ function _remediation(fp) {
95
+ if (/\.py$/i.test(fp)) return 'Use secrets.token_hex(32), secrets.token_urlsafe(32), or secrets.randbelow(n).';
96
+ if (/\.go$/i.test(fp)) return 'Use crypto/rand: n, _ := rand.Int(rand.Reader, big.NewInt(999999)).';
97
+ if (/\.rb$/i.test(fp)) return 'Use SecureRandom.hex(32) or SecureRandom.uuid.';
98
+ if (/\.(?:php|phtml)$/i.test(fp)) return 'Use random_bytes(32) or random_int(0, $max).';
99
+ return 'Use crypto.randomBytes(32).toString("hex") or crypto.getRandomValues().';
100
+ }