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,222 @@
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
+ /** Receiver names that almost always indicate an HTTP response handle. */
35
+ const RESPONSE_RECEIVER_RE = /^(res|response|w|writer|ctx|c)$/i;
36
+ /** Logger receivers (negative guard). */
37
+ const LOGGER_RECEIVER_RE = /^(log|logger|slog|console|pino|winston|sentry)$/i;
38
+ /** Method names on a response that send data to the client. */
39
+ const RESPONSE_SEND_METHODS = new Set([
40
+ 'send', 'json', 'write', 'writeHead', 'end', 'sendFile',
41
+ 'println', 'print', 'getWriter',
42
+ 'Fprintln', 'Fprintf', 'Fprint',
43
+ ]);
44
+ /** Expression heuristics for "this is an exception value". */
45
+ function isExceptionExpression(expr) {
46
+ if (!expr)
47
+ return false;
48
+ const e = expr.trim();
49
+ // err.stack | err.message | e.toString() | e.getMessage() | e.getStackTrace()
50
+ // exc.format_exc() | traceback.format_exc() | str(e)
51
+ return (/\b(err|error|exc|exception|e|t|throwable)\.(stack|message|toString\(|getMessage\(|getStackTrace\(|getLocalizedMessage\(|getCause\()/i.test(e) ||
52
+ /\btraceback\.(format_exc|format_exception|print_exc)\b/i.test(e) ||
53
+ /\bdebug\.Stack\(\)/.test(e) ||
54
+ /\bstr\(\s*(err|error|exc|exception|e)\s*\)/i.test(e) ||
55
+ /\bString\(\s*(err|error|exc|exception|e)\s*\)/i.test(e));
56
+ }
57
+ /** True if an argument carries an exception-like value. */
58
+ function argIsException(arg) {
59
+ if (!arg)
60
+ return false;
61
+ if (arg.variable && /^(err|error|exc|exception|e|t|throwable)$/i.test(arg.variable)) {
62
+ return true;
63
+ }
64
+ return isExceptionExpression(arg.expression);
65
+ }
66
+ /** Detect Java: e.printStackTrace(out) where out is a response writer. */
67
+ function detectJavaPrintStackTrace(call) {
68
+ if (call.method_name !== 'printStackTrace')
69
+ return null;
70
+ // Receiver should look like an exception variable name.
71
+ const rec = call.receiver ?? '';
72
+ if (!/^(e|ex|exc|exception|err|error|t|throwable)$/i.test(rec))
73
+ return null;
74
+ // Arg 0 should be a response writer expression.
75
+ const arg0 = call.arguments.find((a) => a.position === 0);
76
+ if (!arg0)
77
+ return null;
78
+ const expr = (arg0.expression ?? arg0.variable ?? '').trim();
79
+ if (/\bresponse\.getWriter\(\)/.test(expr) ||
80
+ /\bresp\.getWriter\(\)/.test(expr) ||
81
+ /\bout\b/.test(expr) || // common name; conservative
82
+ /\bgetWriter\(\)/.test(expr)) {
83
+ return 'e.printStackTrace(response.getWriter())';
84
+ }
85
+ return null;
86
+ }
87
+ /** Detect calls of the shape `response.send(err.stack)` / `.json(err)`. */
88
+ function detectResponseLeakCall(call) {
89
+ const method = call.method_name ?? '';
90
+ const receiver = call.receiver ?? '';
91
+ if (!RESPONSE_SEND_METHODS.has(method))
92
+ return null;
93
+ if (LOGGER_RECEIVER_RE.test(receiver))
94
+ return null;
95
+ // Accept either a bare known receiver name, or one whose tail is a known name
96
+ // (e.g. `ctx.response`, `event.res`, `response.status(500)` chained returns).
97
+ const recTail = receiver.split('.').pop() ?? receiver;
98
+ const recHead = receiver.split('.')[0] ?? receiver;
99
+ if (!RESPONSE_RECEIVER_RE.test(recTail) && !RESPONSE_RECEIVER_RE.test(recHead)) {
100
+ // Allow chained: res.status(500).send(...) — receiver text often contains
101
+ // `.status(` substring.
102
+ if (!/(?:^|[.\s])(res|response)\.(?:status|set|header|cookie)\b/i.test(receiver)) {
103
+ return null;
104
+ }
105
+ }
106
+ // Any argument contains an exception expression?
107
+ for (const a of call.arguments) {
108
+ if (argIsException(a)) {
109
+ return `${receiver || ''}${receiver ? '.' : ''}${method}(${(a.expression ?? a.variable ?? '').trim()})`;
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ /** Detect Python: `return traceback.format_exc()` inside any function. */
115
+ function detectPythonTracebackReturn(ctx) {
116
+ const out = [];
117
+ const lines = ctx.code.split('\n');
118
+ for (let i = 0; i < lines.length; i++) {
119
+ const ln = lines[i] ?? '';
120
+ if (/\breturn\s+traceback\.format_exc\s*\(\s*\)/.test(ln) ||
121
+ /\breturn\s+\{[^}]*traceback\.format_exc\s*\(\s*\)[^}]*\}/.test(ln) ||
122
+ /\bjsonify\s*\([^)]*traceback\.format_exc\s*\(\s*\)/.test(ln)) {
123
+ out.push({ line: i + 1, api: 'return traceback.format_exc()' });
124
+ continue;
125
+ }
126
+ // `return str(e)` in a handler context — conservative: require the
127
+ // surrounding 5-line window to contain a Flask/FastAPI/Django marker.
128
+ if (/\breturn\s+(?:str|repr)\s*\(\s*(?:e|err|error|exc|exception)\s*\)/.test(ln)) {
129
+ const start = Math.max(0, i - 8);
130
+ const end = Math.min(lines.length, i + 2);
131
+ const window = lines.slice(start, end).join('\n');
132
+ if (/@(?:app|router|blueprint)\.(?:route|get|post|put|delete|patch)\b/.test(window)) {
133
+ out.push({ line: i + 1, api: 'return str(e) in handler' });
134
+ }
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+ export class InfoDisclosureStacktracePass {
140
+ name = 'info-disclosure-stacktrace';
141
+ category = 'security';
142
+ run(ctx) {
143
+ const { graph, language } = ctx;
144
+ const file = graph.ir.meta.file;
145
+ const findings = [];
146
+ if (language === 'python') {
147
+ for (const f of detectPythonTracebackReturn(ctx)) {
148
+ findings.push({ line: f.line, api: f.api, language });
149
+ ctx.addFinding(this.makeFinding(file, f.line, f.api));
150
+ }
151
+ }
152
+ for (const call of graph.ir.calls) {
153
+ let api = null;
154
+ if (language === 'java') {
155
+ api = detectJavaPrintStackTrace(call);
156
+ if (!api)
157
+ api = detectResponseLeakCall(call);
158
+ }
159
+ else if (language === 'javascript' || language === 'typescript') {
160
+ api = detectResponseLeakCall(call);
161
+ }
162
+ else if (language === 'go') {
163
+ // http.Error(w, err.Error()+debug.Stack(), 500)
164
+ // fmt.Fprintln(w, err)
165
+ const method = call.method_name ?? '';
166
+ const rec = call.receiver ?? '';
167
+ if (rec === 'http' && method === 'Error') {
168
+ const arg1 = call.arguments.find((a) => a.position === 1);
169
+ if (argIsException(arg1))
170
+ api = 'http.Error(w, err.Error())';
171
+ }
172
+ else if (rec === 'fmt' && (method === 'Fprintln' || method === 'Fprintf' || method === 'Fprint')) {
173
+ const arg0 = call.arguments.find((a) => a.position === 0);
174
+ if (arg0 && /^(w|writer|resp|response)$/i.test((arg0.variable ?? arg0.expression ?? '').trim())) {
175
+ for (const a of call.arguments) {
176
+ if (a.position === 0)
177
+ continue;
178
+ if (argIsException(a)) {
179
+ api = `fmt.${method}(w, err)`;
180
+ break;
181
+ }
182
+ }
183
+ }
184
+ }
185
+ else {
186
+ api = detectResponseLeakCall(call);
187
+ }
188
+ }
189
+ else if (language === 'python') {
190
+ // Handle response leak shape too: e.g. `return jsonify(stack=...)`
191
+ api = detectResponseLeakCall(call);
192
+ }
193
+ if (!api)
194
+ continue;
195
+ const line = call.location.line;
196
+ findings.push({ line, api, language });
197
+ ctx.addFinding(this.makeFinding(file, line, api));
198
+ }
199
+ return { findings };
200
+ }
201
+ makeFinding(file, line, api) {
202
+ return {
203
+ id: `${this.name}-${file}-${line}`,
204
+ pass: this.name,
205
+ category: this.category,
206
+ rule_id: this.name,
207
+ cwe: 'CWE-209',
208
+ severity: 'medium',
209
+ level: 'warning',
210
+ message: `Exception detail returned to client via \`${api}\`. ` +
211
+ 'Leaking stack traces / exception messages reveals framework internals, ' +
212
+ 'file paths, and class names — useful reconnaissance for an attacker.',
213
+ file,
214
+ line,
215
+ fix: 'Return a generic error response to the client (e.g. status 500 + a ' +
216
+ 'request id) and log the full exception server-side via your logger ' +
217
+ '(e.g. `logger.error("…", e)` or `console.error(err)`).',
218
+ evidence: { api },
219
+ };
220
+ }
221
+ }
222
+ //# sourceMappingURL=info-disclosure-stacktrace-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"info-disclosure-stacktrace-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/info-disclosure-stacktrace-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pass: plaintext-password-storage (CWE-256, category: security)
3
+ *
4
+ * Detects writing a credential-named identifier to a persistent store
5
+ * (file, KV store, cookie, database) without first passing it through a
6
+ * cryptographic hash / KDF.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `open(...).write(password)` / `f.write(password)`
11
+ * - `pickle.dump(password, ...)` / `json.dump(...)` / `yaml.dump(...)`
12
+ * - `redis.set(key, password)`
13
+ * JS/TS:
14
+ * - `fs.writeFile|writeFileSync|appendFile(path, password)`
15
+ * - `localStorage.setItem(key, password)` / `sessionStorage.setItem`
16
+ * - `redis.set(key, password)`
17
+ * Java:
18
+ * - `Files.write|writeString(path, password)`
19
+ * - `FileWriter.write(password)`
20
+ * Go:
21
+ * - `os.WriteFile(name, []byte(password), ...)`
22
+ * - `f.WriteString(password)` / `f.Write([]byte(password))`
23
+ *
24
+ * Heuristic for "not hashed": intraprocedural — walk all calls earlier
25
+ * in the same `in_method` scope; if any of them is a known hash/KDF
26
+ * (see _credential-helpers `isHashFunctionCall`) and consumes the
27
+ * credential identifier, suppress.
28
+ *
29
+ * This is intentionally lightweight (no full DFG); FP risk skewed toward
30
+ * recall loss for cross-function hashing (controller → service.hash →
31
+ * repo.store), which is acceptable for v1.
32
+ */
33
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
34
+ export interface PlaintextPasswordStorageResult {
35
+ findings: Array<{
36
+ line: number;
37
+ language: string;
38
+ api: string;
39
+ identifier: string;
40
+ }>;
41
+ }
42
+ export declare class PlaintextPasswordStoragePass implements AnalysisPass<PlaintextPasswordStorageResult> {
43
+ readonly name = "plaintext-password-storage";
44
+ readonly category: "security";
45
+ run(ctx: PassContext): PlaintextPasswordStorageResult;
46
+ }
47
+ //# sourceMappingURL=plaintext-password-storage-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plaintext-password-storage-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/plaintext-password-storage-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAc9E,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,EAAE,KAAK,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ;AAsED,qBAAa,4BACX,YAAW,YAAY,CAAC,8BAA8B,CAAC;IAEvD,QAAQ,CAAC,IAAI,gCAAgC;IAC7C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,8BAA8B;CAkEtD"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Pass: plaintext-password-storage (CWE-256, category: security)
3
+ *
4
+ * Detects writing a credential-named identifier to a persistent store
5
+ * (file, KV store, cookie, database) without first passing it through a
6
+ * cryptographic hash / KDF.
7
+ *
8
+ * Detection per language:
9
+ * Python:
10
+ * - `open(...).write(password)` / `f.write(password)`
11
+ * - `pickle.dump(password, ...)` / `json.dump(...)` / `yaml.dump(...)`
12
+ * - `redis.set(key, password)`
13
+ * JS/TS:
14
+ * - `fs.writeFile|writeFileSync|appendFile(path, password)`
15
+ * - `localStorage.setItem(key, password)` / `sessionStorage.setItem`
16
+ * - `redis.set(key, password)`
17
+ * Java:
18
+ * - `Files.write|writeString(path, password)`
19
+ * - `FileWriter.write(password)`
20
+ * Go:
21
+ * - `os.WriteFile(name, []byte(password), ...)`
22
+ * - `f.WriteString(password)` / `f.Write([]byte(password))`
23
+ *
24
+ * Heuristic for "not hashed": intraprocedural — walk all calls earlier
25
+ * in the same `in_method` scope; if any of them is a known hash/KDF
26
+ * (see _credential-helpers `isHashFunctionCall`) and consumes the
27
+ * credential identifier, suppress.
28
+ *
29
+ * This is intentionally lightweight (no full DFG); FP risk skewed toward
30
+ * recall loss for cross-function hashing (controller → service.hash →
31
+ * repo.store), which is acceptable for v1.
32
+ */
33
+ import { argLooksLikeCredential, priorHashOf, } from './_credential-helpers.js';
34
+ function isWriteStorageCall(call, language) {
35
+ const method = call.method_name ?? '';
36
+ const receiver = call.receiver ?? '';
37
+ const recvLower = receiver.toLowerCase();
38
+ if (language === 'python') {
39
+ // open(...).write(pw) — receiver is a file handle; we approximate by
40
+ // method name `write` and check arg credential below.
41
+ if (method === 'write' || method === 'writelines') {
42
+ return { credPos: 0, api: `<file>.${method}` };
43
+ }
44
+ if ((recvLower === 'pickle' || recvLower === 'json' || recvLower === 'yaml') &&
45
+ (method === 'dump' || method === 'dumps')) {
46
+ return { credPos: 0, api: `${receiver}.${method}` };
47
+ }
48
+ if (recvLower === 'redis' && (method === 'set' || method === 'setex' || method === 'hset')) {
49
+ return { credPos: 1, api: `redis.${method}` };
50
+ }
51
+ }
52
+ if (language === 'javascript' || language === 'typescript') {
53
+ if ((recvLower === 'fs' || recvLower.endsWith('.fs')) &&
54
+ (method === 'writeFile' || method === 'writeFileSync' ||
55
+ method === 'appendFile' || method === 'appendFileSync')) {
56
+ return { credPos: 1, api: `fs.${method}` };
57
+ }
58
+ if ((recvLower === 'localstorage' || recvLower === 'sessionstorage') &&
59
+ method === 'setItem') {
60
+ return { credPos: 1, api: `${receiver}.setItem` };
61
+ }
62
+ if (recvLower === 'redis' && (method === 'set' || method === 'setex' || method === 'hset')) {
63
+ return { credPos: 1, api: `redis.${method}` };
64
+ }
65
+ }
66
+ if (language === 'java') {
67
+ if ((receiver === 'Files' || receiver.endsWith('.Files')) &&
68
+ (method === 'write' || method === 'writeString')) {
69
+ return { credPos: 1, api: `Files.${method}` };
70
+ }
71
+ // FileWriter.write(pw) — instance call, single arg.
72
+ if (method === 'write') {
73
+ // Heuristic: receiver name contains "writer" / "file" / "stream".
74
+ const lc = (receiver ?? '').toLowerCase();
75
+ if (lc.includes('writer') || lc.includes('file') || lc.includes('stream')) {
76
+ return { credPos: 0, api: `${receiver}.write` };
77
+ }
78
+ }
79
+ }
80
+ if (language === 'go') {
81
+ if (receiver === 'os' || receiver.endsWith('/os')) {
82
+ if (method === 'WriteFile')
83
+ return { credPos: 1, api: 'os.WriteFile' };
84
+ }
85
+ if (receiver === 'ioutil' || receiver.endsWith('/ioutil')) {
86
+ if (method === 'WriteFile')
87
+ return { credPos: 1, api: 'ioutil.WriteFile' };
88
+ }
89
+ if (method === 'WriteString' || method === 'Write') {
90
+ return { credPos: 0, api: `<file>.${method}` };
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ export class PlaintextPasswordStoragePass {
96
+ name = 'plaintext-password-storage';
97
+ category = 'security';
98
+ run(ctx) {
99
+ const { graph, language } = ctx;
100
+ const file = graph.ir.meta.file;
101
+ const findings = [];
102
+ // Group calls by in_method for cheap prior-hash lookup.
103
+ const callsByScope = new Map();
104
+ for (const call of graph.ir.calls) {
105
+ const scope = call.in_method ?? '<top>';
106
+ const arr = callsByScope.get(scope) ?? [];
107
+ arr.push(call);
108
+ callsByScope.set(scope, arr);
109
+ }
110
+ for (const call of graph.ir.calls) {
111
+ const spec = isWriteStorageCall(call, language);
112
+ if (!spec)
113
+ continue;
114
+ const credArg = call.arguments.find((a) => a.position === spec.credPos);
115
+ if (!credArg)
116
+ continue;
117
+ if (!argLooksLikeCredential(credArg))
118
+ continue;
119
+ // Resolve the credential identifier name.
120
+ const identExpr = (credArg.expression ?? '').trim();
121
+ const head = identExpr.split(/[.\s(]/, 1)[0] ?? '';
122
+ const identifier = credArg.variable ?? head;
123
+ if (!identifier)
124
+ continue;
125
+ // Suppress if the identifier was hashed earlier in the same scope.
126
+ const scope = call.in_method ?? '<top>';
127
+ const scopeCalls = callsByScope.get(scope) ?? [];
128
+ const prior = scopeCalls.filter((c) => c.location.line < call.location.line);
129
+ if (priorHashOf(identifier, prior))
130
+ continue;
131
+ // Suppress if the credArg expression itself contains a hash call
132
+ // inline: `f.write(bcrypt.hashpw(pw))`.
133
+ if (/\b(?:hashpw|hash|sha\d+|md5|bcrypt|argon2|pbkdf2|digest)\b/i
134
+ .test(credArg.expression ?? '')) {
135
+ continue;
136
+ }
137
+ const line = call.location.line;
138
+ findings.push({ line, language, api: spec.api, identifier });
139
+ ctx.addFinding({
140
+ id: `${this.name}-${file}-${line}`,
141
+ pass: this.name,
142
+ category: this.category,
143
+ rule_id: this.name,
144
+ cwe: 'CWE-256',
145
+ severity: 'high',
146
+ level: 'warning',
147
+ message: `Credential \`${identifier}\` written in plaintext via \`${spec.api}\`. ` +
148
+ 'Passwords / secrets must be hashed (Argon2id, bcrypt) before storage.',
149
+ file,
150
+ line,
151
+ fix: 'Hash the credential with Argon2id / bcrypt before writing it to ' +
152
+ 'disk, cookie, KV store, or database.',
153
+ evidence: { identifier, api: spec.api, language },
154
+ });
155
+ }
156
+ return { findings };
157
+ }
158
+ }
159
+ //# sourceMappingURL=plaintext-password-storage-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plaintext-password-storage-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/plaintext-password-storage-pass.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
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
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
33
+ export interface UnrestrictedFileUploadResult {
34
+ findings: Array<{
35
+ line: number;
36
+ api: string;
37
+ language: string;
38
+ }>;
39
+ }
40
+ export declare class UnrestrictedFileUploadPass implements AnalysisPass<UnrestrictedFileUploadResult> {
41
+ readonly name = "unrestricted-file-upload";
42
+ readonly category: "security";
43
+ run(ctx: PassContext): UnrestrictedFileUploadResult;
44
+ private emit;
45
+ }
46
+ //# sourceMappingURL=unrestricted-file-upload-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unrestricted-file-upload-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/unrestricted-file-upload-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG9E,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClE;AA2BD,qBAAa,0BAA2B,YAAW,YAAY,CAAC,4BAA4B,CAAC;IAC3F,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,4BAA4B;IAyHnD,OAAO,CAAC,IAAI;CAgCb"}