@greenarmor/ges-audit-engine 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 +21 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +87 -0
- package/dist/index.js.map +1 -0
- package/dist/scanners/auth-scanner.d.ts +12 -0
- package/dist/scanners/auth-scanner.js +176 -0
- package/dist/scanners/auth-scanner.js.map +1 -0
- package/dist/scanners/code-security-scanner.d.ts +5 -0
- package/dist/scanners/code-security-scanner.js +91 -0
- package/dist/scanners/code-security-scanner.js.map +1 -0
- package/dist/scanners/config-scanner.d.ts +12 -0
- package/dist/scanners/config-scanner.js +210 -0
- package/dist/scanners/config-scanner.js.map +1 -0
- package/dist/scanners/crypto-scanner.d.ts +5 -0
- package/dist/scanners/crypto-scanner.js +92 -0
- package/dist/scanners/crypto-scanner.js.map +1 -0
- package/dist/scanners/database-scanner.d.ts +7 -0
- package/dist/scanners/database-scanner.js +82 -0
- package/dist/scanners/database-scanner.js.map +1 -0
- package/dist/scanners/secrets-scanner.d.ts +5 -0
- package/dist/scanners/secrets-scanner.js +69 -0
- package/dist/scanners/secrets-scanner.js.map +1 -0
- package/dist/scanners/types.d.ts +22 -0
- package/dist/scanners/types.js +2 -0
- package/dist/scanners/types.js.map +1 -0
- package/package.json +26 -0
- package/src/index.ts +97 -0
- package/src/scanners/auth-scanner.ts +198 -0
- package/src/scanners/code-security-scanner.ts +102 -0
- package/src/scanners/config-scanner.ts +224 -0
- package/src/scanners/crypto-scanner.ts +103 -0
- package/src/scanners/database-scanner.ts +92 -0
- package/src/scanners/secrets-scanner.ts +75 -0
- package/src/scanners/types.ts +24 -0
- package/tsconfig.json +6 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 greenarmor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Finding } from "./scanners/types.js";
|
|
2
|
+
export type { Finding } from "./scanners/types.js";
|
|
3
|
+
export declare function runAudit(root: string): {
|
|
4
|
+
findings: Finding[];
|
|
5
|
+
scannedFiles: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function deduplicateFindings(findings: Finding[]): Finding[];
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { SecretsScanner } from "./scanners/secrets-scanner.js";
|
|
4
|
+
import { CryptoScanner } from "./scanners/crypto-scanner.js";
|
|
5
|
+
import { CodeSecurityScanner } from "./scanners/code-security-scanner.js";
|
|
6
|
+
import { AuthScanner } from "./scanners/auth-scanner.js";
|
|
7
|
+
import { ConfigScanner } from "./scanners/config-scanner.js";
|
|
8
|
+
import { DatabaseScanner } from "./scanners/database-scanner.js";
|
|
9
|
+
const IGNORE_DIRS = new Set([
|
|
10
|
+
"node_modules", ".git", "dist", "build", ".next", ".nuxt", "coverage",
|
|
11
|
+
".ges", "vendor", "__pycache__", ".venv", "venv", ".turbo", ".cache",
|
|
12
|
+
"reports", "compliance", "security", "controls", "policies", "checklists", "docs",
|
|
13
|
+
]);
|
|
14
|
+
const IGNORE_EXTENSIONS = new Set([
|
|
15
|
+
".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".woff",
|
|
16
|
+
".woff2", ".ttf", ".eot", ".mp4", ".mp3", ".zip", ".gz", ".tar",
|
|
17
|
+
".lock", ".map", ".wasm",
|
|
18
|
+
]);
|
|
19
|
+
function collectFiles(root) {
|
|
20
|
+
const files = [];
|
|
21
|
+
function walk(dir) {
|
|
22
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
if (IGNORE_DIRS.has(entry.name))
|
|
25
|
+
continue;
|
|
26
|
+
const fullPath = path.join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
walk(fullPath);
|
|
29
|
+
}
|
|
30
|
+
else if (entry.isFile()) {
|
|
31
|
+
const ext = path.extname(entry.name);
|
|
32
|
+
if (!IGNORE_EXTENSIONS.has(ext)) {
|
|
33
|
+
files.push(path.relative(root, fullPath));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
walk(root);
|
|
39
|
+
return files;
|
|
40
|
+
}
|
|
41
|
+
function readFiles(root, files) {
|
|
42
|
+
const contents = new Map();
|
|
43
|
+
const MAX_FILE_SIZE = 1024 * 1024;
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
try {
|
|
46
|
+
const fullPath = path.join(root, file);
|
|
47
|
+
const stat = fs.statSync(fullPath);
|
|
48
|
+
if (stat.size > MAX_FILE_SIZE)
|
|
49
|
+
continue;
|
|
50
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
51
|
+
contents.set(file, content);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// skip unreadable files
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return contents;
|
|
58
|
+
}
|
|
59
|
+
export function runAudit(root) {
|
|
60
|
+
const files = collectFiles(root);
|
|
61
|
+
const fileContents = readFiles(root, files);
|
|
62
|
+
const ctx = { root, files, fileContents };
|
|
63
|
+
const scanners = [
|
|
64
|
+
new SecretsScanner(),
|
|
65
|
+
new CryptoScanner(),
|
|
66
|
+
new CodeSecurityScanner(),
|
|
67
|
+
new AuthScanner(),
|
|
68
|
+
new ConfigScanner(),
|
|
69
|
+
new DatabaseScanner(),
|
|
70
|
+
];
|
|
71
|
+
const allFindings = [];
|
|
72
|
+
for (const scanner of scanners) {
|
|
73
|
+
allFindings.push(...scanner.scan(ctx));
|
|
74
|
+
}
|
|
75
|
+
return { findings: allFindings, scannedFiles: files.length };
|
|
76
|
+
}
|
|
77
|
+
export function deduplicateFindings(findings) {
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
return findings.filter(f => {
|
|
80
|
+
const key = `${f.ruleId}:${f.file}:${f.line || ""}:${f.evidence}`;
|
|
81
|
+
if (seen.has(key))
|
|
82
|
+
return false;
|
|
83
|
+
seen.add(key);
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Scanner, Finding, ScanContext } from "./types.js";
|
|
2
|
+
export declare class AuthScanner implements Scanner {
|
|
3
|
+
name: string;
|
|
4
|
+
scan(ctx: ScanContext): Finding[];
|
|
5
|
+
private detectAuthMiddleware;
|
|
6
|
+
private detectRoutesWithoutAuth;
|
|
7
|
+
private detectRateLimiting;
|
|
8
|
+
private detectSessionConfig;
|
|
9
|
+
private detectCORSSettings;
|
|
10
|
+
private detectMFA;
|
|
11
|
+
private searchPatterns;
|
|
12
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const SCAN_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".go", ".java", ".php"]);
|
|
2
|
+
export class AuthScanner {
|
|
3
|
+
name = "auth";
|
|
4
|
+
scan(ctx) {
|
|
5
|
+
const findings = [];
|
|
6
|
+
const content = ctx.fileContents;
|
|
7
|
+
const hasAuthMiddleware = this.detectAuthMiddleware(content);
|
|
8
|
+
const routesWithoutAuth = this.detectRoutesWithoutAuth(content, hasAuthMiddleware);
|
|
9
|
+
const hasRateLimiting = this.detectRateLimiting(content);
|
|
10
|
+
const hasSessionConfig = this.detectSessionConfig(content);
|
|
11
|
+
const hasCORSSettings = this.detectCORSSettings(content);
|
|
12
|
+
if (routesWithoutAuth.length > 0) {
|
|
13
|
+
for (const route of routesWithoutAuth.slice(0, 20)) {
|
|
14
|
+
findings.push({
|
|
15
|
+
ruleId: "AUTH-001",
|
|
16
|
+
severity: "high",
|
|
17
|
+
category: "authentication",
|
|
18
|
+
title: "Route without authentication",
|
|
19
|
+
description: `Endpoint ${route.method} ${route.path} does not require authentication. All endpoints handling personal data must require auth.`,
|
|
20
|
+
file: route.file,
|
|
21
|
+
line: route.line,
|
|
22
|
+
evidence: route.evidence,
|
|
23
|
+
controlIds: ["GDPR-ART32-004", "OWASP-ASVS-003", "OWASP-ASVS-004"],
|
|
24
|
+
fix: "Add authentication middleware to this route or apply globally.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!hasRateLimiting) {
|
|
29
|
+
findings.push({
|
|
30
|
+
ruleId: "AUTH-002",
|
|
31
|
+
severity: "high",
|
|
32
|
+
category: "authentication",
|
|
33
|
+
title: "No rate limiting detected",
|
|
34
|
+
description: "No rate limiting library or configuration found. Rate limiting is required on authentication endpoints and API routes.",
|
|
35
|
+
file: "project",
|
|
36
|
+
evidence: "No rate limiter (express-rate-limit, etc.) found in codebase",
|
|
37
|
+
controlIds: ["GDPR-ART32-004", "OWASP-ASVS-003"],
|
|
38
|
+
fix: "Install and configure rate limiting: npm install express-rate-limit",
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (!hasSessionConfig) {
|
|
42
|
+
findings.push({
|
|
43
|
+
ruleId: "AUTH-003",
|
|
44
|
+
severity: "medium",
|
|
45
|
+
category: "authentication",
|
|
46
|
+
title: "No session timeout configuration detected",
|
|
47
|
+
description: "No session expiration or timeout configuration found. Sessions must expire after a period of inactivity.",
|
|
48
|
+
file: "project",
|
|
49
|
+
evidence: "No session timeout configuration found",
|
|
50
|
+
controlIds: ["GDPR-ART32-005"],
|
|
51
|
+
fix: "Configure session expiration: maxAge, idle timeout, or JWT expiration.",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (hasCORSSettings === "wildcard") {
|
|
55
|
+
findings.push({
|
|
56
|
+
ruleId: "AUTH-004",
|
|
57
|
+
severity: "high",
|
|
58
|
+
category: "security",
|
|
59
|
+
title: "CORS configured as wildcard (*)",
|
|
60
|
+
description: "CORS is set to allow all origins. This is insecure for production. Restrict to known origins.",
|
|
61
|
+
file: "project",
|
|
62
|
+
evidence: "cors({ origin: '*' }) or Access-Control-Allow-Origin: *",
|
|
63
|
+
controlIds: ["OWASP-ASVS-006"],
|
|
64
|
+
fix: "Restrict CORS to specific origins: cors({ origin: ['https://yourdomain.com'] })",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (!this.detectMFA(content)) {
|
|
68
|
+
findings.push({
|
|
69
|
+
ruleId: "AUTH-005",
|
|
70
|
+
severity: "high",
|
|
71
|
+
category: "authentication",
|
|
72
|
+
title: "No MFA implementation detected",
|
|
73
|
+
description: "No multi-factor authentication implementation found. MFA is mandatory per GDPR Article 32.",
|
|
74
|
+
file: "project",
|
|
75
|
+
evidence: "No MFA/2FA/OTP/TOTP library found in dependencies or code",
|
|
76
|
+
controlIds: ["GDPR-ART32-004"],
|
|
77
|
+
fix: "Implement MFA using TOTP (otpauth, speakeasy) or WebAuthn.",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return findings;
|
|
81
|
+
}
|
|
82
|
+
detectAuthMiddleware(content) {
|
|
83
|
+
const authIndicators = [
|
|
84
|
+
/jwt\.verify|jsonwebtoken|jwtDecode/i,
|
|
85
|
+
/passport\.use|passport\.authenticate/i,
|
|
86
|
+
/authMiddleware|authGuard|requireAuth|isAuthenticated/i,
|
|
87
|
+
/session\s*\(\s*{/i,
|
|
88
|
+
/bearer\s+token/i,
|
|
89
|
+
/firebase.*auth/i,
|
|
90
|
+
/nextAuth|next-auth/i,
|
|
91
|
+
/supabase.*auth/i,
|
|
92
|
+
/clerk/i,
|
|
93
|
+
/auth0/i,
|
|
94
|
+
];
|
|
95
|
+
return this.searchPatterns(content, authIndicators);
|
|
96
|
+
}
|
|
97
|
+
detectRoutesWithoutAuth(content, hasGlobalAuth) {
|
|
98
|
+
const routes = [];
|
|
99
|
+
if (hasGlobalAuth)
|
|
100
|
+
return routes;
|
|
101
|
+
const routePattern = /(?:app|router|route)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]*)/gi;
|
|
102
|
+
for (const [filePath, fileContent] of content) {
|
|
103
|
+
const ext = filePath.substring(filePath.lastIndexOf("."));
|
|
104
|
+
if (!SCAN_EXTENSIONS.has(ext))
|
|
105
|
+
continue;
|
|
106
|
+
const lines = fileContent.split("\n");
|
|
107
|
+
for (let i = 0; i < lines.length; i++) {
|
|
108
|
+
routePattern.lastIndex = 0;
|
|
109
|
+
const match = routePattern.exec(lines[i]);
|
|
110
|
+
if (match) {
|
|
111
|
+
const path = match[2];
|
|
112
|
+
const publicPaths = ["/", "/health", "/healthz", "/status", "/ping", "/ready", "/readiness", "/version", "/public"];
|
|
113
|
+
if (!publicPaths.some(p => path === p)) {
|
|
114
|
+
routes.push({
|
|
115
|
+
method: match[1].toUpperCase(),
|
|
116
|
+
path,
|
|
117
|
+
file: filePath,
|
|
118
|
+
line: i + 1,
|
|
119
|
+
evidence: lines[i].trim(),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return routes;
|
|
126
|
+
}
|
|
127
|
+
detectRateLimiting(content) {
|
|
128
|
+
return this.searchPatterns(content, [
|
|
129
|
+
/rate.?limit/i,
|
|
130
|
+
/rateLimit|rate-limit/i,
|
|
131
|
+
/express-rate-limit/i,
|
|
132
|
+
/throttl/i,
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
detectSessionConfig(content) {
|
|
136
|
+
return this.searchPatterns(content, [
|
|
137
|
+
/session\s*\(\s*{[^}]*maxAge/i,
|
|
138
|
+
/maxAge\s*[:=]/i,
|
|
139
|
+
/expiresIn\s*[:=]/i,
|
|
140
|
+
/expires\s*[:=]/i,
|
|
141
|
+
/cookie\s*:\s*{[^}]*maxAge/i,
|
|
142
|
+
/idleTimeout/i,
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
detectCORSSettings(content) {
|
|
146
|
+
for (const [, fileContent] of content) {
|
|
147
|
+
if (/cors\s*\(\s*{[^}]*origin\s*:\s*['"]\*['"]/s.test(fileContent) ||
|
|
148
|
+
/Access-Control-Allow-Origin\s*:\s*\*/i.test(fileContent)) {
|
|
149
|
+
return "wildcard";
|
|
150
|
+
}
|
|
151
|
+
if (/cors\s*\(/i.test(fileContent) || /Access-Control-Allow/i.test(fileContent)) {
|
|
152
|
+
return "configured";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return "none";
|
|
156
|
+
}
|
|
157
|
+
detectMFA(content) {
|
|
158
|
+
return this.searchPatterns(content, [
|
|
159
|
+
/mfa|multi.?factor|2fa|two.?factor/i,
|
|
160
|
+
/totp|otpauth|speakeasy|otplib/i,
|
|
161
|
+
/webauthn|fido2|passkey/i,
|
|
162
|
+
/authenticator/i,
|
|
163
|
+
]);
|
|
164
|
+
}
|
|
165
|
+
searchPatterns(content, patterns) {
|
|
166
|
+
for (const [, fileContent] of content) {
|
|
167
|
+
for (const pattern of patterns) {
|
|
168
|
+
pattern.lastIndex = 0;
|
|
169
|
+
if (pattern.test(fileContent))
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=auth-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-scanner.js","sourceRoot":"","sources":["../../src/scanners/auth-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const SQL_INJECTION_PATTERNS = [
|
|
2
|
+
{ pattern: /(?:query|execute|raw|sql)\s*\(\s*[`"'].*\+\s*(?:req|params|query|body|input|request)/gi, desc: "SQL query with string concatenation from user input" },
|
|
3
|
+
{ pattern: /(?:query|execute|raw|sql)\s*\(\s*[`"'].*\$\{(?:req|params|query|body)/gi, desc: "SQL query with template literal injection" },
|
|
4
|
+
{ pattern: /SELECT\s+.*\s+FROM\s+.*\s+WHERE\s+.*\+\s*(?:req|params|query|body)/gi, desc: "SQL SELECT with concatenated user input" },
|
|
5
|
+
{ pattern: /INSERT\s+INTO\s+.*VALUES\s*\(.*\+\s*(?:req|params|query|body)/gi, desc: "SQL INSERT with concatenated user input" },
|
|
6
|
+
{ pattern: /DELETE\s+FROM\s+.*WHERE\s+.*\+\s*(?:req|params|query|body)/gi, desc: "SQL DELETE with concatenated user input" },
|
|
7
|
+
{ pattern: /UPDATE\s+.*SET\s+.*\+\s*(?:req|params|query|body)/gi, desc: "SQL UPDATE with concatenated user input" },
|
|
8
|
+
];
|
|
9
|
+
const XSS_PATTERNS = [
|
|
10
|
+
{ pattern: /innerHTML\s*=\s*(?:req|params|query|body|input)/gi, desc: "Direct innerHTML assignment from user input" },
|
|
11
|
+
{ pattern: /document\.write\s*\(\s*(?:req|params|query|body)/gi, desc: "document.write with user input" },
|
|
12
|
+
{ pattern: /v-html\s*=\s*(?:req|params|query|body|input)/gi, desc: "Vue v-html with user input" },
|
|
13
|
+
{ pattern: /dangerouslySetInnerHTML\s*=\s*\{.*(?:req|params|query|body)/gi, desc: "React dangerouslySetInnerHTML with user input" },
|
|
14
|
+
{ pattern: /\.html\s*\(\s*(?:req|params|query|body)/gi, desc: "jQuery .html() with user input" },
|
|
15
|
+
];
|
|
16
|
+
const INPUT_VALIDATION_PATTERNS = [
|
|
17
|
+
{ pattern: /(?:parseInt|parseFloat|Number)\s*\(\s*req\.(?:body|params|query)/gi, desc: "Unvalidated number parsing from request" },
|
|
18
|
+
{ pattern: /eval\s*\(\s*(?:req|params|query|body|input)/gi, desc: "eval() with user input - critical RCE risk" },
|
|
19
|
+
{ pattern: /Function\s*\(\s*(?:req|params|query|body)/gi, desc: "Function constructor with user input" },
|
|
20
|
+
{ pattern: /exec\s*\(\s*(?:req|params|query|body)/gi, desc: "Command execution with user input" },
|
|
21
|
+
{ pattern: /child_process.*(?:req|params|query|body)/gi, desc: "Child process with user input" },
|
|
22
|
+
];
|
|
23
|
+
const SCAN_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".go", ".java", ".php"]);
|
|
24
|
+
export class CodeSecurityScanner {
|
|
25
|
+
name = "code-security";
|
|
26
|
+
scan(ctx) {
|
|
27
|
+
const findings = [];
|
|
28
|
+
for (const [filePath, content] of ctx.fileContents) {
|
|
29
|
+
const ext = filePath.substring(filePath.lastIndexOf("."));
|
|
30
|
+
if (!SCAN_EXTENSIONS.has(ext))
|
|
31
|
+
continue;
|
|
32
|
+
const lines = content.split("\n");
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
const line = lines[i];
|
|
35
|
+
for (const { pattern, desc } of SQL_INJECTION_PATTERNS) {
|
|
36
|
+
pattern.lastIndex = 0;
|
|
37
|
+
if (pattern.test(line)) {
|
|
38
|
+
findings.push({
|
|
39
|
+
ruleId: "INJECT-001",
|
|
40
|
+
severity: "critical",
|
|
41
|
+
category: "injection",
|
|
42
|
+
title: "SQL Injection vulnerability",
|
|
43
|
+
description: desc + ". Use parameterized queries or an ORM.",
|
|
44
|
+
file: filePath,
|
|
45
|
+
line: i + 1,
|
|
46
|
+
evidence: line.trim(),
|
|
47
|
+
controlIds: ["OWASP-ASVS-001", "GDPR-ART5-006"],
|
|
48
|
+
fix: "Use parameterized queries: db.query('SELECT * FROM users WHERE id = $1', [req.query.id])",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (const { pattern, desc } of XSS_PATTERNS) {
|
|
53
|
+
pattern.lastIndex = 0;
|
|
54
|
+
if (pattern.test(line)) {
|
|
55
|
+
findings.push({
|
|
56
|
+
ruleId: "INJECT-002",
|
|
57
|
+
severity: "critical",
|
|
58
|
+
category: "xss",
|
|
59
|
+
title: "Cross-Site Scripting (XSS) vulnerability",
|
|
60
|
+
description: desc + ". Sanitize all user input before rendering.",
|
|
61
|
+
file: filePath,
|
|
62
|
+
line: i + 1,
|
|
63
|
+
evidence: line.trim(),
|
|
64
|
+
controlIds: ["OWASP-ASVS-002", "GDPR-ART5-006"],
|
|
65
|
+
fix: "Use textContent instead of innerHTML, or sanitize input with a library like DOMPurify.",
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const { pattern, desc } of INPUT_VALIDATION_PATTERNS) {
|
|
70
|
+
pattern.lastIndex = 0;
|
|
71
|
+
if (pattern.test(line)) {
|
|
72
|
+
findings.push({
|
|
73
|
+
ruleId: "INJECT-003",
|
|
74
|
+
severity: "critical",
|
|
75
|
+
category: "injection",
|
|
76
|
+
title: "Code injection risk",
|
|
77
|
+
description: desc + ". Never pass user input to code execution functions.",
|
|
78
|
+
file: filePath,
|
|
79
|
+
line: i + 1,
|
|
80
|
+
evidence: line.trim(),
|
|
81
|
+
controlIds: ["OWASP-ASVS-001"],
|
|
82
|
+
fix: "Remove eval/exec usage with user input. Use safe alternatives.",
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return findings;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=code-security-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-security-scanner.js","sourceRoot":"","sources":["../../src/scanners/code-security-scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Scanner, Finding, ScanContext } from "./types.js";
|
|
2
|
+
export declare class ConfigScanner implements Scanner {
|
|
3
|
+
name: string;
|
|
4
|
+
scan(ctx: ScanContext): Finding[];
|
|
5
|
+
private checkPackageJson;
|
|
6
|
+
private checkEnvFiles;
|
|
7
|
+
private checkDockerConfig;
|
|
8
|
+
private checkTLSConfig;
|
|
9
|
+
private checkGitignore;
|
|
10
|
+
private checkLoggingConfig;
|
|
11
|
+
private searchContent;
|
|
12
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
export class ConfigScanner {
|
|
2
|
+
name = "config";
|
|
3
|
+
scan(ctx) {
|
|
4
|
+
const findings = [];
|
|
5
|
+
this.checkPackageJson(ctx, findings);
|
|
6
|
+
this.checkEnvFiles(ctx, findings);
|
|
7
|
+
this.checkDockerConfig(ctx, findings);
|
|
8
|
+
this.checkTLSConfig(ctx, findings);
|
|
9
|
+
this.checkGitignore(ctx, findings);
|
|
10
|
+
this.checkLoggingConfig(ctx, findings);
|
|
11
|
+
return findings;
|
|
12
|
+
}
|
|
13
|
+
checkPackageJson(ctx, findings) {
|
|
14
|
+
const pkgContent = ctx.fileContents.get("package.json");
|
|
15
|
+
if (!pkgContent)
|
|
16
|
+
return;
|
|
17
|
+
try {
|
|
18
|
+
const pkg = JSON.parse(pkgContent);
|
|
19
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
20
|
+
if (deps.helmet === undefined && (deps.express || deps.koa || deps.fastify)) {
|
|
21
|
+
findings.push({
|
|
22
|
+
ruleId: "CONFIG-001",
|
|
23
|
+
severity: "high",
|
|
24
|
+
category: "security",
|
|
25
|
+
title: "Missing security headers (no helmet)",
|
|
26
|
+
description: "No helmet middleware detected for HTTP framework. Security headers protect against XSS, clickjacking, and other attacks.",
|
|
27
|
+
file: "package.json",
|
|
28
|
+
evidence: "helmet not in dependencies",
|
|
29
|
+
controlIds: ["OWASP-ASVS-002", "OWASP-ASVS-006"],
|
|
30
|
+
fix: "npm install helmet && app.use(helmet())",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (deps.cors === undefined && (deps.express || deps.fastify)) {
|
|
34
|
+
findings.push({
|
|
35
|
+
ruleId: "CONFIG-002",
|
|
36
|
+
severity: "medium",
|
|
37
|
+
category: "security",
|
|
38
|
+
title: "No CORS configuration",
|
|
39
|
+
description: "No CORS package found. Unrestricted CORS can expose your API to cross-origin attacks.",
|
|
40
|
+
file: "package.json",
|
|
41
|
+
evidence: "cors not in dependencies",
|
|
42
|
+
controlIds: ["OWASP-ASVS-006"],
|
|
43
|
+
fix: "npm install cors and configure allowed origins explicitly.",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const auditDeps = ["express", "lodash", "axios", "underscore"];
|
|
47
|
+
for (const dep of auditDeps) {
|
|
48
|
+
if (deps[dep]) {
|
|
49
|
+
findings.push({
|
|
50
|
+
ruleId: "CONFIG-003",
|
|
51
|
+
severity: "medium",
|
|
52
|
+
category: "dependencies",
|
|
53
|
+
title: `Dependency review needed: ${dep}`,
|
|
54
|
+
description: `${dep} is a commonly exploited dependency. Ensure you are running the latest version with no known vulnerabilities.`,
|
|
55
|
+
file: "package.json",
|
|
56
|
+
evidence: `${dep}: ${deps[dep]}`,
|
|
57
|
+
controlIds: ["CIS-004", "OWASP-ASVS-005"],
|
|
58
|
+
fix: "Run npm audit regularly. Update to latest version. Consider automated dependency scanning.",
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// not valid JSON
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
checkEnvFiles(ctx, findings) {
|
|
68
|
+
for (const [filePath, content] of ctx.fileContents) {
|
|
69
|
+
if (filePath !== ".env" && !filePath.endsWith("/.env") && !filePath.startsWith(".env."))
|
|
70
|
+
continue;
|
|
71
|
+
if (filePath.includes("example") || filePath.includes("template"))
|
|
72
|
+
continue;
|
|
73
|
+
const lines = content.split("\n");
|
|
74
|
+
for (let i = 0; i < lines.length; i++) {
|
|
75
|
+
const line = lines[i].trim();
|
|
76
|
+
if (!line || line.startsWith("#"))
|
|
77
|
+
continue;
|
|
78
|
+
if (/\b(PASSWORD|SECRET|KEY|TOKEN|PRIVATE)\b.*=\s*[^\s]/i.test(line) &&
|
|
79
|
+
!line.includes("your_") && !line.includes("changeme") && !line.includes("xxx")) {
|
|
80
|
+
findings.push({
|
|
81
|
+
ruleId: "CONFIG-004",
|
|
82
|
+
severity: "critical",
|
|
83
|
+
category: "secrets",
|
|
84
|
+
title: "Secret with value in .env file",
|
|
85
|
+
description: "A .env file contains actual secret values. Ensure .env files are in .gitignore and never committed.",
|
|
86
|
+
file: filePath,
|
|
87
|
+
line: i + 1,
|
|
88
|
+
evidence: line.split("=")[0] + "=***",
|
|
89
|
+
controlIds: ["OWASP-ASVS-005", "GDPR-ART32-002"],
|
|
90
|
+
fix: "Ensure .env is in .gitignore. Use a secrets management solution for production.",
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
checkDockerConfig(ctx, findings) {
|
|
97
|
+
const dockerfile = ctx.fileContents.get("Dockerfile");
|
|
98
|
+
if (dockerfile) {
|
|
99
|
+
if (/USER\s+root/i.test(dockerfile) || (!/USER\s+/i.test(dockerfile))) {
|
|
100
|
+
findings.push({
|
|
101
|
+
ruleId: "CONFIG-005",
|
|
102
|
+
severity: "medium",
|
|
103
|
+
category: "infrastructure",
|
|
104
|
+
title: "Docker running as root",
|
|
105
|
+
description: "Container may be running as root. Use a non-root user for security.",
|
|
106
|
+
file: "Dockerfile",
|
|
107
|
+
evidence: "No non-root USER directive found",
|
|
108
|
+
controlIds: ["CIS-003"],
|
|
109
|
+
fix: "Add: USER node (or other non-root user) to your Dockerfile.",
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (/\bENV\b.*(?:PASSWORD|SECRET|KEY|TOKEN)\s*=\s*\S+/i.test(dockerfile)) {
|
|
113
|
+
findings.push({
|
|
114
|
+
ruleId: "CONFIG-006",
|
|
115
|
+
severity: "critical",
|
|
116
|
+
category: "secrets",
|
|
117
|
+
title: "Secret in Dockerfile ENV",
|
|
118
|
+
description: "Secrets must not be baked into Docker images.",
|
|
119
|
+
file: "Dockerfile",
|
|
120
|
+
evidence: "ENV with secret value",
|
|
121
|
+
controlIds: ["OWASP-ASVS-005"],
|
|
122
|
+
fix: "Use Docker secrets or environment variables at runtime instead.",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
checkTLSConfig(ctx, findings) {
|
|
128
|
+
for (const [filePath, content] of ctx.fileContents) {
|
|
129
|
+
if (!filePath.includes(".env") && !filePath.includes("config"))
|
|
130
|
+
continue;
|
|
131
|
+
if (/\bNODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]?0['"]?/i.test(content)) {
|
|
132
|
+
findings.push({
|
|
133
|
+
ruleId: "CONFIG-007",
|
|
134
|
+
severity: "critical",
|
|
135
|
+
category: "encryption",
|
|
136
|
+
title: "TLS verification disabled",
|
|
137
|
+
description: "NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate verification, enabling MITM attacks.",
|
|
138
|
+
file: filePath,
|
|
139
|
+
evidence: "NODE_TLS_REJECT_UNAUTHORIZED=0",
|
|
140
|
+
controlIds: ["GDPR-ART32-003", "OWASP-ASVS-006"],
|
|
141
|
+
fix: "Remove NODE_TLS_REJECT_UNAUTHORIZED=0. Fix the certificate issue instead.",
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
checkGitignore(ctx, findings) {
|
|
147
|
+
const gitignore = ctx.fileContents.get(".gitignore");
|
|
148
|
+
if (!gitignore) {
|
|
149
|
+
findings.push({
|
|
150
|
+
ruleId: "CONFIG-008",
|
|
151
|
+
severity: "high",
|
|
152
|
+
category: "security",
|
|
153
|
+
title: "No .gitignore file",
|
|
154
|
+
description: "No .gitignore found. Secrets and build artifacts may be committed accidentally.",
|
|
155
|
+
file: ".gitignore",
|
|
156
|
+
evidence: "File not found",
|
|
157
|
+
controlIds: ["OWASP-ASVS-005"],
|
|
158
|
+
fix: "Create .gitignore with node_modules/, .env, dist/, *.key, etc.",
|
|
159
|
+
});
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const required = [".env", "node_modules"];
|
|
163
|
+
for (const pattern of required) {
|
|
164
|
+
if (!gitignore.includes(pattern)) {
|
|
165
|
+
findings.push({
|
|
166
|
+
ruleId: "CONFIG-009",
|
|
167
|
+
severity: "high",
|
|
168
|
+
category: "security",
|
|
169
|
+
title: `.gitignore missing ${pattern}`,
|
|
170
|
+
description: `${pattern} should be in .gitignore to prevent accidental commits.`,
|
|
171
|
+
file: ".gitignore",
|
|
172
|
+
evidence: `${pattern} not found in .gitignore`,
|
|
173
|
+
controlIds: ["OWASP-ASVS-005"],
|
|
174
|
+
fix: `Add ${pattern} to .gitignore.`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
checkLoggingConfig(ctx, findings) {
|
|
180
|
+
const hasLogging = this.searchContent(ctx, [
|
|
181
|
+
/winston|pino|bunyan|morgan|helmet/i,
|
|
182
|
+
/logging|logger/i,
|
|
183
|
+
/auditLog|audit_log/i,
|
|
184
|
+
]);
|
|
185
|
+
if (!hasLogging) {
|
|
186
|
+
findings.push({
|
|
187
|
+
ruleId: "CONFIG-010",
|
|
188
|
+
severity: "high",
|
|
189
|
+
category: "audit",
|
|
190
|
+
title: "No logging framework detected",
|
|
191
|
+
description: "No logging library or audit logging found. Audit logging is mandatory for GDPR compliance.",
|
|
192
|
+
file: "project",
|
|
193
|
+
evidence: "No logging library (winston, pino, etc.) found",
|
|
194
|
+
controlIds: ["GDPR-ART32-006", "OWASP-ASVS-004"],
|
|
195
|
+
fix: "Install a logging library (winston or pino) and implement structured audit logging.",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
searchContent(ctx, patterns) {
|
|
200
|
+
for (const [, content] of ctx.fileContents) {
|
|
201
|
+
for (const pattern of patterns) {
|
|
202
|
+
pattern.lastIndex = 0;
|
|
203
|
+
if (pattern.test(content))
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=config-scanner.js.map
|