circle-ir 3.80.0 → 3.82.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/configs/sinks/xss.yaml +2 -1
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +26 -4
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/_credential-helpers.d.ts +40 -0
- package/dist/analysis/passes/_credential-helpers.d.ts.map +1 -0
- package/dist/analysis/passes/_credential-helpers.js +152 -0
- package/dist/analysis/passes/_credential-helpers.js.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +42 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.js +196 -0
- package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.d.ts +48 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.d.ts.map +1 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.js +222 -0
- package/dist/analysis/passes/info-disclosure-stacktrace-pass.js.map +1 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.d.ts +47 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.d.ts.map +1 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.js +159 -0
- package/dist/analysis/passes/plaintext-password-storage-pass.js.map +1 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts +46 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts.map +1 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.js +193 -0
- package/dist/analysis/passes/unrestricted-file-upload-pass.js.map +1 -0
- package/dist/analysis/passes/weak-password-encoding-pass.d.ts +40 -0
- package/dist/analysis/passes/weak-password-encoding-pass.d.ts.map +1 -0
- package/dist/analysis/passes/weak-password-encoding-pass.js +157 -0
- package/dist/analysis/passes/weak-password-encoding-pass.js.map +1 -0
- package/dist/analysis/passes/weak-password-hash-pass.d.ts +49 -0
- package/dist/analysis/passes/weak-password-hash-pass.d.ts.map +1 -0
- package/dist/analysis/passes/weak-password-hash-pass.js +225 -0
- package/dist/analysis/passes/weak-password-hash-pass.js.map +1 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +18 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +912 -4
- package/dist/core/circle-ir-core.cjs +26 -4
- package/dist/core/circle-ir-core.js +26 -4
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the credential / crypto rule family (Sprint 28).
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - weak-password-hash-pass.ts (CWE-916)
|
|
6
|
+
* - plaintext-password-storage-pass.ts (CWE-256)
|
|
7
|
+
* - cleartext-credential-transport-pass.ts (CWE-523)
|
|
8
|
+
* - weak-password-encoding-pass.ts (CWE-261)
|
|
9
|
+
*
|
|
10
|
+
* The credential-keyword regex is the same shape used by
|
|
11
|
+
* scan-secrets-pass.ts (CWE-260, Sprint 26). Kept identifier-anchored:
|
|
12
|
+
* `password`, `passwd`, `pwd`, `secret`, `api[_-]?key`, `auth[_-]?token`,
|
|
13
|
+
* `private[_-]?key`, `access[_-]?key`, `credential`.
|
|
14
|
+
*/
|
|
15
|
+
import type { CallInfo, ArgumentInfo } from '../../types/index.js';
|
|
16
|
+
/** True if a bare identifier name carries a credential keyword. */
|
|
17
|
+
export declare function isCredentialIdentifier(name: string | null | undefined): boolean;
|
|
18
|
+
/** True if any of the argument's variable / expression text carries a credential keyword. */
|
|
19
|
+
export declare function argLooksLikeCredential(arg: ArgumentInfo | undefined): boolean;
|
|
20
|
+
/** Strip surrounding quotes from a literal expression. */
|
|
21
|
+
export declare function stripQuotes(s: string): string;
|
|
22
|
+
/** Return the literal/value of an argument, with quotes stripped; null if not a literal. */
|
|
23
|
+
export declare function literalAt(call: CallInfo, position: number): string | null;
|
|
24
|
+
/**
|
|
25
|
+
* True if the call is a known cryptographic hash / KDF that "protects" a
|
|
26
|
+
* credential value. Used by plaintext-storage detector to suppress when
|
|
27
|
+
* the credential identifier has already been passed through a hash.
|
|
28
|
+
*
|
|
29
|
+
* Recognised:
|
|
30
|
+
* - Java: MessageDigest.update / .digest, DigestUtils.*, BCrypt.hashpw,
|
|
31
|
+
* PBKDF2*, Argon2*, SecretKeyFactory.generateSecret
|
|
32
|
+
* - Python: hashlib.*, bcrypt.hashpw / .hash, argon2.hash, passlib.hash
|
|
33
|
+
* - JS/TS: crypto.createHash().update / .digest, bcrypt.hash / .hashSync,
|
|
34
|
+
* argon2.hash, scrypt
|
|
35
|
+
* - Go: md5.Sum, sha*.Sum, bcrypt.GenerateFromPassword, argon2.*
|
|
36
|
+
*/
|
|
37
|
+
export declare function isHashFunctionCall(call: CallInfo): boolean;
|
|
38
|
+
/** True if the argument identifier was the target of a hash call earlier in `priorCalls`. */
|
|
39
|
+
export declare function priorHashOf(varName: string, priorCalls: CallInfo[]): boolean;
|
|
40
|
+
//# sourceMappingURL=_credential-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_credential-helpers.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/_credential-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAMnE,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAK/E;AAED,6FAA6F;AAC7F,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,OAAO,CAQ7E;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAU7C;AAED,4FAA4F;AAC5F,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAczE;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAqE1D;AAED,6FAA6F;AAC7F,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,OAAO,CAU5E"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the credential / crypto rule family (Sprint 28).
|
|
3
|
+
*
|
|
4
|
+
* Used by:
|
|
5
|
+
* - weak-password-hash-pass.ts (CWE-916)
|
|
6
|
+
* - plaintext-password-storage-pass.ts (CWE-256)
|
|
7
|
+
* - cleartext-credential-transport-pass.ts (CWE-523)
|
|
8
|
+
* - weak-password-encoding-pass.ts (CWE-261)
|
|
9
|
+
*
|
|
10
|
+
* The credential-keyword regex is the same shape used by
|
|
11
|
+
* scan-secrets-pass.ts (CWE-260, Sprint 26). Kept identifier-anchored:
|
|
12
|
+
* `password`, `passwd`, `pwd`, `secret`, `api[_-]?key`, `auth[_-]?token`,
|
|
13
|
+
* `private[_-]?key`, `access[_-]?key`, `credential`.
|
|
14
|
+
*/
|
|
15
|
+
/** Identifier-name credential keywords. Case-insensitive substring match. */
|
|
16
|
+
const CRED_KEYWORD_RE = /(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|private[_-]?key|access[_-]?key|credential)/i;
|
|
17
|
+
/** True if a bare identifier name carries a credential keyword. */
|
|
18
|
+
export function isCredentialIdentifier(name) {
|
|
19
|
+
if (!name)
|
|
20
|
+
return false;
|
|
21
|
+
// Reject very short / generic — `pwd` alone is fine, but reject obvious noise.
|
|
22
|
+
if (name.length < 3)
|
|
23
|
+
return false;
|
|
24
|
+
return CRED_KEYWORD_RE.test(name);
|
|
25
|
+
}
|
|
26
|
+
/** True if any of the argument's variable / expression text carries a credential keyword. */
|
|
27
|
+
export function argLooksLikeCredential(arg) {
|
|
28
|
+
if (!arg)
|
|
29
|
+
return false;
|
|
30
|
+
if (arg.variable && isCredentialIdentifier(arg.variable))
|
|
31
|
+
return true;
|
|
32
|
+
const expr = (arg.expression ?? '').trim();
|
|
33
|
+
if (!expr)
|
|
34
|
+
return false;
|
|
35
|
+
// Strip method-call tail (e.g. `password.getBytes()`, `pw.encode()`).
|
|
36
|
+
const head = expr.split(/[.\s(]/, 1)[0] ?? '';
|
|
37
|
+
return isCredentialIdentifier(head);
|
|
38
|
+
}
|
|
39
|
+
/** Strip surrounding quotes from a literal expression. */
|
|
40
|
+
export function stripQuotes(s) {
|
|
41
|
+
const t = s.trim();
|
|
42
|
+
if ((t.startsWith('"') && t.endsWith('"')) ||
|
|
43
|
+
(t.startsWith("'") && t.endsWith("'")) ||
|
|
44
|
+
(t.startsWith('`') && t.endsWith('`'))) {
|
|
45
|
+
return t.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
return t;
|
|
48
|
+
}
|
|
49
|
+
/** Return the literal/value of an argument, with quotes stripped; null if not a literal. */
|
|
50
|
+
export function literalAt(call, position) {
|
|
51
|
+
const arg = call.arguments.find((a) => a.position === position);
|
|
52
|
+
if (!arg)
|
|
53
|
+
return null;
|
|
54
|
+
const raw = arg.literal ?? arg.expression ?? '';
|
|
55
|
+
const trimmed = raw.trim();
|
|
56
|
+
if (trimmed.startsWith('"') ||
|
|
57
|
+
trimmed.startsWith("'") ||
|
|
58
|
+
trimmed.startsWith('`')) {
|
|
59
|
+
return stripQuotes(trimmed);
|
|
60
|
+
}
|
|
61
|
+
if (arg.literal)
|
|
62
|
+
return stripQuotes(arg.literal);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Hash-function detection (used by plaintext-password-storage-pass)
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
/**
|
|
69
|
+
* True if the call is a known cryptographic hash / KDF that "protects" a
|
|
70
|
+
* credential value. Used by plaintext-storage detector to suppress when
|
|
71
|
+
* the credential identifier has already been passed through a hash.
|
|
72
|
+
*
|
|
73
|
+
* Recognised:
|
|
74
|
+
* - Java: MessageDigest.update / .digest, DigestUtils.*, BCrypt.hashpw,
|
|
75
|
+
* PBKDF2*, Argon2*, SecretKeyFactory.generateSecret
|
|
76
|
+
* - Python: hashlib.*, bcrypt.hashpw / .hash, argon2.hash, passlib.hash
|
|
77
|
+
* - JS/TS: crypto.createHash().update / .digest, bcrypt.hash / .hashSync,
|
|
78
|
+
* argon2.hash, scrypt
|
|
79
|
+
* - Go: md5.Sum, sha*.Sum, bcrypt.GenerateFromPassword, argon2.*
|
|
80
|
+
*/
|
|
81
|
+
export function isHashFunctionCall(call) {
|
|
82
|
+
const method = call.method_name ?? '';
|
|
83
|
+
const receiver = call.receiver ?? '';
|
|
84
|
+
const recvLower = receiver.toLowerCase();
|
|
85
|
+
// bcrypt across all langs
|
|
86
|
+
if (recvLower === 'bcrypt' || recvLower.endsWith('.bcrypt')) {
|
|
87
|
+
return (method === 'hashpw' || method === 'hash' || method === 'hashSync' ||
|
|
88
|
+
method === 'GenerateFromPassword' || method === 'generate_password_hash');
|
|
89
|
+
}
|
|
90
|
+
// argon2
|
|
91
|
+
if (recvLower === 'argon2' || recvLower.endsWith('.argon2')) {
|
|
92
|
+
return method === 'hash' || method === 'Hash' || method === 'PasswordHash';
|
|
93
|
+
}
|
|
94
|
+
// Python hashlib + passlib
|
|
95
|
+
if (recvLower === 'hashlib')
|
|
96
|
+
return true;
|
|
97
|
+
if (recvLower === 'passlib' || recvLower.includes('passlib.hash'))
|
|
98
|
+
return true;
|
|
99
|
+
// Python pyca/cryptography PBKDF2HMAC
|
|
100
|
+
if (method === 'PBKDF2HMAC' || method === 'derive')
|
|
101
|
+
return true;
|
|
102
|
+
// Node.js crypto
|
|
103
|
+
if (recvLower === 'crypto') {
|
|
104
|
+
return (method === 'createHash' || method === 'createHmac' ||
|
|
105
|
+
method === 'pbkdf2' || method === 'pbkdf2Sync' ||
|
|
106
|
+
method === 'scrypt' || method === 'scryptSync');
|
|
107
|
+
}
|
|
108
|
+
// Java MessageDigest
|
|
109
|
+
if (receiver === 'MessageDigest' || receiver.endsWith('.MessageDigest')) {
|
|
110
|
+
return method === 'getInstance' || method === 'update' || method === 'digest';
|
|
111
|
+
}
|
|
112
|
+
// Java Apache Commons DigestUtils
|
|
113
|
+
if (receiver === 'DigestUtils' || receiver.endsWith('.DigestUtils')) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
// Java SecretKeyFactory / PBEKeySpec (PBKDF2 family)
|
|
117
|
+
if (receiver === 'SecretKeyFactory' || receiver.endsWith('.SecretKeyFactory')) {
|
|
118
|
+
return method === 'getInstance' || method === 'generateSecret';
|
|
119
|
+
}
|
|
120
|
+
if (method === 'PBEKeySpec')
|
|
121
|
+
return true;
|
|
122
|
+
// Go crypto/* hash packages
|
|
123
|
+
if (receiver === 'md5' || receiver === 'sha1' || receiver === 'sha256' ||
|
|
124
|
+
receiver === 'sha512' || receiver === 'sha3' ||
|
|
125
|
+
receiver.endsWith('/md5') || receiver.endsWith('/sha1') ||
|
|
126
|
+
receiver.endsWith('/sha256') || receiver.endsWith('/sha512')) {
|
|
127
|
+
return method === 'New' || method === 'Sum' || method === 'New224' || method === 'New384';
|
|
128
|
+
}
|
|
129
|
+
// Generic: anything literally named like a hash
|
|
130
|
+
const m = method.toLowerCase();
|
|
131
|
+
if (m === 'hash' || m === 'hashpw' || m === 'hashsync' ||
|
|
132
|
+
m === 'pbkdf2' || m === 'pbkdf2sync' ||
|
|
133
|
+
m === 'scrypt' || m === 'scryptsync')
|
|
134
|
+
return true;
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
/** True if the argument identifier was the target of a hash call earlier in `priorCalls`. */
|
|
138
|
+
export function priorHashOf(varName, priorCalls) {
|
|
139
|
+
for (const c of priorCalls) {
|
|
140
|
+
if (!isHashFunctionCall(c))
|
|
141
|
+
continue;
|
|
142
|
+
for (const a of c.arguments) {
|
|
143
|
+
if (a.variable === varName)
|
|
144
|
+
return true;
|
|
145
|
+
const head = (a.expression ?? '').trim().split(/[.\s(]/, 1)[0];
|
|
146
|
+
if (head === varName)
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=_credential-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_credential-helpers.js","sourceRoot":"","sources":["../../../src/analysis/passes/_credential-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,6EAA6E;AAC7E,MAAM,eAAe,GACnB,sGAAsG,CAAC;AAEzG,mEAAmE;AACnE,MAAM,UAAU,sBAAsB,CAAC,IAA+B;IACpE,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,+EAA+E;IAC/E,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,sBAAsB,CAAC,GAA6B;IAClE,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,QAAQ,IAAI,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,sEAAsE;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACnB,IACE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EACtC,CAAC;QACD,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,4FAA4F;AAC5F,MAAM,UAAU,SAAS,CAAC,IAAc,EAAE,QAAgB;IACxD,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;IAChE,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;IAChD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IACE,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QACvB,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;QACvB,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EACvB,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,GAAG,CAAC,OAAO;QAAE,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,oEAAoE;AACpE,8EAA8E;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAc;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IAEzC,0BAA0B;IAC1B,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5D,OAAO,CACL,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,UAAU;YACjE,MAAM,KAAK,sBAAsB,IAAI,MAAM,KAAK,wBAAwB,CACzE,CAAC;IACJ,CAAC;IAED,SAAS;IACT,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5D,OAAO,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,cAAc,CAAC;IAC7E,CAAC;IAED,2BAA2B;IAC3B,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IACzC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/E,sCAAsC;IACtC,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEhE,iBAAiB;IACjB,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,OAAO,CACL,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,YAAY;YAClD,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,YAAY;YAC9C,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,YAAY,CAC/C,CAAC;IACJ,CAAC;IAED,qBAAqB;IACrB,IAAI,QAAQ,KAAK,eAAe,IAAI,QAAQ,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACxE,OAAO,MAAM,KAAK,aAAa,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,CAAC;IAChF,CAAC;IAED,kCAAkC;IAClC,IAAI,QAAQ,KAAK,aAAa,IAAI,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qDAAqD;IACrD,IAAI,QAAQ,KAAK,kBAAkB,IAAI,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC9E,OAAO,MAAM,KAAK,aAAa,IAAI,MAAM,KAAK,gBAAgB,CAAC;IACjE,CAAC;IACD,IAAI,MAAM,KAAK,YAAY;QAAE,OAAO,IAAI,CAAC;IAEzC,4BAA4B;IAC5B,IACE,QAAQ,KAAK,KAAK,IAAI,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,QAAQ;QAClE,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,MAAM;QAC5C,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;QACvD,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,EAC5D,CAAC;QACD,OAAO,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,QAAQ,CAAC;IAC5F,CAAC;IAED,gDAAgD;IAChD,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAC/B,IACE,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,UAAU;QAClD,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,YAAY;QACpC,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,YAAY;QACpC,OAAO,IAAI,CAAC;IAEd,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,WAAW,CAAC,OAAe,EAAE,UAAsB;IACjE,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;YAAE,SAAS;QACrC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO;gBAAE,OAAO,IAAI,CAAC;YACxC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/D,IAAI,IAAI,KAAK,OAAO;gBAAE,OAAO,IAAI,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: cleartext-credential-transport (CWE-523, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects HTTP requests to an `http://` URL where the request body /
|
|
5
|
+
* params / headers carry a credential-named identifier.
|
|
6
|
+
*
|
|
7
|
+
* Detection per language:
|
|
8
|
+
* Python:
|
|
9
|
+
* - `requests.post|put|patch("http://...", ..., data|json=password)`
|
|
10
|
+
* - `urllib.request.urlopen("http://...", data=password)`
|
|
11
|
+
* - `httpx.post|put|patch(...)`
|
|
12
|
+
* JS/TS:
|
|
13
|
+
* - `fetch("http://...", { body: { password } })`
|
|
14
|
+
* - `axios.post|put|patch("http://...", { password })`
|
|
15
|
+
* - `http.request({ host, ... }, ...)` with `protocol: 'http:'` or
|
|
16
|
+
* absent + body contains credential.
|
|
17
|
+
* Java:
|
|
18
|
+
* - `new URL("http://...")` + `outputStream.write(...)` carrying credential.
|
|
19
|
+
* - `HttpClient.send(...)` with `URI.create("http://...")`.
|
|
20
|
+
* Go:
|
|
21
|
+
* - `http.Post("http://...", ..., body)` w/ credential.
|
|
22
|
+
* - `http.NewRequest("POST", "http://...", body)` w/ credential.
|
|
23
|
+
*
|
|
24
|
+
* Negative guards (URL allowlist):
|
|
25
|
+
* - `localhost`, `127.0.0.1`, `0.0.0.0` (dev environments).
|
|
26
|
+
*/
|
|
27
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
28
|
+
export interface CleartextCredentialTransportResult {
|
|
29
|
+
findings: Array<{
|
|
30
|
+
line: number;
|
|
31
|
+
language: string;
|
|
32
|
+
api: string;
|
|
33
|
+
url: string;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
export declare class CleartextCredentialTransportPass implements AnalysisPass<CleartextCredentialTransportResult> {
|
|
37
|
+
readonly name = "cleartext-credential-transport";
|
|
38
|
+
readonly category: "security";
|
|
39
|
+
run(ctx: PassContext): CleartextCredentialTransportResult;
|
|
40
|
+
private detect;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=cleartext-credential-transport-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleartext-credential-transport-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/cleartext-credential-transport-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAU9E,MAAM,WAAW,kCAAkC;IACjD,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AA0CD,qBAAa,gCACX,YAAW,YAAY,CAAC,kCAAkC,CAAC;IAE3D,QAAQ,CAAC,IAAI,oCAAoC;IACjD,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,kCAAkC;IAoCzD,OAAO,CAAC,MAAM;CA8Ff"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: cleartext-credential-transport (CWE-523, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects HTTP requests to an `http://` URL where the request body /
|
|
5
|
+
* params / headers carry a credential-named identifier.
|
|
6
|
+
*
|
|
7
|
+
* Detection per language:
|
|
8
|
+
* Python:
|
|
9
|
+
* - `requests.post|put|patch("http://...", ..., data|json=password)`
|
|
10
|
+
* - `urllib.request.urlopen("http://...", data=password)`
|
|
11
|
+
* - `httpx.post|put|patch(...)`
|
|
12
|
+
* JS/TS:
|
|
13
|
+
* - `fetch("http://...", { body: { password } })`
|
|
14
|
+
* - `axios.post|put|patch("http://...", { password })`
|
|
15
|
+
* - `http.request({ host, ... }, ...)` with `protocol: 'http:'` or
|
|
16
|
+
* absent + body contains credential.
|
|
17
|
+
* Java:
|
|
18
|
+
* - `new URL("http://...")` + `outputStream.write(...)` carrying credential.
|
|
19
|
+
* - `HttpClient.send(...)` with `URI.create("http://...")`.
|
|
20
|
+
* Go:
|
|
21
|
+
* - `http.Post("http://...", ..., body)` w/ credential.
|
|
22
|
+
* - `http.NewRequest("POST", "http://...", body)` w/ credential.
|
|
23
|
+
*
|
|
24
|
+
* Negative guards (URL allowlist):
|
|
25
|
+
* - `localhost`, `127.0.0.1`, `0.0.0.0` (dev environments).
|
|
26
|
+
*/
|
|
27
|
+
import { argLooksLikeCredential, literalAt, isCredentialIdentifier, } from './_credential-helpers.js';
|
|
28
|
+
const LOCALHOST_RE = /^(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(?::\d+)?$/i;
|
|
29
|
+
/** True if `urlLiteral` is an `http://` URL pointing at a non-localhost host. */
|
|
30
|
+
function isInsecureHttpUrl(urlLiteral) {
|
|
31
|
+
if (!urlLiteral)
|
|
32
|
+
return false;
|
|
33
|
+
if (!/^http:\/\//i.test(urlLiteral))
|
|
34
|
+
return false;
|
|
35
|
+
// Extract host portion.
|
|
36
|
+
const rest = urlLiteral.slice('http://'.length);
|
|
37
|
+
const host = rest.split('/', 1)[0] ?? '';
|
|
38
|
+
if (LOCALHOST_RE.test(host))
|
|
39
|
+
return false;
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* True if any argument expression (after position 0) carries a credential
|
|
44
|
+
* keyword — either as identifier, as `{password}` object literal, or as a
|
|
45
|
+
* keyword arg like `data=password` / `json={"password": pw}`.
|
|
46
|
+
*/
|
|
47
|
+
function anyArgCarriesCredential(call, startPos) {
|
|
48
|
+
for (const a of call.arguments) {
|
|
49
|
+
if (a.position < startPos)
|
|
50
|
+
continue;
|
|
51
|
+
// Direct identifier (`requests.post(url, password)` shape).
|
|
52
|
+
if (argLooksLikeCredential(a))
|
|
53
|
+
return true;
|
|
54
|
+
// Inline object literal: `{ password: pw, ... }`.
|
|
55
|
+
const expr = (a.expression ?? '').trim();
|
|
56
|
+
if (!expr)
|
|
57
|
+
continue;
|
|
58
|
+
// Look for credential-keyword as a key OR value identifier.
|
|
59
|
+
if (/(?:["'`]?(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|credential)["'`]?\s*[:=])/i
|
|
60
|
+
.test(expr)) {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
// Look for credential identifier appearing as a word.
|
|
64
|
+
if (/\b(?:password|passwd|pwd|secret|api_key|api-key|apiKey|auth_token|authToken|credential)\w*\b/i
|
|
65
|
+
.test(expr)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
export class CleartextCredentialTransportPass {
|
|
72
|
+
name = 'cleartext-credential-transport';
|
|
73
|
+
category = 'security';
|
|
74
|
+
run(ctx) {
|
|
75
|
+
const { graph, language } = ctx;
|
|
76
|
+
const file = graph.ir.meta.file;
|
|
77
|
+
const findings = [];
|
|
78
|
+
for (const call of graph.ir.calls) {
|
|
79
|
+
const detection = this.detect(call, language);
|
|
80
|
+
if (!detection)
|
|
81
|
+
continue;
|
|
82
|
+
const { api, url } = detection;
|
|
83
|
+
const line = call.location.line;
|
|
84
|
+
findings.push({ line, language, api, url });
|
|
85
|
+
ctx.addFinding({
|
|
86
|
+
id: `${this.name}-${file}-${line}`,
|
|
87
|
+
pass: this.name,
|
|
88
|
+
category: this.category,
|
|
89
|
+
rule_id: this.name,
|
|
90
|
+
cwe: 'CWE-523',
|
|
91
|
+
severity: 'high',
|
|
92
|
+
level: 'error',
|
|
93
|
+
message: `Credentials transmitted to \`${url}\` over HTTP via \`${api}\`. ` +
|
|
94
|
+
'Cleartext transport exposes credentials to network observers.',
|
|
95
|
+
file,
|
|
96
|
+
line,
|
|
97
|
+
fix: 'Use HTTPS (https://) for all endpoints that receive credentials. ' +
|
|
98
|
+
'For internal traffic, terminate TLS at the service boundary.',
|
|
99
|
+
evidence: { api, url, language },
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return { findings };
|
|
103
|
+
}
|
|
104
|
+
detect(call, language) {
|
|
105
|
+
const method = call.method_name ?? '';
|
|
106
|
+
const receiver = call.receiver ?? '';
|
|
107
|
+
const recvLower = receiver.toLowerCase();
|
|
108
|
+
// Python: requests.post / .put / .patch / .request
|
|
109
|
+
if (language === 'python') {
|
|
110
|
+
if ((recvLower === 'requests' || recvLower === 'httpx' ||
|
|
111
|
+
recvLower.endsWith('.requests') || recvLower.endsWith('.httpx')) &&
|
|
112
|
+
(method === 'post' || method === 'put' || method === 'patch' ||
|
|
113
|
+
method === 'request')) {
|
|
114
|
+
const url = literalAt(call, method === 'request' ? 1 : 0);
|
|
115
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
|
|
116
|
+
return { api: `${receiver}.${method}`, url: url };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// urllib.request.urlopen("http://...", data=password)
|
|
120
|
+
if (method === 'urlopen' && recvLower.includes('urllib')) {
|
|
121
|
+
const url = literalAt(call, 0);
|
|
122
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
|
|
123
|
+
return { api: 'urllib.request.urlopen', url: url };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// JS/TS: axios.post / fetch / http.request
|
|
128
|
+
if (language === 'javascript' || language === 'typescript') {
|
|
129
|
+
if ((recvLower === 'axios' || recvLower.endsWith('.axios')) &&
|
|
130
|
+
(method === 'post' || method === 'put' || method === 'patch' ||
|
|
131
|
+
method === 'request')) {
|
|
132
|
+
const url = literalAt(call, 0);
|
|
133
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
|
|
134
|
+
return { api: `axios.${method}`, url: url };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (method === 'fetch' && receiver === '') {
|
|
138
|
+
const url = literalAt(call, 0);
|
|
139
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
|
|
140
|
+
return { api: 'fetch', url: url };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// node http.request(url, opts, cb) — first-arg URL string form.
|
|
144
|
+
if (method === 'request' &&
|
|
145
|
+
(recvLower === 'http' || recvLower.endsWith('.http'))) {
|
|
146
|
+
const url = literalAt(call, 0);
|
|
147
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
|
|
148
|
+
return { api: 'http.request', url: url };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Java: URL("http://...") — flag the URL constructor when a credential
|
|
153
|
+
// identifier appears in the same in_method body. Conservative: only
|
|
154
|
+
// emit when the URL literal itself is http:// and there's a credential
|
|
155
|
+
// var present in the same method scope.
|
|
156
|
+
if (language === 'java' && method === 'URL' && receiver === '') {
|
|
157
|
+
const url = literalAt(call, 0);
|
|
158
|
+
if (!isInsecureHttpUrl(url))
|
|
159
|
+
return null;
|
|
160
|
+
// Look for any other call in the same in_method that carries a
|
|
161
|
+
// credential identifier.
|
|
162
|
+
const scope = call.in_method ?? null;
|
|
163
|
+
if (!scope)
|
|
164
|
+
return null;
|
|
165
|
+
// Defer the cross-call check to the run loop — we don't have access
|
|
166
|
+
// here. Conservative inline check: scan for any argument across the
|
|
167
|
+
// file's calls with credential-shape identifier within the same scope.
|
|
168
|
+
// (Done in a second pass below — see run() comment.)
|
|
169
|
+
// For inline detection we emit only when arg[0] of URL has the URL,
|
|
170
|
+
// and arg[1] (rare) carries credential — skip for now.
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
// Go: http.Post(url, ct, body)
|
|
174
|
+
if (language === 'go') {
|
|
175
|
+
if (method === 'Post' && (receiver === 'http' || receiver.endsWith('/http'))) {
|
|
176
|
+
const url = literalAt(call, 0);
|
|
177
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 1)) {
|
|
178
|
+
return { api: 'http.Post', url: url };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (method === 'NewRequest' &&
|
|
182
|
+
(receiver === 'http' || receiver.endsWith('/http'))) {
|
|
183
|
+
// (method, url, body)
|
|
184
|
+
const url = literalAt(call, 1);
|
|
185
|
+
if (isInsecureHttpUrl(url) && anyArgCarriesCredential(call, 2)) {
|
|
186
|
+
return { api: 'http.NewRequest', url: url };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Silence unused-import lint for isCredentialIdentifier (kept for future
|
|
194
|
+
// scope-based detection refinement).
|
|
195
|
+
void isCredentialIdentifier;
|
|
196
|
+
//# sourceMappingURL=cleartext-credential-transport-pass.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleartext-credential-transport-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/cleartext-credential-transport-pass.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass: info-disclosure-stacktrace (CWE-209, category: security)
|
|
3
|
+
*
|
|
4
|
+
* Detects exception stack traces / messages being returned to remote clients
|
|
5
|
+
* via an HTTP response handler. This leaks framework internals, file paths,
|
|
6
|
+
* SQL fragments, and class names — useful reconnaissance for an attacker.
|
|
7
|
+
*
|
|
8
|
+
* Detection per language:
|
|
9
|
+
* Java:
|
|
10
|
+
* - `e.printStackTrace(response.getWriter())` / `.printStackTrace(out)`
|
|
11
|
+
* where `out` is a response writer.
|
|
12
|
+
* - `response.getWriter().write(e.toString())`
|
|
13
|
+
* - `response.getWriter().println(e.getMessage())`
|
|
14
|
+
* - `new ResponseEntity<>(e.getStackTrace(), …)`
|
|
15
|
+
*
|
|
16
|
+
* Python:
|
|
17
|
+
* - `return traceback.format_exc()` from a handler-like function
|
|
18
|
+
* - `flask.jsonify(error=traceback.format_exc())`
|
|
19
|
+
* - Bare `return str(e)` / `return {"error": str(e)}` in handler
|
|
20
|
+
*
|
|
21
|
+
* JS/TS:
|
|
22
|
+
* - `res.send(err.stack)` / `res.json({error: err.stack})`
|
|
23
|
+
* - `res.json(err)` (whole error object)
|
|
24
|
+
* - `res.status(N).send(err.message + err.stack)` / similar
|
|
25
|
+
*
|
|
26
|
+
* Go:
|
|
27
|
+
* - `http.Error(w, err.Error()+debug.Stack(), 500)` — narrow
|
|
28
|
+
* - `fmt.Fprintln(w, err)` in an HTTP handler
|
|
29
|
+
*
|
|
30
|
+
* Negative guard: if the consumer is a logger (`console.error`,
|
|
31
|
+
* `logger.error`, `log.Error`), do NOT fire — logging stack traces
|
|
32
|
+
* server-side is not a leak.
|
|
33
|
+
*/
|
|
34
|
+
import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
|
|
35
|
+
export interface InfoDisclosureStacktraceResult {
|
|
36
|
+
findings: Array<{
|
|
37
|
+
line: number;
|
|
38
|
+
api: string;
|
|
39
|
+
language: string;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
export declare class InfoDisclosureStacktracePass implements AnalysisPass<InfoDisclosureStacktraceResult> {
|
|
43
|
+
readonly name = "info-disclosure-stacktrace";
|
|
44
|
+
readonly category: "security";
|
|
45
|
+
run(ctx: PassContext): InfoDisclosureStacktraceResult;
|
|
46
|
+
private makeFinding;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=info-disclosure-stacktrace-pass.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"info-disclosure-stacktrace-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/info-disclosure-stacktrace-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG9E,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AAoHD,qBAAa,4BAA6B,YAAW,YAAY,CAAC,8BAA8B,CAAC;IAC/F,QAAQ,CAAC,IAAI,gCAAgC;IAC7C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,8BAA8B;IAqDrD,OAAO,CAAC,WAAW;CAsBpB"}
|