@datahogo/core 0.1.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/LICENSE +661 -0
- package/README.md +17 -0
- package/dist/engines/config.d.ts +3 -0
- package/dist/engines/config.d.ts.map +1 -0
- package/dist/engines/config.js +433 -0
- package/dist/engines/config.js.map +1 -0
- package/dist/engines/dast.d.ts +3 -0
- package/dist/engines/dast.d.ts.map +1 -0
- package/dist/engines/dast.js +167 -0
- package/dist/engines/dast.js.map +1 -0
- package/dist/engines/db-rules.d.ts +3 -0
- package/dist/engines/db-rules.d.ts.map +1 -0
- package/dist/engines/db-rules.js +166 -0
- package/dist/engines/db-rules.js.map +1 -0
- package/dist/engines/dependencies.d.ts +3 -0
- package/dist/engines/dependencies.d.ts.map +1 -0
- package/dist/engines/dependencies.js +167 -0
- package/dist/engines/dependencies.js.map +1 -0
- package/dist/engines/github-actions.d.ts +3 -0
- package/dist/engines/github-actions.d.ts.map +1 -0
- package/dist/engines/github-actions.js +215 -0
- package/dist/engines/github-actions.js.map +1 -0
- package/dist/engines/gitleaks.d.ts +3 -0
- package/dist/engines/gitleaks.d.ts.map +1 -0
- package/dist/engines/gitleaks.js +107 -0
- package/dist/engines/gitleaks.js.map +1 -0
- package/dist/engines/npm-audit.d.ts +3 -0
- package/dist/engines/npm-audit.d.ts.map +1 -0
- package/dist/engines/npm-audit.js +113 -0
- package/dist/engines/npm-audit.js.map +1 -0
- package/dist/engines/patterns.d.ts +3 -0
- package/dist/engines/patterns.d.ts.map +1 -0
- package/dist/engines/patterns.js +1330 -0
- package/dist/engines/patterns.js.map +1 -0
- package/dist/engines/secrets.d.ts +4 -0
- package/dist/engines/secrets.d.ts.map +1 -0
- package/dist/engines/secrets.js +260 -0
- package/dist/engines/secrets.js.map +1 -0
- package/dist/engines/semgrep.d.ts +3 -0
- package/dist/engines/semgrep.d.ts.map +1 -0
- package/dist/engines/semgrep.js +173 -0
- package/dist/engines/semgrep.js.map +1 -0
- package/dist/engines/types.d.ts +30 -0
- package/dist/engines/types.d.ts.map +1 -0
- package/dist/engines/types.js +3 -0
- package/dist/engines/types.js.map +1 -0
- package/dist/engines/url-scanner.d.ts +3 -0
- package/dist/engines/url-scanner.d.ts.map +1 -0
- package/dist/engines/url-scanner.js +469 -0
- package/dist/engines/url-scanner.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +32 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +237 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/rules/.gitkeep +0 -0
- package/dist/rules/ai-llm-security.yaml +199 -0
- package/dist/rules/api-security.yaml +489 -0
- package/dist/rules/auth-patterns.yaml +406 -0
- package/dist/rules/crypto-patterns.yaml +227 -0
- package/dist/rules/database-cloud-security.yaml +169 -0
- package/dist/rules/general-security.yaml +588 -0
- package/dist/rules/injection-patterns.yaml +318 -0
- package/dist/rules/nextjs-security.yaml +532 -0
- package/dist/rules/node-security.yaml +380 -0
- package/dist/rules/rules/.gitkeep +0 -0
- package/dist/rules/rules/ai-llm-security.yaml +199 -0
- package/dist/rules/rules/api-security.yaml +489 -0
- package/dist/rules/rules/auth-patterns.yaml +406 -0
- package/dist/rules/rules/crypto-patterns.yaml +227 -0
- package/dist/rules/rules/database-cloud-security.yaml +169 -0
- package/dist/rules/rules/general-security.yaml +588 -0
- package/dist/rules/rules/injection-patterns.yaml +318 -0
- package/dist/rules/rules/nextjs-security.yaml +532 -0
- package/dist/rules/rules/node-security.yaml +380 -0
- package/dist/rules/rules/supabase-security.yaml +387 -0
- package/dist/rules/supabase-security.yaml +387 -0
- package/dist/scanning/agents/csharp-agent.d.ts +12 -0
- package/dist/scanning/agents/csharp-agent.d.ts.map +1 -0
- package/dist/scanning/agents/csharp-agent.js +592 -0
- package/dist/scanning/agents/csharp-agent.js.map +1 -0
- package/dist/scanning/agents/go-agent.d.ts +14 -0
- package/dist/scanning/agents/go-agent.d.ts.map +1 -0
- package/dist/scanning/agents/go-agent.js +641 -0
- package/dist/scanning/agents/go-agent.js.map +1 -0
- package/dist/scanning/agents/java-agent.d.ts +8 -0
- package/dist/scanning/agents/java-agent.d.ts.map +1 -0
- package/dist/scanning/agents/java-agent.js +807 -0
- package/dist/scanning/agents/java-agent.js.map +1 -0
- package/dist/scanning/agents/javascript-agent.d.ts +8 -0
- package/dist/scanning/agents/javascript-agent.d.ts.map +1 -0
- package/dist/scanning/agents/javascript-agent.js +77 -0
- package/dist/scanning/agents/javascript-agent.js.map +1 -0
- package/dist/scanning/agents/mobile-agent.d.ts +8 -0
- package/dist/scanning/agents/mobile-agent.d.ts.map +1 -0
- package/dist/scanning/agents/mobile-agent.js +916 -0
- package/dist/scanning/agents/mobile-agent.js.map +1 -0
- package/dist/scanning/agents/php-agent.d.ts +8 -0
- package/dist/scanning/agents/php-agent.d.ts.map +1 -0
- package/dist/scanning/agents/php-agent.js +679 -0
- package/dist/scanning/agents/php-agent.js.map +1 -0
- package/dist/scanning/agents/python-agent.d.ts +8 -0
- package/dist/scanning/agents/python-agent.d.ts.map +1 -0
- package/dist/scanning/agents/python-agent.js +701 -0
- package/dist/scanning/agents/python-agent.js.map +1 -0
- package/dist/scanning/agents/supabase-agent.d.ts +8 -0
- package/dist/scanning/agents/supabase-agent.d.ts.map +1 -0
- package/dist/scanning/agents/supabase-agent.js +484 -0
- package/dist/scanning/agents/supabase-agent.js.map +1 -0
- package/dist/scanning/orchestrator.d.ts +29 -0
- package/dist/scanning/orchestrator.d.ts.map +1 -0
- package/dist/scanning/orchestrator.js +186 -0
- package/dist/scanning/orchestrator.js.map +1 -0
- package/dist/scanning/tech-detector.d.ts +12 -0
- package/dist/scanning/tech-detector.d.ts.map +1 -0
- package/dist/scanning/tech-detector.js +252 -0
- package/dist/scanning/tech-detector.js.map +1 -0
- package/dist/scanning/types.d.ts +87 -0
- package/dist/scanning/types.d.ts.map +1 -0
- package/dist/scanning/types.js +5 -0
- package/dist/scanning/types.js.map +1 -0
- package/dist/utils/child-process.d.ts +2 -0
- package/dist/utils/child-process.d.ts.map +1 -0
- package/dist/utils/child-process.js +4 -0
- package/dist/utils/child-process.js.map +1 -0
- package/dist/utils/context-classifier.d.ts +11 -0
- package/dist/utils/context-classifier.d.ts.map +1 -0
- package/dist/utils/context-classifier.js +79 -0
- package/dist/utils/context-classifier.js.map +1 -0
- package/dist/utils/exec.d.ts +18 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +51 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/file-filter.d.ts +4 -0
- package/dist/utils/file-filter.d.ts.map +1 -0
- package/dist/utils/file-filter.js +68 -0
- package/dist/utils/file-filter.js.map +1 -0
- package/dist/utils/fs.d.ts +2 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +5 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/post-processor.d.ts +10 -0
- package/dist/utils/post-processor.d.ts.map +1 -0
- package/dist/utils/post-processor.js +183 -0
- package/dist/utils/post-processor.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
// Go ecosystem security scanner agent.
|
|
2
|
+
// Detects Go projects (go.mod) and scans for common security vulnerabilities:
|
|
3
|
+
// SQL injection, command injection, hardcoded credentials, weak crypto,
|
|
4
|
+
// template HTML bypass, path traversal, CORS misconfig, and more.
|
|
5
|
+
export class GoScanAgent {
|
|
6
|
+
async detect(files) {
|
|
7
|
+
for (const filePath of files.keys()) {
|
|
8
|
+
if (filePath === "go.mod" || filePath.endsWith("/go.mod")) {
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
async scan(files) {
|
|
15
|
+
const results = [];
|
|
16
|
+
for (const [filePath, content] of files) {
|
|
17
|
+
if (!isGoFile(filePath))
|
|
18
|
+
continue;
|
|
19
|
+
results.push(...checkSqlInjection(filePath, content));
|
|
20
|
+
results.push(...checkErrorInResponse(filePath, content));
|
|
21
|
+
results.push(...checkHttpWithoutTls(filePath, content));
|
|
22
|
+
results.push(...checkCommandInjection(filePath, content));
|
|
23
|
+
results.push(...checkTemplateHtmlBypass(filePath, content));
|
|
24
|
+
results.push(...checkHardcodedCredentials(filePath, content));
|
|
25
|
+
results.push(...checkWeakHashAlgorithm(filePath, content));
|
|
26
|
+
results.push(...checkHttpHandlerWithoutAuth(filePath, content));
|
|
27
|
+
results.push(...checkCorsWildcard(filePath, content));
|
|
28
|
+
results.push(...checkPathTraversal(filePath, content));
|
|
29
|
+
results.push(...checkUnsafeYamlDeserialization(filePath, content));
|
|
30
|
+
results.push(...checkHttpClientWithoutTimeout(filePath, content));
|
|
31
|
+
results.push(...checkInsecureRandom(filePath, content));
|
|
32
|
+
results.push(...checkSensitiveDataLogged(filePath, content));
|
|
33
|
+
}
|
|
34
|
+
return results;
|
|
35
|
+
}
|
|
36
|
+
getMetadata() {
|
|
37
|
+
return {
|
|
38
|
+
name: "go-agent",
|
|
39
|
+
version: "1.0.0",
|
|
40
|
+
technologies: ["go"],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
getChecks() {
|
|
44
|
+
return [
|
|
45
|
+
{ id: "go:sql-injection", name: "SQL injection via fmt.Sprintf in query", severity: "CRITICAL" },
|
|
46
|
+
{ id: "go:error-in-response", name: "Error details exposed in HTTP response", severity: "MEDIUM" },
|
|
47
|
+
{ id: "go:http-without-tls", name: "HTTP server without TLS", severity: "MEDIUM" },
|
|
48
|
+
{ id: "go:command-injection", name: "Potential command injection via exec.Command", severity: "CRITICAL" },
|
|
49
|
+
{ id: "go:template-html-bypass", name: "Template HTML bypass via template.HTML()", severity: "HIGH" },
|
|
50
|
+
{ id: "go:hardcoded-credentials", name: "Hardcoded credential in source code", severity: "CRITICAL" },
|
|
51
|
+
{ id: "go:weak-hash-md5", name: "Weak hash algorithm: MD5", severity: "HIGH" },
|
|
52
|
+
{ id: "go:weak-hash-sha1", name: "Weak hash algorithm: SHA-1", severity: "HIGH" },
|
|
53
|
+
{ id: "go:handler-without-auth", name: "HTTP handler registered without authentication middleware", severity: "MEDIUM" },
|
|
54
|
+
{ id: "go:cors-wildcard", name: "CORS wildcard: Access-Control-Allow-Origin set to *", severity: "MEDIUM" },
|
|
55
|
+
{ id: "go:path-traversal", name: "Potential path traversal in file operation", severity: "HIGH" },
|
|
56
|
+
{ id: "go:unsafe-yaml", name: "Unsafe YAML deserialization into interface{}", severity: "HIGH" },
|
|
57
|
+
{ id: "go:http-client-no-timeout", name: "HTTP client created without timeout", severity: "MEDIUM" },
|
|
58
|
+
{ id: "go:http-default-client", name: "http.Get/http.Post uses default client with no timeout", severity: "MEDIUM" },
|
|
59
|
+
{ id: "go:insecure-random", name: "math/rand used for security-sensitive value", severity: "HIGH" },
|
|
60
|
+
{ id: "go:sensitive-data-logged", name: "Sensitive data in log output", severity: "MEDIUM" },
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// --- Helper ---
|
|
65
|
+
/** Returns true if the file path ends with .go. */
|
|
66
|
+
export function isGoFile(path) {
|
|
67
|
+
return path.endsWith(".go");
|
|
68
|
+
}
|
|
69
|
+
// --- Check 1: SQL injection via fmt.Sprintf in queries (CWE-89) ---
|
|
70
|
+
//
|
|
71
|
+
// Confidence rules:
|
|
72
|
+
// high — %s or %v in the format string (injectable string value)
|
|
73
|
+
// low — %d only (type-safe integer, cannot break SQL syntax)
|
|
74
|
+
function checkSqlInjection(filePath, content) {
|
|
75
|
+
const results = [];
|
|
76
|
+
const lines = content.split("\n");
|
|
77
|
+
for (let i = 0; i < lines.length; i++) {
|
|
78
|
+
const line = lines[i];
|
|
79
|
+
if (line.includes("db.Query(fmt.Sprintf(") ||
|
|
80
|
+
line.includes("db.Exec(fmt.Sprintf(") ||
|
|
81
|
+
line.includes("db.QueryRow(fmt.Sprintf(")) {
|
|
82
|
+
// %s or %v allows injecting arbitrary strings; %d is type-safe (integer only).
|
|
83
|
+
const hasInjectableVerb = /%[^d\s"']/.test(line) || line.includes("%s") || line.includes("%v");
|
|
84
|
+
const confidence = hasInjectableVerb ? "high" : "low";
|
|
85
|
+
results.push({
|
|
86
|
+
checkId: "go:sql-injection",
|
|
87
|
+
title: "SQL injection via fmt.Sprintf in query",
|
|
88
|
+
severity: "CRITICAL",
|
|
89
|
+
file: filePath,
|
|
90
|
+
line: i + 1,
|
|
91
|
+
description: "Using fmt.Sprintf() to build SQL queries allows injection attacks. User-controlled input interpolated into the query string can break out and execute arbitrary SQL.",
|
|
92
|
+
fix: 'Use parameterized queries: db.Query("SELECT * FROM users WHERE id = $1", id)',
|
|
93
|
+
cwe: "CWE-89",
|
|
94
|
+
confidence,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
100
|
+
// --- Check 2: Error details in HTTP response (CWE-209) ---
|
|
101
|
+
function checkErrorInResponse(filePath, content) {
|
|
102
|
+
const results = [];
|
|
103
|
+
const lines = content.split("\n");
|
|
104
|
+
for (let i = 0; i < lines.length; i++) {
|
|
105
|
+
const line = lines[i];
|
|
106
|
+
// fmt.Fprintf(w, ..., err) or http.Error(w, err.Error(), ...)
|
|
107
|
+
const hasFprintfWithErr = line.includes("fmt.Fprintf(w,") && line.includes("err");
|
|
108
|
+
const hasHttpErrorWithErrMsg = line.includes("http.Error(w, err.Error()");
|
|
109
|
+
if (hasFprintfWithErr || hasHttpErrorWithErrMsg) {
|
|
110
|
+
results.push({
|
|
111
|
+
checkId: "go:error-in-response",
|
|
112
|
+
title: "Error details exposed in HTTP response",
|
|
113
|
+
severity: "MEDIUM",
|
|
114
|
+
file: filePath,
|
|
115
|
+
line: i + 1,
|
|
116
|
+
description: "Internal error details are being sent to the client. This can leak implementation details, file paths, or database schema information that aids attackers.",
|
|
117
|
+
fix: "Return generic error messages to clients. Log detailed errors server-side.",
|
|
118
|
+
cwe: "CWE-209",
|
|
119
|
+
confidence: "medium",
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return results;
|
|
124
|
+
}
|
|
125
|
+
// --- Check 3: HTTP without TLS (CWE-319) ---
|
|
126
|
+
function checkHttpWithoutTls(filePath, content) {
|
|
127
|
+
const results = [];
|
|
128
|
+
const lines = content.split("\n");
|
|
129
|
+
for (let i = 0; i < lines.length; i++) {
|
|
130
|
+
const line = lines[i];
|
|
131
|
+
// Match http.ListenAndServe( but NOT http.ListenAndServeTLS(
|
|
132
|
+
if (line.includes("http.ListenAndServe(") &&
|
|
133
|
+
!line.includes("http.ListenAndServeTLS(")) {
|
|
134
|
+
results.push({
|
|
135
|
+
checkId: "go:http-without-tls",
|
|
136
|
+
title: "HTTP server without TLS",
|
|
137
|
+
severity: "MEDIUM",
|
|
138
|
+
file: filePath,
|
|
139
|
+
line: i + 1,
|
|
140
|
+
description: "The server is configured to listen over plain HTTP. Data transmitted between the client and server is unencrypted and vulnerable to interception.",
|
|
141
|
+
fix: "Use http.ListenAndServeTLS() or a reverse proxy with TLS termination.",
|
|
142
|
+
cwe: "CWE-319",
|
|
143
|
+
confidence: "medium",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
// --- Check 4: Command injection (CWE-78) ---
|
|
150
|
+
//
|
|
151
|
+
// Go's exec.Command separates the executable from its arguments, so passing a
|
|
152
|
+
// plain variable as an argument is NOT inherently dangerous — the OS never
|
|
153
|
+
// invokes a shell. We only flag genuinely dangerous patterns:
|
|
154
|
+
//
|
|
155
|
+
// high — string concatenation (`+`) used inside the exec.Command call, OR
|
|
156
|
+
// a known request-input source (r.FormValue, r.URL, chi.URLParam,
|
|
157
|
+
// mux.Vars) appears on the SAME line as exec.Command
|
|
158
|
+
// medium — fmt.Sprintf used to build the command string
|
|
159
|
+
//
|
|
160
|
+
// Simple variable refs with no concat and no same-line request input are
|
|
161
|
+
// intentionally NOT flagged (e.g. exec.Command("git", tag)).
|
|
162
|
+
// Patterns that mean user data is on this exact line.
|
|
163
|
+
const CMD_SAME_LINE_INPUT = /r\.FormValue\s*\(|r\.URL\s*\.|chi\.URLParam\s*\(|mux\.Vars\s*\(/;
|
|
164
|
+
function checkCommandInjection(filePath, content) {
|
|
165
|
+
const results = [];
|
|
166
|
+
const lines = content.split("\n");
|
|
167
|
+
for (let i = 0; i < lines.length; i++) {
|
|
168
|
+
const line = lines[i];
|
|
169
|
+
if (!line.includes("exec.Command("))
|
|
170
|
+
continue;
|
|
171
|
+
// Only string literals — completely safe.
|
|
172
|
+
const afterCommand = line.slice(line.indexOf("exec.Command(") + "exec.Command(".length);
|
|
173
|
+
const isOnlyStringLiterals = /^"[^"]*"(?:\s*,\s*"[^"]*")*\s*\)/.test(afterCommand.trim());
|
|
174
|
+
if (isOnlyStringLiterals)
|
|
175
|
+
continue;
|
|
176
|
+
// fmt.Sprintf used to build the command or args — medium confidence.
|
|
177
|
+
if (line.includes("fmt.Sprintf(")) {
|
|
178
|
+
results.push({
|
|
179
|
+
checkId: "go:command-injection",
|
|
180
|
+
title: "Potential command injection via exec.Command",
|
|
181
|
+
severity: "CRITICAL",
|
|
182
|
+
file: filePath,
|
|
183
|
+
line: i + 1,
|
|
184
|
+
description: "exec.Command() receives a string built with fmt.Sprintf(). If user-controlled input reaches the format arguments, an attacker can influence the command executed.",
|
|
185
|
+
fix: "Never build command strings with fmt.Sprintf. Pass arguments as separate exec.Command parameters and validate them with an allowlist.",
|
|
186
|
+
cwe: "CWE-78",
|
|
187
|
+
confidence: "medium",
|
|
188
|
+
});
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// String concatenation (`+`) inside the exec.Command call arguments.
|
|
192
|
+
if (afterCommand.includes("+")) {
|
|
193
|
+
results.push({
|
|
194
|
+
checkId: "go:command-injection",
|
|
195
|
+
title: "Potential command injection via exec.Command",
|
|
196
|
+
severity: "CRITICAL",
|
|
197
|
+
file: filePath,
|
|
198
|
+
line: i + 1,
|
|
199
|
+
description: "exec.Command() is called with string concatenation in its arguments. Concatenating user-controlled input here allows an attacker to inject additional shell arguments or commands.",
|
|
200
|
+
fix: "Pass all arguments as separate exec.Command parameters. Validate each argument against an allowlist.",
|
|
201
|
+
cwe: "CWE-78",
|
|
202
|
+
confidence: "high",
|
|
203
|
+
});
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
// A known request-input source appears on the same line as exec.Command.
|
|
207
|
+
if (CMD_SAME_LINE_INPUT.test(line)) {
|
|
208
|
+
results.push({
|
|
209
|
+
checkId: "go:command-injection",
|
|
210
|
+
title: "Potential command injection via exec.Command",
|
|
211
|
+
severity: "CRITICAL",
|
|
212
|
+
file: filePath,
|
|
213
|
+
line: i + 1,
|
|
214
|
+
description: "exec.Command() is called with a value read directly from the HTTP request on the same line. If unsanitized, an attacker can supply arbitrary command arguments.",
|
|
215
|
+
fix: "Validate and sanitize all request-derived input before passing to exec.Command. Use allowlists.",
|
|
216
|
+
cwe: "CWE-78",
|
|
217
|
+
confidence: "high",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
// Simple variable reference with no concat and no same-line request input:
|
|
221
|
+
// e.g. exec.Command("git", tag) — NOT flagged. This is the safe Go idiom.
|
|
222
|
+
}
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
225
|
+
// --- Check 5: Template HTML bypass (CWE-79) ---
|
|
226
|
+
//
|
|
227
|
+
// template.HTML() marks a string as safe, bypassing auto-escaping. If the
|
|
228
|
+
// argument is already sanitized via a known library we skip the finding.
|
|
229
|
+
//
|
|
230
|
+
// medium — template.HTML() with an unsanitized argument
|
|
231
|
+
// (skipped entirely if sanitize/bluemonday/policy.Sanitize/html.EscapeString
|
|
232
|
+
// appears in the same argument expression)
|
|
233
|
+
// Patterns that indicate the value has already been sanitized.
|
|
234
|
+
// Case-insensitive so both `sanitize` and `Sanitize` match.
|
|
235
|
+
const TEMPLATE_HTML_SAFE_PATTERNS = /sanitize|bluemonday|policy\.sanitize|html\.escapestring/i;
|
|
236
|
+
// How many lines before/after the template.HTML( call to scan for sanitizer evidence.
|
|
237
|
+
const TEMPLATE_HTML_CONTEXT_WINDOW = 3;
|
|
238
|
+
function checkTemplateHtmlBypass(filePath, content) {
|
|
239
|
+
const results = [];
|
|
240
|
+
const lines = content.split("\n");
|
|
241
|
+
for (let i = 0; i < lines.length; i++) {
|
|
242
|
+
const line = lines[i];
|
|
243
|
+
if (!line.includes("template.HTML("))
|
|
244
|
+
continue;
|
|
245
|
+
// Check the call line AND a small surrounding context for sanitizer evidence.
|
|
246
|
+
// This covers cases where a sanitized value is assigned on the previous line
|
|
247
|
+
// or where bluemonday is imported and clearly used in the same block.
|
|
248
|
+
const windowStart = Math.max(0, i - TEMPLATE_HTML_CONTEXT_WINDOW);
|
|
249
|
+
const windowEnd = Math.min(lines.length - 1, i + TEMPLATE_HTML_CONTEXT_WINDOW);
|
|
250
|
+
const contextChunk = lines.slice(windowStart, windowEnd + 1).join("\n");
|
|
251
|
+
if (TEMPLATE_HTML_SAFE_PATTERNS.test(contextChunk))
|
|
252
|
+
continue;
|
|
253
|
+
results.push({
|
|
254
|
+
checkId: "go:template-html-bypass",
|
|
255
|
+
title: "Template HTML bypass via template.HTML()",
|
|
256
|
+
severity: "HIGH",
|
|
257
|
+
file: filePath,
|
|
258
|
+
line: i + 1,
|
|
259
|
+
description: "template.HTML() marks a string as safe HTML, bypassing Go's html/template auto-escaping. If user-controlled input is wrapped in template.HTML(), it can lead to XSS attacks.",
|
|
260
|
+
fix: "Avoid template.HTML(). Use text/template escaping or sanitize input with bluemonday.",
|
|
261
|
+
cwe: "CWE-79",
|
|
262
|
+
confidence: "medium",
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
return results;
|
|
266
|
+
}
|
|
267
|
+
// --- Check 6: Hardcoded credentials (CWE-798) ---
|
|
268
|
+
// Matches: password = "...", secret: "...", token = "...", apiKey = "...", api_key = "..."
|
|
269
|
+
const HARDCODED_CRED_PATTERN = /(?:password|secret|token|apikey|api_key)\s*[:=]\s*"[^"]{8,}"/i;
|
|
270
|
+
// Skip lines that use environment or config sources.
|
|
271
|
+
const CRED_SAFE_SOURCES = /os\.Getenv|viper\.Get|flag\.String/;
|
|
272
|
+
function checkHardcodedCredentials(filePath, content) {
|
|
273
|
+
const results = [];
|
|
274
|
+
const lines = content.split("\n");
|
|
275
|
+
for (let i = 0; i < lines.length; i++) {
|
|
276
|
+
const line = lines[i];
|
|
277
|
+
const trimmed = line.trim();
|
|
278
|
+
// Skip comment lines
|
|
279
|
+
if (trimmed.startsWith("//"))
|
|
280
|
+
continue;
|
|
281
|
+
// Skip lines pulling from env/config
|
|
282
|
+
if (CRED_SAFE_SOURCES.test(line))
|
|
283
|
+
continue;
|
|
284
|
+
if (HARDCODED_CRED_PATTERN.test(line)) {
|
|
285
|
+
results.push({
|
|
286
|
+
checkId: "go:hardcoded-credentials",
|
|
287
|
+
title: "Hardcoded credential in source code",
|
|
288
|
+
severity: "CRITICAL",
|
|
289
|
+
file: filePath,
|
|
290
|
+
line: i + 1,
|
|
291
|
+
description: "A credential (password, secret, token, or API key) appears to be hardcoded in source code. Anyone with repository access can read this value.",
|
|
292
|
+
fix: "Use environment variables or a secrets manager instead of hardcoded credentials.",
|
|
293
|
+
cwe: "CWE-798",
|
|
294
|
+
confidence: "high",
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
// --- Check 7: Weak hash algorithm (CWE-328) ---
|
|
301
|
+
function checkWeakHashAlgorithm(filePath, content) {
|
|
302
|
+
const results = [];
|
|
303
|
+
const lines = content.split("\n");
|
|
304
|
+
for (let i = 0; i < lines.length; i++) {
|
|
305
|
+
const line = lines[i];
|
|
306
|
+
const usesMd5 = line.includes("crypto/md5") ||
|
|
307
|
+
line.includes("md5.New()") ||
|
|
308
|
+
line.includes("md5.Sum(");
|
|
309
|
+
const usesSha1 = line.includes("crypto/sha1") ||
|
|
310
|
+
line.includes("sha1.New()") ||
|
|
311
|
+
line.includes("sha1.Sum(");
|
|
312
|
+
if (usesMd5) {
|
|
313
|
+
results.push({
|
|
314
|
+
checkId: "go:weak-hash-md5",
|
|
315
|
+
title: "Weak hash algorithm: MD5",
|
|
316
|
+
severity: "HIGH",
|
|
317
|
+
file: filePath,
|
|
318
|
+
line: i + 1,
|
|
319
|
+
description: "MD5 is cryptographically broken and should not be used for security-sensitive operations. Collision attacks are practical and preimage resistance is weakened.",
|
|
320
|
+
fix: "Use crypto/sha256 or crypto/sha512 for hashing. For passwords, use bcrypt or argon2.",
|
|
321
|
+
cwe: "CWE-328",
|
|
322
|
+
confidence: "high",
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (usesSha1) {
|
|
326
|
+
results.push({
|
|
327
|
+
checkId: "go:weak-hash-sha1",
|
|
328
|
+
title: "Weak hash algorithm: SHA-1",
|
|
329
|
+
severity: "HIGH",
|
|
330
|
+
file: filePath,
|
|
331
|
+
line: i + 1,
|
|
332
|
+
description: "SHA-1 is cryptographically deprecated and should not be used for security-sensitive operations. Collision attacks have been demonstrated in practice.",
|
|
333
|
+
fix: "Use crypto/sha256 or crypto/sha512 for hashing. For passwords, use bcrypt or argon2.",
|
|
334
|
+
cwe: "CWE-328",
|
|
335
|
+
confidence: "high",
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return results;
|
|
340
|
+
}
|
|
341
|
+
// --- Check 8: HTTP handler without auth middleware (CWE-306) ---
|
|
342
|
+
//
|
|
343
|
+
// Instead of skipping the entire file when ANY auth-like keyword appears, we
|
|
344
|
+
// now check whether the SPECIFIC handler path has middleware wrapping it.
|
|
345
|
+
// A handler is considered protected if the line itself — or the 3 lines
|
|
346
|
+
// immediately following it — contain an auth-middleware reference wrapping
|
|
347
|
+
// that same path.
|
|
348
|
+
//
|
|
349
|
+
// low — absence-based finding: the handler appears unprotected
|
|
350
|
+
// Paths that are public by convention and do not need auth checks.
|
|
351
|
+
const PUBLIC_PATH_PATTERN = /["']\/(?:health|public|static|favicon|webhook|callback|ws|metrics|status)[/"']/;
|
|
352
|
+
// Identifiers that strongly suggest a handler is wrapped in auth middleware
|
|
353
|
+
// on the SAME or immediately adjacent lines.
|
|
354
|
+
const AUTH_INLINE_PATTERN = /\b(?:middleware|auth|jwt|session|authenticate|authorize)\b/i;
|
|
355
|
+
function checkHttpHandlerWithoutAuth(filePath, content) {
|
|
356
|
+
const results = [];
|
|
357
|
+
const lines = content.split("\n");
|
|
358
|
+
for (let i = 0; i < lines.length; i++) {
|
|
359
|
+
const line = lines[i];
|
|
360
|
+
if (!line.includes('http.HandleFunc("'))
|
|
361
|
+
continue;
|
|
362
|
+
// Public paths don't need auth.
|
|
363
|
+
if (PUBLIC_PATH_PATTERN.test(line))
|
|
364
|
+
continue;
|
|
365
|
+
// Check whether this specific handler line or the next 3 lines reference
|
|
366
|
+
// an auth middleware (e.g. a chained call or a comment-documented wrapper).
|
|
367
|
+
const lookAheadEnd = Math.min(lines.length - 1, i + 3);
|
|
368
|
+
const localContext = lines.slice(i, lookAheadEnd + 1).join("\n");
|
|
369
|
+
if (AUTH_INLINE_PATTERN.test(localContext))
|
|
370
|
+
continue;
|
|
371
|
+
results.push({
|
|
372
|
+
checkId: "go:handler-without-auth",
|
|
373
|
+
title: "HTTP handler registered without authentication middleware",
|
|
374
|
+
severity: "MEDIUM",
|
|
375
|
+
file: filePath,
|
|
376
|
+
line: i + 1,
|
|
377
|
+
description: "This HTTP handler is registered without any visible authentication middleware in the surrounding code. The endpoint may be accessible to unauthenticated users.",
|
|
378
|
+
fix: "Add authentication middleware to protect this endpoint.",
|
|
379
|
+
cwe: "CWE-306",
|
|
380
|
+
confidence: "low",
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return results;
|
|
384
|
+
}
|
|
385
|
+
// --- Check 9: CORS wildcard (CWE-942) ---
|
|
386
|
+
function checkCorsWildcard(filePath, content) {
|
|
387
|
+
const results = [];
|
|
388
|
+
const lines = content.split("\n");
|
|
389
|
+
for (let i = 0; i < lines.length; i++) {
|
|
390
|
+
const line = lines[i];
|
|
391
|
+
// "Access-Control-Allow-Origin" with "*" on the same line, or
|
|
392
|
+
// the wildcard value on a line immediately following the header name.
|
|
393
|
+
if (line.includes("Access-Control-Allow-Origin") && line.includes('"*"')) {
|
|
394
|
+
results.push({
|
|
395
|
+
checkId: "go:cors-wildcard",
|
|
396
|
+
title: "CORS wildcard: Access-Control-Allow-Origin set to *",
|
|
397
|
+
severity: "MEDIUM",
|
|
398
|
+
file: filePath,
|
|
399
|
+
line: i + 1,
|
|
400
|
+
description: 'The Access-Control-Allow-Origin header is set to "*", which allows any origin to make cross-origin requests to this server. This can lead to data leakage.',
|
|
401
|
+
fix: "Restrict CORS to specific origins instead of using wildcard.",
|
|
402
|
+
cwe: "CWE-942",
|
|
403
|
+
confidence: "high",
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
else if (line.includes("Access-Control-Allow-Origin")) {
|
|
407
|
+
// Check the next line as well (multiline header assignment).
|
|
408
|
+
const nextLine = lines[i + 1] ?? "";
|
|
409
|
+
if (nextLine.includes('"*"')) {
|
|
410
|
+
results.push({
|
|
411
|
+
checkId: "go:cors-wildcard",
|
|
412
|
+
title: "CORS wildcard: Access-Control-Allow-Origin set to *",
|
|
413
|
+
severity: "MEDIUM",
|
|
414
|
+
file: filePath,
|
|
415
|
+
line: i + 1,
|
|
416
|
+
description: 'The Access-Control-Allow-Origin header is set to "*", which allows any origin to make cross-origin requests to this server. This can lead to data leakage.',
|
|
417
|
+
fix: "Restrict CORS to specific origins instead of using wildcard.",
|
|
418
|
+
cwe: "CWE-942",
|
|
419
|
+
confidence: "high",
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return results;
|
|
425
|
+
}
|
|
426
|
+
// --- Check 10: Path traversal (CWE-22) ---
|
|
427
|
+
//
|
|
428
|
+
// Only flag when BOTH a file operation AND a user-input source appear on the
|
|
429
|
+
// SAME line (or are directly concatenated). A 5-line context window produced
|
|
430
|
+
// too many false positives when the input variable was assigned far above.
|
|
431
|
+
//
|
|
432
|
+
// Additionally, if filepath.Clean or filepath.Abs appears on the same line,
|
|
433
|
+
// the path has been sanitized — don't flag it.
|
|
434
|
+
//
|
|
435
|
+
// high — direct string concatenation (`+`) between input and file op
|
|
436
|
+
// medium — variable reference on the same line (no concat visible)
|
|
437
|
+
// User-controlled value sources that commonly appear in path traversal.
|
|
438
|
+
const PATH_TRAVERSAL_INPUT_PATTERN = /\b(?:r\.FormValue|r\.URL\.Query|r\.URL\b|mux\.Vars|chi\.URLParam)\b/;
|
|
439
|
+
// Safe path-cleaning functions.
|
|
440
|
+
const PATH_SAFE_PATTERN = /filepath\.Clean|filepath\.Abs/;
|
|
441
|
+
function checkPathTraversal(filePath, content) {
|
|
442
|
+
const results = [];
|
|
443
|
+
const lines = content.split("\n");
|
|
444
|
+
for (let i = 0; i < lines.length; i++) {
|
|
445
|
+
const line = lines[i];
|
|
446
|
+
const opensFile = line.includes("os.Open(") ||
|
|
447
|
+
line.includes("os.ReadFile(") ||
|
|
448
|
+
line.includes("ioutil.ReadFile(");
|
|
449
|
+
if (!opensFile)
|
|
450
|
+
continue;
|
|
451
|
+
// Must also have a request-input source on the same line.
|
|
452
|
+
if (!PATH_TRAVERSAL_INPUT_PATTERN.test(line))
|
|
453
|
+
continue;
|
|
454
|
+
// If a path-cleaning call is present on the same line, it is sanitized.
|
|
455
|
+
if (PATH_SAFE_PATTERN.test(line))
|
|
456
|
+
continue;
|
|
457
|
+
// Determine confidence: direct concat vs. variable reference.
|
|
458
|
+
const confidence = line.includes("+") ? "high" : "medium";
|
|
459
|
+
results.push({
|
|
460
|
+
checkId: "go:path-traversal",
|
|
461
|
+
title: "Potential path traversal in file operation",
|
|
462
|
+
severity: "HIGH",
|
|
463
|
+
file: filePath,
|
|
464
|
+
line: i + 1,
|
|
465
|
+
description: "A file operation uses a path derived from request input without visible sanitization. An attacker could supply a path like ../../etc/passwd to read arbitrary files.",
|
|
466
|
+
fix: "Validate file paths against a whitelist. Use filepath.Clean() and check the path doesn't escape the intended directory.",
|
|
467
|
+
cwe: "CWE-22",
|
|
468
|
+
confidence,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
return results;
|
|
472
|
+
}
|
|
473
|
+
// --- Check 11: Unsafe YAML deserialization (CWE-502) ---
|
|
474
|
+
// Lines before/after yaml.Unmarshal to search for an interface{} target variable.
|
|
475
|
+
const YAML_CONTEXT_WINDOW = 5;
|
|
476
|
+
function checkUnsafeYamlDeserialization(filePath, content) {
|
|
477
|
+
const results = [];
|
|
478
|
+
const lines = content.split("\n");
|
|
479
|
+
for (let i = 0; i < lines.length; i++) {
|
|
480
|
+
const line = lines[i];
|
|
481
|
+
if (!line.includes("yaml.Unmarshal("))
|
|
482
|
+
continue;
|
|
483
|
+
// Check the line itself AND surrounding lines for interface{} target.
|
|
484
|
+
const windowStart = Math.max(0, i - YAML_CONTEXT_WINDOW);
|
|
485
|
+
const windowEnd = Math.min(lines.length - 1, i + YAML_CONTEXT_WINDOW);
|
|
486
|
+
const contextChunk = lines.slice(windowStart, windowEnd + 1).join("\n");
|
|
487
|
+
if (contextChunk.includes("interface{}")) {
|
|
488
|
+
results.push({
|
|
489
|
+
checkId: "go:unsafe-yaml",
|
|
490
|
+
title: "Unsafe YAML deserialization into interface{}",
|
|
491
|
+
severity: "HIGH",
|
|
492
|
+
file: filePath,
|
|
493
|
+
line: i + 1,
|
|
494
|
+
description: "yaml.Unmarshal() is called with an interface{} target. Deserializing into an untyped map can allow unexpected types to be injected and may enable YAML deserialization attacks.",
|
|
495
|
+
fix: "Unmarshal YAML into strictly typed structs instead of interface{} to prevent arbitrary type instantiation.",
|
|
496
|
+
cwe: "CWE-502",
|
|
497
|
+
confidence: "low",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return results;
|
|
502
|
+
}
|
|
503
|
+
// --- Check 12: HTTP client without timeout (CWE-400) ---
|
|
504
|
+
//
|
|
505
|
+
// Single-line regex misses struct literals that span multiple lines.
|
|
506
|
+
// We now scan up to 10 lines from the opening brace to detect Timeout.
|
|
507
|
+
//
|
|
508
|
+
// low — absence-based: no Timeout field found in the client definition block
|
|
509
|
+
// Matches the start of an http.Client struct literal (may be multiline).
|
|
510
|
+
const HTTP_CLIENT_START_PATTERN = /&?http\.Client\s*\{/;
|
|
511
|
+
function checkHttpClientWithoutTimeout(filePath, content) {
|
|
512
|
+
const results = [];
|
|
513
|
+
const lines = content.split("\n");
|
|
514
|
+
for (let i = 0; i < lines.length; i++) {
|
|
515
|
+
const line = lines[i];
|
|
516
|
+
if (HTTP_CLIENT_START_PATTERN.test(line)) {
|
|
517
|
+
// If the struct closes on the same line, check that line only.
|
|
518
|
+
if (line.includes("}")) {
|
|
519
|
+
if (!line.includes("Timeout")) {
|
|
520
|
+
results.push({
|
|
521
|
+
checkId: "go:http-client-no-timeout",
|
|
522
|
+
title: "HTTP client created without timeout",
|
|
523
|
+
severity: "MEDIUM",
|
|
524
|
+
file: filePath,
|
|
525
|
+
line: i + 1,
|
|
526
|
+
description: "An http.Client is created without a Timeout. A client with no timeout can hang indefinitely on slow or unresponsive servers, leading to resource exhaustion.",
|
|
527
|
+
fix: "Always set a Timeout on http.Client: &http.Client{Timeout: 30 * time.Second}",
|
|
528
|
+
cwe: "CWE-400",
|
|
529
|
+
confidence: "low",
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
// Multi-line struct literal: scan up to 10 lines for closing brace and Timeout.
|
|
535
|
+
const blockEnd = Math.min(lines.length - 1, i + 10);
|
|
536
|
+
let foundTimeout = false;
|
|
537
|
+
let closingLine = blockEnd;
|
|
538
|
+
for (let j = i; j <= blockEnd; j++) {
|
|
539
|
+
if (lines[j].includes("Timeout")) {
|
|
540
|
+
foundTimeout = true;
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
if (j > i && lines[j].includes("}")) {
|
|
544
|
+
closingLine = j;
|
|
545
|
+
break;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
if (!foundTimeout) {
|
|
549
|
+
results.push({
|
|
550
|
+
checkId: "go:http-client-no-timeout",
|
|
551
|
+
title: "HTTP client created without timeout",
|
|
552
|
+
severity: "MEDIUM",
|
|
553
|
+
file: filePath,
|
|
554
|
+
line: i + 1,
|
|
555
|
+
description: "An http.Client is created without a Timeout. A client with no timeout can hang indefinitely on slow or unresponsive servers, leading to resource exhaustion.",
|
|
556
|
+
fix: "Always set a Timeout on http.Client: &http.Client{Timeout: 30 * time.Second}",
|
|
557
|
+
cwe: "CWE-400",
|
|
558
|
+
confidence: "low",
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
// Skip ahead to the closing brace to avoid re-scanning the block.
|
|
562
|
+
i = closingLine;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
// Default http.Get / http.Post use the default client which has no timeout.
|
|
566
|
+
if (line.includes("http.Get(") || line.includes("http.Post(")) {
|
|
567
|
+
results.push({
|
|
568
|
+
checkId: "go:http-default-client",
|
|
569
|
+
title: "http.Get/http.Post uses default client with no timeout",
|
|
570
|
+
severity: "MEDIUM",
|
|
571
|
+
file: filePath,
|
|
572
|
+
line: i + 1,
|
|
573
|
+
description: "http.Get() and http.Post() use the default http.Client which has no timeout. Requests can hang indefinitely, leading to resource exhaustion or denial of service.",
|
|
574
|
+
fix: "Always set a Timeout on http.Client: &http.Client{Timeout: 30 * time.Second}",
|
|
575
|
+
cwe: "CWE-400",
|
|
576
|
+
confidence: "low",
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return results;
|
|
581
|
+
}
|
|
582
|
+
// --- Check 13: Insecure randomness (CWE-330) ---
|
|
583
|
+
const MATH_RAND_IMPORT = /["']math\/rand["']/;
|
|
584
|
+
const RAND_CALL = /rand\.(?:Int|Intn|Int31|Int63|Float32|Float64|Read|New)\s*\(/;
|
|
585
|
+
const SECURITY_CONTEXT = /token|secret|key|session|nonce|salt|otp|password|csrf/i;
|
|
586
|
+
function checkInsecureRandom(filePath, content) {
|
|
587
|
+
const results = [];
|
|
588
|
+
const lines = content.split("\n");
|
|
589
|
+
if (!MATH_RAND_IMPORT.test(content))
|
|
590
|
+
return results;
|
|
591
|
+
for (let i = 0; i < lines.length; i++) {
|
|
592
|
+
const line = lines[i];
|
|
593
|
+
if (!RAND_CALL.test(line))
|
|
594
|
+
continue;
|
|
595
|
+
const contextStart = Math.max(0, i - 3);
|
|
596
|
+
const contextEnd = Math.min(lines.length, i + 3);
|
|
597
|
+
const context = lines.slice(contextStart, contextEnd).join("\n");
|
|
598
|
+
if (SECURITY_CONTEXT.test(context)) {
|
|
599
|
+
results.push({
|
|
600
|
+
checkId: "go:insecure-random",
|
|
601
|
+
title: "math/rand used for security-sensitive value",
|
|
602
|
+
severity: "HIGH",
|
|
603
|
+
file: filePath,
|
|
604
|
+
line: i + 1,
|
|
605
|
+
description: "math/rand is not cryptographically secure. For tokens, secrets, or session IDs, use crypto/rand instead.",
|
|
606
|
+
fix: 'import "crypto/rand"; b := make([]byte, 32); rand.Read(b)',
|
|
607
|
+
cwe: "CWE-330",
|
|
608
|
+
confidence: "medium",
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return results;
|
|
613
|
+
}
|
|
614
|
+
// --- Check 14: Sensitive data in logs (CWE-532) ---
|
|
615
|
+
const SENSITIVE_LOG_PATTERN = /(?:password|token|secret|apiKey|api_key|authorization|creditCard|ssn|privateKey)/i;
|
|
616
|
+
function checkSensitiveDataLogged(filePath, content) {
|
|
617
|
+
const results = [];
|
|
618
|
+
const lines = content.split("\n");
|
|
619
|
+
for (let i = 0; i < lines.length; i++) {
|
|
620
|
+
const line = lines[i];
|
|
621
|
+
const isLogCall = /log\.(?:Print|Printf|Println|Fatal|Fatalf)\s*\(/.test(line) ||
|
|
622
|
+
/fmt\.(?:Print|Printf|Println)\s*\(/.test(line);
|
|
623
|
+
if (!isLogCall)
|
|
624
|
+
continue;
|
|
625
|
+
if (SENSITIVE_LOG_PATTERN.test(line)) {
|
|
626
|
+
results.push({
|
|
627
|
+
checkId: "go:sensitive-data-logged",
|
|
628
|
+
title: "Sensitive data in log output",
|
|
629
|
+
severity: "MEDIUM",
|
|
630
|
+
file: filePath,
|
|
631
|
+
line: i + 1,
|
|
632
|
+
description: "A log statement references a variable with a sensitive name (password, token, secret, etc.). Logging sensitive data can expose credentials in log files, monitoring systems, or SIEM tools.",
|
|
633
|
+
fix: "Remove sensitive data from log output. Log identifiers instead of credentials.",
|
|
634
|
+
cwe: "CWE-532",
|
|
635
|
+
confidence: "medium",
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return results;
|
|
640
|
+
}
|
|
641
|
+
//# sourceMappingURL=go-agent.js.map
|