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.
Files changed (39) hide show
  1. package/configs/sinks/xss.yaml +2 -1
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +26 -4
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/passes/_credential-helpers.d.ts +40 -0
  6. package/dist/analysis/passes/_credential-helpers.d.ts.map +1 -0
  7. package/dist/analysis/passes/_credential-helpers.js +152 -0
  8. package/dist/analysis/passes/_credential-helpers.js.map +1 -0
  9. package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts +42 -0
  10. package/dist/analysis/passes/cleartext-credential-transport-pass.d.ts.map +1 -0
  11. package/dist/analysis/passes/cleartext-credential-transport-pass.js +196 -0
  12. package/dist/analysis/passes/cleartext-credential-transport-pass.js.map +1 -0
  13. package/dist/analysis/passes/info-disclosure-stacktrace-pass.d.ts +48 -0
  14. package/dist/analysis/passes/info-disclosure-stacktrace-pass.d.ts.map +1 -0
  15. package/dist/analysis/passes/info-disclosure-stacktrace-pass.js +222 -0
  16. package/dist/analysis/passes/info-disclosure-stacktrace-pass.js.map +1 -0
  17. package/dist/analysis/passes/plaintext-password-storage-pass.d.ts +47 -0
  18. package/dist/analysis/passes/plaintext-password-storage-pass.d.ts.map +1 -0
  19. package/dist/analysis/passes/plaintext-password-storage-pass.js +159 -0
  20. package/dist/analysis/passes/plaintext-password-storage-pass.js.map +1 -0
  21. package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts +46 -0
  22. package/dist/analysis/passes/unrestricted-file-upload-pass.d.ts.map +1 -0
  23. package/dist/analysis/passes/unrestricted-file-upload-pass.js +193 -0
  24. package/dist/analysis/passes/unrestricted-file-upload-pass.js.map +1 -0
  25. package/dist/analysis/passes/weak-password-encoding-pass.d.ts +40 -0
  26. package/dist/analysis/passes/weak-password-encoding-pass.d.ts.map +1 -0
  27. package/dist/analysis/passes/weak-password-encoding-pass.js +157 -0
  28. package/dist/analysis/passes/weak-password-encoding-pass.js.map +1 -0
  29. package/dist/analysis/passes/weak-password-hash-pass.d.ts +49 -0
  30. package/dist/analysis/passes/weak-password-hash-pass.d.ts.map +1 -0
  31. package/dist/analysis/passes/weak-password-hash-pass.js +225 -0
  32. package/dist/analysis/passes/weak-password-hash-pass.js.map +1 -0
  33. package/dist/analyzer.d.ts.map +1 -1
  34. package/dist/analyzer.js +18 -0
  35. package/dist/analyzer.js.map +1 -1
  36. package/dist/browser/circle-ir.js +912 -4
  37. package/dist/core/circle-ir-core.cjs +26 -4
  38. package/dist/core/circle-ir-core.js +26 -4
  39. package/package.json +1 -1
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Pass: unrestricted-file-upload (CWE-434, category: security)
3
+ *
4
+ * Detects when an HTTP-uploaded file is saved to disk WITHOUT a filename
5
+ * allow-list (extension check) or `secure_filename` normalization.
6
+ *
7
+ * Detection (per language):
8
+ *
9
+ * Java (Spring MultipartFile / Servlet Part):
10
+ * - `file.transferTo(new File(dir, file.getOriginalFilename()))`
11
+ * - `Files.copy(part.getInputStream(), Path.of(dir, part.getSubmittedFileName()))`
12
+ * - Without preceding `ALLOWED_*.contains(ext)` or
13
+ * `FilenameUtils.getExtension(name)` + check.
14
+ *
15
+ * JS/TS (multer / express-fileupload):
16
+ * - `multer({ dest: '…' })` with NO `fileFilter` option.
17
+ * - `fs.writeFile(path, req.file.buffer)` / `req.files.x.mv(path)`
18
+ * without prior `path.extname` allow-list check.
19
+ *
20
+ * Python (Flask / Django / FastAPI):
21
+ * - `f.save(os.path.join(UPLOAD_DIR, f.filename))` without prior
22
+ * `secure_filename(f.filename)` wrapping.
23
+ *
24
+ * Go:
25
+ * - `io.Copy(dst, file)` where `dst = os.Create(fileHeader.Filename)`
26
+ * without an extension allow-list.
27
+ *
28
+ * The pass is intentionally conservative — it only fires when an upload-name
29
+ * expression flows directly into a save sink in the same function and no
30
+ * known allow-list / canonicalizer call appears earlier in the function.
31
+ */
32
+ /** Receivers / expressions that look like an uploaded file value. */
33
+ const UPLOAD_NAME_RE = /(?:getOriginalFilename|getSubmittedFileName|originalname|originalName|\.filename|\.Filename|FileHeader\.Filename|UploadFile)/;
34
+ /** Identifier-level allow-list / canonicalization calls that defang upload paths. */
35
+ const FILE_SAFE_CALL_RE = /(?:secure_filename|FilenameUtils\.getExtension|\.lastIndexOf\(['"]\.['"]\)|ALLOWED_EXT|ALLOWED_EXTENSIONS|allowedExtensions|\bfileFilter\b|filepath\.Ext|path\.extname)/;
36
+ function lineWindow(code, startLine, endLine) {
37
+ const lines = code.split('\n');
38
+ const s = Math.max(0, startLine - 1);
39
+ const e = Math.min(lines.length, endLine);
40
+ return lines.slice(s, e).join('\n');
41
+ }
42
+ function callHasUploadName(call) {
43
+ for (const a of call.arguments) {
44
+ const expr = (a.expression ?? a.variable ?? '').trim();
45
+ if (UPLOAD_NAME_RE.test(expr))
46
+ return true;
47
+ }
48
+ // Receiver too (e.g. multipart `file.transferTo(...)`).
49
+ if (UPLOAD_NAME_RE.test(call.receiver ?? ''))
50
+ return true;
51
+ return false;
52
+ }
53
+ export class UnrestrictedFileUploadPass {
54
+ name = 'unrestricted-file-upload';
55
+ category = 'security';
56
+ run(ctx) {
57
+ const { graph, language, code } = ctx;
58
+ const file = graph.ir.meta.file;
59
+ const findings = [];
60
+ // Build per-function safety windows: a function is "safe" if its body
61
+ // contains any FILE_SAFE_CALL_RE marker. Used to suppress findings inside
62
+ // that scope.
63
+ const safeFunctionRanges = [];
64
+ for (const t of graph.ir.types) {
65
+ for (const m of t.methods) {
66
+ const body = lineWindow(code, m.start_line, m.end_line);
67
+ if (FILE_SAFE_CALL_RE.test(body)) {
68
+ safeFunctionRanges.push({ start: m.start_line, end: m.end_line });
69
+ }
70
+ }
71
+ }
72
+ const inSafeRange = (line) => {
73
+ for (const r of safeFunctionRanges) {
74
+ if (line >= r.start && line <= r.end)
75
+ return true;
76
+ }
77
+ // No method information — fall back to a ±20-line window around the call.
78
+ const win = lineWindow(code, Math.max(1, line - 20), line + 5);
79
+ return FILE_SAFE_CALL_RE.test(win);
80
+ };
81
+ // --- Per-language detection -----------------------------------------------
82
+ if (language === 'java') {
83
+ for (const call of graph.ir.calls) {
84
+ const m = call.method_name ?? '';
85
+ // file.transferTo(...)
86
+ if (m === 'transferTo' && callHasUploadName(call)) {
87
+ if (inSafeRange(call.location.line))
88
+ continue;
89
+ this.emit(ctx, findings, file, call.location.line, language, 'MultipartFile.transferTo(<original filename>)');
90
+ continue;
91
+ }
92
+ // Files.copy(getInputStream, Path.of(dir, getOriginalFilename))
93
+ if (m === 'copy' && (call.receiver === 'Files' || (call.receiver ?? '').endsWith('.Files'))) {
94
+ if (callHasUploadName(call)) {
95
+ if (inSafeRange(call.location.line))
96
+ continue;
97
+ this.emit(ctx, findings, file, call.location.line, language, 'Files.copy(input, Path.of(dir, <original filename>))');
98
+ }
99
+ }
100
+ }
101
+ }
102
+ if (language === 'javascript' || language === 'typescript') {
103
+ for (const call of graph.ir.calls) {
104
+ const m = call.method_name ?? '';
105
+ const rec = call.receiver ?? '';
106
+ // multer({ dest: '...' }) — flag if same call object lacks fileFilter.
107
+ if (m === 'multer' || (rec === '' && m === 'multer')) {
108
+ const arg0 = call.arguments.find((a) => a.position === 0);
109
+ const expr = (arg0?.expression ?? '').trim();
110
+ if (/\bdest\s*:/.test(expr) && !/\bfileFilter\s*:/.test(expr)) {
111
+ if (inSafeRange(call.location.line))
112
+ continue;
113
+ this.emit(ctx, findings, file, call.location.line, language, 'multer({ dest }) without fileFilter');
114
+ continue;
115
+ }
116
+ }
117
+ // fs.writeFile(<path>, req.file.buffer) / req.files.x.mv(<path>)
118
+ if (rec === 'fs' && (m === 'writeFile' || m === 'writeFileSync' || m === 'appendFile')) {
119
+ if (callHasUploadName(call) ||
120
+ call.arguments.some((a) => /\breq\.file(?:s)?\b/.test((a.expression ?? a.variable ?? '')))) {
121
+ if (inSafeRange(call.location.line))
122
+ continue;
123
+ this.emit(ctx, findings, file, call.location.line, language, `fs.${m}(<path>, req.file.buffer)`);
124
+ }
125
+ }
126
+ }
127
+ }
128
+ if (language === 'python') {
129
+ for (const call of graph.ir.calls) {
130
+ const m = call.method_name ?? '';
131
+ // f.save(os.path.join(UPLOAD_DIR, f.filename))
132
+ if (m === 'save') {
133
+ const rec = call.receiver ?? '';
134
+ // Accept any receiver that looks file-ish or empty (`f.save`, `file.save`).
135
+ if (!/^(f|file|upload|attachment)$/i.test(rec) && rec !== '')
136
+ continue;
137
+ if (!callHasUploadName(call))
138
+ continue;
139
+ if (inSafeRange(call.location.line))
140
+ continue;
141
+ this.emit(ctx, findings, file, call.location.line, language, 'f.save(<dir>, f.filename) without secure_filename');
142
+ }
143
+ }
144
+ }
145
+ if (language === 'go') {
146
+ for (const call of graph.ir.calls) {
147
+ const m = call.method_name ?? '';
148
+ const rec = call.receiver ?? '';
149
+ // os.Create(header.Filename)
150
+ if (rec === 'os' && (m === 'Create' || m === 'OpenFile')) {
151
+ if (callHasUploadName(call)) {
152
+ if (inSafeRange(call.location.line))
153
+ continue;
154
+ this.emit(ctx, findings, file, call.location.line, language, `os.${m}(<uploaded filename>)`);
155
+ }
156
+ }
157
+ // ioutil.WriteFile(header.Filename, …)
158
+ if ((rec === 'os' || rec === 'ioutil') && m === 'WriteFile') {
159
+ if (callHasUploadName(call)) {
160
+ if (inSafeRange(call.location.line))
161
+ continue;
162
+ this.emit(ctx, findings, file, call.location.line, language, `${rec}.WriteFile(<uploaded filename>, …)`);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ return { findings };
168
+ }
169
+ emit(ctx, findings, file, line, language, api) {
170
+ findings.push({ line, api, language });
171
+ ctx.addFinding({
172
+ id: `${this.name}-${file}-${line}`,
173
+ pass: this.name,
174
+ category: this.category,
175
+ rule_id: this.name,
176
+ cwe: 'CWE-434',
177
+ severity: 'high',
178
+ level: 'error',
179
+ message: `File upload saved using untrusted name (${api}) — no extension allow-list or ` +
180
+ 'filename canonicalization detected. An attacker can upload a `.jsp`/`.php`/`.html` ' +
181
+ 'file and request it back, achieving RCE or stored XSS.',
182
+ file,
183
+ line,
184
+ fix: 'Validate the uploaded extension against an allow-list (e.g. ' +
185
+ '`Set.of("png","jpg")`), then save with a sanitized filename. In Python use ' +
186
+ '`werkzeug.utils.secure_filename`. In multer pass a `fileFilter`. Never ' +
187
+ 'concatenate the upload\'s original filename into a save path without ' +
188
+ 'validation.',
189
+ evidence: { api, language },
190
+ });
191
+ }
192
+ }
193
+ //# sourceMappingURL=unrestricted-file-upload-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unrestricted-file-upload-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/unrestricted-file-upload-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AASH,qEAAqE;AACrE,MAAM,cAAc,GAClB,8HAA8H,CAAC;AAEjI,qFAAqF;AACrF,MAAM,iBAAiB,GACrB,yKAAyK,CAAC;AAE5K,SAAS,UAAU,CAAC,IAAY,EAAE,SAAiB,EAAE,OAAe;IAClE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;IACrC,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAc;IACvC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAC7C,CAAC;IACD,wDAAwD;IACxD,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,OAAO,0BAA0B;IAC5B,IAAI,GAAG,0BAA0B,CAAC;IAClC,QAAQ,GAAG,UAAmB,CAAC;IAExC,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAChC,MAAM,QAAQ,GAA6C,EAAE,CAAC;QAE9D,sEAAsE;QACtE,0EAA0E;QAC1E,cAAc;QACd,MAAM,kBAAkB,GAA0C,EAAE,CAAC;QACrE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAC/B,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC1B,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC;gBACxD,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACjC,kBAAkB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACpE,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE;YAC5C,KAAK,MAAM,CAAC,IAAI,kBAAkB,EAAE,CAAC;gBACnC,IAAI,IAAI,IAAI,CAAC,CAAC,KAAK,IAAI,IAAI,IAAI,CAAC,CAAC,GAAG;oBAAE,OAAO,IAAI,CAAC;YACpD,CAAC;YACD,0EAA0E;YAC1E,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;YAC/D,OAAO,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC,CAAC;QAEF,6EAA6E;QAE7E,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACxB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;gBACjC,uBAAuB;gBACvB,IAAI,CAAC,KAAK,YAAY,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;oBAClD,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,+CAA+C,CAAC,CAAC;oBAC3D,SAAS;gBACX,CAAC;gBACD,gEAAgE;gBAChE,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;oBAC5F,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC5B,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;4BAAE,SAAS;wBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,sDAAsD,CAAC,CAAC;oBACpE,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC3D,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAEhC,uEAAuE;gBACvE,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,GAAG,KAAK,EAAE,IAAI,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC;oBACrD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;oBAC1D,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC7C,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC9D,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;4BAAE,SAAS;wBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,qCAAqC,CAAC,CAAC;wBACjD,SAAS;oBACX,CAAC;gBACH,CAAC;gBAED,iEAAiE;gBACjE,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,WAAW,IAAI,CAAC,KAAK,eAAe,IAAI,CAAC,KAAK,YAAY,CAAC,EAAE,CAAC;oBACvF,IAAI,iBAAiB,CAAC,IAAI,CAAC;wBACvB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC/F,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;4BAAE,SAAS;wBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,MAAM,CAAC,2BAA2B,CAAC,CAAC;oBAChD,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;gBACjC,+CAA+C;gBAC/C,IAAI,CAAC,KAAK,MAAM,EAAE,CAAC;oBACjB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;oBAChC,4EAA4E;oBAC5E,IAAI,CAAC,+BAA+B,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,EAAE;wBAAE,SAAS;oBACvE,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC;wBAAE,SAAS;oBACvC,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;wBAAE,SAAS;oBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,mDAAmD,CAAC,CAAC;gBACjE,CAAC;YACH,CAAC;QACH,CAAC;QAED,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YACtB,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;gBAClC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;gBACjC,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAChC,6BAA6B;gBAC7B,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,UAAU,CAAC,EAAE,CAAC;oBACzD,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC5B,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;4BAAE,SAAS;wBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,MAAM,CAAC,uBAAuB,CAAC,CAAC;oBAC5C,CAAC;gBACH,CAAC;gBACD,uCAAuC;gBACvC,IAAI,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,QAAQ,CAAC,IAAI,CAAC,KAAK,WAAW,EAAE,CAAC;oBAC5D,IAAI,iBAAiB,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC5B,IAAI,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;4BAAE,SAAS;wBAC9C,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EACjD,GAAG,GAAG,oCAAoC,CAAC,CAAC;oBACxD,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;IAEO,IAAI,CACV,GAAgB,EAChB,QAAkD,EAClD,IAAY,EACZ,IAAY,EACZ,QAAgB,EAChB,GAAW;QAEX,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QACvC,GAAG,CAAC,UAAU,CAAC;YACb,EAAE,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,EAAE;YAClC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,IAAI,CAAC,IAAI;YAClB,GAAG,EAAE,SAAS;YACd,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,OAAO;YACd,OAAO,EACL,2CAA2C,GAAG,iCAAiC;gBAC/E,qFAAqF;gBACrF,wDAAwD;YAC1D,IAAI;YACJ,IAAI;YACJ,GAAG,EACD,8DAA8D;gBAC9D,6EAA6E;gBAC7E,yEAAyE;gBACzE,uEAAuE;gBACvE,aAAa;YACf,QAAQ,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;SAC5B,CAAC,CAAC;IACL,CAAC;CACF"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Pass: weak-password-encoding (CWE-261, category: security)
3
+ *
4
+ * Detects use of an encoding (base64 / hex) on a credential-named identifier.
5
+ * Encoding is NOT encryption — base64-encoding a password before storing or
6
+ * transmitting it provides no confidentiality. Common anti-pattern.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `base64.b64encode(password)` / `.urlsafe_b64encode(...)`
11
+ * - `binascii.hexlify(password)`
12
+ * JS/TS:
13
+ * - `Buffer.from(password).toString('base64')` / `.toString('hex')`
14
+ * - `btoa(password)`
15
+ * Java:
16
+ * - `Base64.getEncoder().encodeToString(passwordBytes)`
17
+ * - `Base64.getUrlEncoder().encodeToString(...)`
18
+ * - `Hex.encodeHexString(passwordBytes)`
19
+ * Go:
20
+ * - `base64.StdEncoding.EncodeToString(passwordBytes)`
21
+ * - `hex.EncodeToString(...)`
22
+ *
23
+ * FP-guard: skip when the encoded value is part of an HTTP Basic auth
24
+ * header construction (`"Basic " + base64(...)`) — that IS the spec.
25
+ */
26
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
27
+ export interface WeakPasswordEncodingResult {
28
+ findings: Array<{
29
+ line: number;
30
+ language: string;
31
+ api: string;
32
+ }>;
33
+ }
34
+ export declare class WeakPasswordEncodingPass implements AnalysisPass<WeakPasswordEncodingResult> {
35
+ readonly name = "weak-password-encoding";
36
+ readonly category: "security";
37
+ run(ctx: PassContext): WeakPasswordEncodingResult;
38
+ private detect;
39
+ }
40
+ //# sourceMappingURL=weak-password-encoding-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weak-password-encoding-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-encoding-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAO9E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAcD,qBAAa,wBAAyB,YAAW,YAAY,CAAC,0BAA0B,CAAC;IACvF,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,0BAA0B;IAoCjD,OAAO,CAAC,MAAM;CAsFf"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Pass: weak-password-encoding (CWE-261, category: security)
3
+ *
4
+ * Detects use of an encoding (base64 / hex) on a credential-named identifier.
5
+ * Encoding is NOT encryption — base64-encoding a password before storing or
6
+ * transmitting it provides no confidentiality. Common anti-pattern.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `base64.b64encode(password)` / `.urlsafe_b64encode(...)`
11
+ * - `binascii.hexlify(password)`
12
+ * JS/TS:
13
+ * - `Buffer.from(password).toString('base64')` / `.toString('hex')`
14
+ * - `btoa(password)`
15
+ * Java:
16
+ * - `Base64.getEncoder().encodeToString(passwordBytes)`
17
+ * - `Base64.getUrlEncoder().encodeToString(...)`
18
+ * - `Hex.encodeHexString(passwordBytes)`
19
+ * Go:
20
+ * - `base64.StdEncoding.EncodeToString(passwordBytes)`
21
+ * - `hex.EncodeToString(...)`
22
+ *
23
+ * FP-guard: skip when the encoded value is part of an HTTP Basic auth
24
+ * header construction (`"Basic " + base64(...)`) — that IS the spec.
25
+ */
26
+ import { argLooksLikeCredential, literalAt, } from './_credential-helpers.js';
27
+ function isBasicAuthContext(call, code) {
28
+ // Look at the source line for "Basic " literal nearby — heuristic
29
+ // for HTTP Basic auth construction where base64 is part of the spec.
30
+ const line = call.location.line;
31
+ if (line < 1)
32
+ return false;
33
+ const lines = code.split('\n');
34
+ const start = Math.max(0, line - 2);
35
+ const end = Math.min(lines.length, line + 1);
36
+ const window = lines.slice(start, end).join('\n');
37
+ return /["'`]Basic\s/i.test(window);
38
+ }
39
+ export class WeakPasswordEncodingPass {
40
+ name = 'weak-password-encoding';
41
+ category = 'security';
42
+ run(ctx) {
43
+ const { graph, language, code } = ctx;
44
+ const file = graph.ir.meta.file;
45
+ const findings = [];
46
+ for (const call of graph.ir.calls) {
47
+ const api = this.detect(call, language);
48
+ if (!api)
49
+ continue;
50
+ if (isBasicAuthContext(call, code))
51
+ continue;
52
+ const line = call.location.line;
53
+ findings.push({ line, language, api });
54
+ ctx.addFinding({
55
+ id: `${this.name}-${file}-${line}`,
56
+ pass: this.name,
57
+ category: this.category,
58
+ rule_id: this.name,
59
+ cwe: 'CWE-261',
60
+ severity: 'medium',
61
+ level: 'warning',
62
+ message: `Credential encoded via \`${api}\` — encoding is NOT encryption. ` +
63
+ 'Base64/hex provide no confidentiality; anyone with the encoded value can decode it.',
64
+ file,
65
+ line,
66
+ fix: 'For storage, use a password hash (Argon2id / bcrypt). ' +
67
+ 'For transport, use TLS. For symmetric secrecy, use authenticated encryption (AES-GCM).',
68
+ evidence: { api, language },
69
+ });
70
+ }
71
+ return { findings };
72
+ }
73
+ detect(call, language) {
74
+ const method = call.method_name ?? '';
75
+ const receiver = call.receiver ?? '';
76
+ const recvLower = receiver.toLowerCase();
77
+ const arg0 = call.arguments.find((a) => a.position === 0);
78
+ if (language === 'python') {
79
+ // base64.b64encode(password)
80
+ if (recvLower === 'base64' &&
81
+ (method === 'b64encode' || method === 'urlsafe_b64encode' ||
82
+ method === 'standard_b64encode')) {
83
+ if (argLooksLikeCredential(arg0))
84
+ return `base64.${method}`;
85
+ }
86
+ // binascii.hexlify(password)
87
+ if (recvLower === 'binascii' && method === 'hexlify') {
88
+ if (argLooksLikeCredential(arg0))
89
+ return 'binascii.hexlify';
90
+ }
91
+ }
92
+ if (language === 'javascript' || language === 'typescript') {
93
+ // Buffer.from(password).toString('base64')
94
+ if (method === 'toString') {
95
+ const encoding = literalAt(call, 0);
96
+ if (encoding === 'base64' || encoding === 'hex' || encoding === 'base64url') {
97
+ // Receiver expression should look like `Buffer.from(<credential>)`.
98
+ // Conservative: check if receiver text contains "Buffer.from" and a
99
+ // credential keyword.
100
+ const recv = (receiver ?? '').toLowerCase();
101
+ if (recv.includes('buffer.from') &&
102
+ /(?:password|passwd|pwd|secret|api[_-]?key|auth[_-]?token|private[_-]?key|access[_-]?key|credential)/i
103
+ .test(receiver ?? '')) {
104
+ return `Buffer.from().toString('${encoding}')`;
105
+ }
106
+ }
107
+ }
108
+ // btoa(password)
109
+ if (method === 'btoa' && receiver === '') {
110
+ if (argLooksLikeCredential(arg0))
111
+ return 'btoa';
112
+ }
113
+ }
114
+ if (language === 'java') {
115
+ // Base64.getEncoder().encodeToString(passwordBytes)
116
+ // Or Base64.getUrlEncoder().encodeToString(...).
117
+ if (method === 'encodeToString') {
118
+ const recv = (receiver ?? '').toLowerCase();
119
+ if (recv.includes('encoder') || recv.includes('base64')) {
120
+ // arg[0] expr typically looks like `password.getBytes()`.
121
+ const expr = (arg0?.expression ?? '').trim();
122
+ const head = expr.split(/[.\s(]/, 1)[0] ?? '';
123
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
124
+ return 'Base64.encodeToString';
125
+ }
126
+ }
127
+ }
128
+ // Hex.encodeHexString(passwordBytes)
129
+ if (method === 'encodeHexString' &&
130
+ (receiver === 'Hex' || receiver.endsWith('.Hex'))) {
131
+ const expr = (arg0?.expression ?? '').trim();
132
+ const head = expr.split(/[.\s(]/, 1)[0] ?? '';
133
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
134
+ return 'Hex.encodeHexString';
135
+ }
136
+ }
137
+ }
138
+ if (language === 'go') {
139
+ // base64.StdEncoding.EncodeToString(passwordBytes)
140
+ if (method === 'EncodeToString') {
141
+ const recv = (receiver ?? '').toLowerCase();
142
+ if (recv.includes('base64') || recv.includes('hex') ||
143
+ recv.includes('encoding')) {
144
+ const expr = (arg0?.expression ?? '').trim();
145
+ // Strip `[]byte(...)` wrapper.
146
+ const inner = expr.replace(/^\[\]byte\s*\(\s*/, '').replace(/\s*\)\s*$/, '');
147
+ const head = inner.split(/[.\s(]/, 1)[0] ?? '';
148
+ if (argLooksLikeCredential({ position: 0, expression: head, variable: head })) {
149
+ return recv.includes('hex') ? 'hex.EncodeToString' : 'base64.EncodeToString';
150
+ }
151
+ }
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ }
157
+ //# sourceMappingURL=weak-password-encoding-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weak-password-encoding-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-encoding-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pass: weak-password-hash (CWE-916, category: security)
3
+ *
4
+ * Detects use of a fast / unsalted hash, or a KDF with insufficient
5
+ * computational cost, applied to a credential-named identifier.
6
+ *
7
+ * Distinct from `weak-hash` (CWE-328):
8
+ * - `weak-hash` flags broken algorithms (MD2/MD4/MD5/SHA-1) at any call site.
9
+ * - `weak-password-hash` flags algorithm/cost choices that are SAFE for
10
+ * general digests but UNSAFE for password storage (e.g. plain SHA-256
11
+ * of a password, bcrypt cost < 10, PBKDF2 iterations < 100k).
12
+ *
13
+ * Detection per language:
14
+ * Python:
15
+ * - `hashlib.sha256(password)` / `.sha512(...)` / etc. where the
16
+ * argument is a credential-named identifier.
17
+ * - `bcrypt.hashpw(pw, bcrypt.gensalt(rounds=N))` where N < 10.
18
+ * - `PBKDF2HMAC(..., iterations=N).derive(pw)` where N < 100000.
19
+ * JS/TS:
20
+ * - `crypto.createHash('sha256').update(password).digest()`.
21
+ * - `bcrypt.hash(pw, N)` / `bcrypt.hashSync(pw, N)` where N < 10.
22
+ * - `crypto.pbkdf2Sync(pw, salt, N, ...)` where N < 100000.
23
+ * Java:
24
+ * - `MessageDigest.getInstance("SHA-256")` followed by `.update(pw)` —
25
+ * conservative: detect `MessageDigest.getInstance` + `.update(credIdent)`
26
+ * on any non-broken algorithm. (Broken algos already flagged by weak-hash.)
27
+ * - `PBEKeySpec(pw, salt, N, ...)` where N < 100000.
28
+ * Go:
29
+ * - `sha256.Sum256([]byte(password))`, `sha512.Sum512([]byte(password))`.
30
+ * - `bcrypt.GenerateFromPassword(pw, cost)` where cost < 10.
31
+ *
32
+ * Aligned with: OWASP ASVS 2.4.1, NIST SP 800-63B §5.1.1.2, gosec G401-style.
33
+ */
34
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
35
+ export interface WeakPasswordHashResult {
36
+ findings: Array<{
37
+ line: number;
38
+ language: string;
39
+ kind: 'fast-unsalted-hash' | 'low-bcrypt-cost' | 'low-pbkdf2-iterations';
40
+ api: string;
41
+ }>;
42
+ }
43
+ export declare class WeakPasswordHashPass implements AnalysisPass<WeakPasswordHashResult> {
44
+ readonly name = "weak-password-hash";
45
+ readonly category: "security";
46
+ run(ctx: PassContext): WeakPasswordHashResult;
47
+ private detect;
48
+ }
49
+ //# sourceMappingURL=weak-password-hash-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weak-password-hash-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/weak-password-hash-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAmB9E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,oBAAoB,GAAG,iBAAiB,GAAG,uBAAuB,CAAC;QACzE,GAAG,EAAE,MAAM,CAAC;KACb,CAAC,CAAC;CACJ;AAsBD,qBAAa,oBAAqB,YAAW,YAAY,CAAC,sBAAsB,CAAC;IAC/E,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,sBAAsB;IA0C7C,OAAO,CAAC,MAAM;CAyIf"}