@havoc-security/scanner 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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +22 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.d.ts +7 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.js +100 -0
- package/dist/analyzers/AuthorizationCoverageAnalyzer.js.map +1 -0
- package/dist/analyzers/CredentialExposureAnalyzer.d.ts +11 -0
- package/dist/analyzers/CredentialExposureAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/CredentialExposureAnalyzer.js +262 -0
- package/dist/analyzers/CredentialExposureAnalyzer.js.map +1 -0
- package/dist/analyzers/DependencyAuditAnalyzer.d.ts +28 -0
- package/dist/analyzers/DependencyAuditAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/DependencyAuditAnalyzer.js +107 -0
- package/dist/analyzers/DependencyAuditAnalyzer.js.map +1 -0
- package/dist/analyzers/EncryptionAnalyzer.d.ts +7 -0
- package/dist/analyzers/EncryptionAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/EncryptionAnalyzer.js +170 -0
- package/dist/analyzers/EncryptionAnalyzer.js.map +1 -0
- package/dist/analyzers/FileUploadAnalyzer.d.ts +8 -0
- package/dist/analyzers/FileUploadAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/FileUploadAnalyzer.js +193 -0
- package/dist/analyzers/FileUploadAnalyzer.js.map +1 -0
- package/dist/analyzers/IdorAnalyzer.d.ts +7 -0
- package/dist/analyzers/IdorAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/IdorAnalyzer.js +91 -0
- package/dist/analyzers/IdorAnalyzer.js.map +1 -0
- package/dist/analyzers/MassAssignmentAnalyzer.d.ts +7 -0
- package/dist/analyzers/MassAssignmentAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/MassAssignmentAnalyzer.js +90 -0
- package/dist/analyzers/MassAssignmentAnalyzer.js.map +1 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.d.ts +7 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.js +217 -0
- package/dist/analyzers/PrivilegeEscalationAnalyzer.js.map +1 -0
- package/dist/analyzers/RateLimitAnalyzer.d.ts +7 -0
- package/dist/analyzers/RateLimitAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/RateLimitAnalyzer.js +151 -0
- package/dist/analyzers/RateLimitAnalyzer.js.map +1 -0
- package/dist/analyzers/SessionSecurityAnalyzer.d.ts +10 -0
- package/dist/analyzers/SessionSecurityAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/SessionSecurityAnalyzer.js +295 -0
- package/dist/analyzers/SessionSecurityAnalyzer.js.map +1 -0
- package/dist/analyzers/SqlInjectionAnalyzer.d.ts +7 -0
- package/dist/analyzers/SqlInjectionAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/SqlInjectionAnalyzer.js +77 -0
- package/dist/analyzers/SqlInjectionAnalyzer.js.map +1 -0
- package/dist/analyzers/XssSurfaceAnalyzer.d.ts +7 -0
- package/dist/analyzers/XssSurfaceAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/XssSurfaceAnalyzer.js +100 -0
- package/dist/analyzers/XssSurfaceAnalyzer.js.map +1 -0
- package/dist/analyzers/index.d.ts +13 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/index.js +13 -0
- package/dist/analyzers/index.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +139 -0
- package/dist/index.js.map +1 -0
- package/dist/parsers/PhpParser.d.ts +56 -0
- package/dist/parsers/PhpParser.d.ts.map +1 -0
- package/dist/parsers/PhpParser.js +193 -0
- package/dist/parsers/PhpParser.js.map +1 -0
- package/dist/parsers/RouteParser.d.ts +87 -0
- package/dist/parsers/RouteParser.d.ts.map +1 -0
- package/dist/parsers/RouteParser.js +327 -0
- package/dist/parsers/RouteParser.js.map +1 -0
- package/dist/rules/index.d.ts +14 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +9 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/types/index.d.ts +137 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +13 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +30 -0
- package/package.json.bak +27 -0
- package/src/analyzers/AuthorizationCoverageAnalyzer.ts +213 -0
- package/src/analyzers/CredentialExposureAnalyzer.ts +312 -0
- package/src/analyzers/DependencyAuditAnalyzer.ts +135 -0
- package/src/analyzers/EncryptionAnalyzer.ts +195 -0
- package/src/analyzers/FileUploadAnalyzer.ts +239 -0
- package/src/analyzers/IdorAnalyzer.ts +118 -0
- package/src/analyzers/InsecureDeserializationAnalyzer.ts +212 -0
- package/src/analyzers/MassAssignmentAnalyzer.ts +105 -0
- package/src/analyzers/OpenRedirectAnalyzer.ts +149 -0
- package/src/analyzers/PrivilegeEscalationAnalyzer.ts +258 -0
- package/src/analyzers/RateLimitAnalyzer.ts +195 -0
- package/src/analyzers/SecurityHeaderAnalyzer.ts +263 -0
- package/src/analyzers/SessionSecurityAnalyzer.ts +342 -0
- package/src/analyzers/SqlInjectionAnalyzer.ts +99 -0
- package/src/analyzers/XssSurfaceAnalyzer.ts +112 -0
- package/src/analyzers/exclusions.ts +87 -0
- package/src/analyzers/index.ts +15 -0
- package/src/index.ts +226 -0
- package/src/parsers/PhpParser.ts +259 -0
- package/src/parsers/RouteParser.ts +384 -0
- package/src/rules/index.ts +16 -0
- package/src/types/index.ts +164 -0
- package/tests/EncryptionAnalyzer.test.ts +137 -0
- package/tests/PrivilegeEscalationAnalyzer.test.ts +141 -0
- package/tests/RateLimitAnalyzer.test.ts +112 -0
- package/tests/analyzers.test.ts +678 -0
- package/tests/auth-coverage-route-aware.test.ts +294 -0
- package/tests/credential-exposure.test.ts +142 -0
- package/tests/file-upload.test.ts +141 -0
- package/tests/fixtures/app/Http/Controllers/AdminController.php +19 -0
- package/tests/fixtures/app/Http/Controllers/PostController.php +49 -0
- package/tests/fixtures/app/Http/Controllers/PublicController.php +17 -0
- package/tests/fixtures/app/Models/Comment.php +11 -0
- package/tests/fixtures/app/Models/OpenModel.php +11 -0
- package/tests/fixtures/app/Models/Post.php +14 -0
- package/tests/fixtures/app/Models/SafeModel.php +10 -0
- package/tests/fixtures/app/Models/User.php +15 -0
- package/tests/fixtures/blade/mail.blade.php +8 -0
- package/tests/fixtures/blade/safe.blade.php +12 -0
- package/tests/fixtures/blade/vulnerable.blade.php +12 -0
- package/tests/fixtures/controllers/AdminController.php +19 -0
- package/tests/fixtures/controllers/PostController.php +49 -0
- package/tests/fixtures/controllers/PublicController.php +17 -0
- package/tests/fixtures/deserialization/safe.php +32 -0
- package/tests/fixtures/deserialization/unsafe.php +60 -0
- package/tests/fixtures/models/Comment.php +11 -0
- package/tests/fixtures/models/OpenModel.php +11 -0
- package/tests/fixtures/models/Post.php +14 -0
- package/tests/fixtures/models/SafeModel.php +10 -0
- package/tests/fixtures/models/User.php +15 -0
- package/tests/fixtures/redirect/safe.php +38 -0
- package/tests/fixtures/redirect/unsafe.php +39 -0
- package/tests/fixtures/routes/api.php +9 -0
- package/tests/fixtures/routes/web.php +18 -0
- package/tests/fixtures/security-headers/app/Http/Middleware/SecurityHeaders.php +24 -0
- package/tests/fixtures/security-headers/app/Providers/AppServiceProvider.php +16 -0
- package/tests/fixtures/sql/safe_queries.php +7 -0
- package/tests/fixtures/sql/vulnerable_queries.php +7 -0
- package/tests/new-analyzers.test.ts +373 -0
- package/tests/route-parser.test.ts +257 -0
- package/tests/scanner.test.ts +82 -0
- package/tests/session-security.test.ts +161 -0
- package/tests/types.test.ts +29 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HAVOC Route Parser
|
|
3
|
+
*
|
|
4
|
+
* Parses Laravel route files (routes/web.php, routes/api.php, etc.) to extract
|
|
5
|
+
* route definitions and their associated middleware — including group-inherited
|
|
6
|
+
* middleware. This data is used by the AuthorizationCoverageAnalyzer to eliminate
|
|
7
|
+
* false positives where auth/authorization is handled at the route level.
|
|
8
|
+
*/
|
|
9
|
+
import { readFile } from 'node:fs/promises';
|
|
10
|
+
import { resolve, relative } from 'node:path';
|
|
11
|
+
import { glob } from 'glob';
|
|
12
|
+
// ─── Middleware Classification ────────────────────────────────────────────────
|
|
13
|
+
/** Middleware that provide AUTHORIZATION (i.e., permission checks — clears finding) */
|
|
14
|
+
const AUTHORIZATION_MIDDLEWARE_PATTERNS = [
|
|
15
|
+
/^can:/,
|
|
16
|
+
/^permission:/,
|
|
17
|
+
/^role:/,
|
|
18
|
+
];
|
|
19
|
+
/** Middleware that provide AUTHENTICATION only (downgrades severity but does not clear) */
|
|
20
|
+
const AUTHENTICATION_MIDDLEWARE_PATTERNS = [
|
|
21
|
+
/^auth$/,
|
|
22
|
+
/^auth:/,
|
|
23
|
+
/^sanctum$/,
|
|
24
|
+
/^verified$/,
|
|
25
|
+
];
|
|
26
|
+
export function isAuthorizationMiddleware(mw) {
|
|
27
|
+
return AUTHORIZATION_MIDDLEWARE_PATTERNS.some((re) => re.test(mw.trim()));
|
|
28
|
+
}
|
|
29
|
+
export function isAuthenticationMiddleware(mw) {
|
|
30
|
+
return AUTHENTICATION_MIDDLEWARE_PATTERNS.some((re) => re.test(mw.trim()));
|
|
31
|
+
}
|
|
32
|
+
export function hasAuthorizationMiddleware(middleware) {
|
|
33
|
+
return middleware.some(isAuthorizationMiddleware);
|
|
34
|
+
}
|
|
35
|
+
export function hasAuthenticationMiddleware(middleware) {
|
|
36
|
+
return middleware.some(isAuthenticationMiddleware);
|
|
37
|
+
}
|
|
38
|
+
// ─── Webhook Detection ────────────────────────────────────────────────────────
|
|
39
|
+
const WEBHOOK_PATTERNS = [
|
|
40
|
+
/\$request->header\s*\(\s*['"]X-[^'"]*Signature/i,
|
|
41
|
+
/hash_hmac\s*\(/,
|
|
42
|
+
/hash_equals\s*\(/,
|
|
43
|
+
/SignatureVerif/i,
|
|
44
|
+
/validateSignature/i,
|
|
45
|
+
/verifySignature/i,
|
|
46
|
+
/StripeSignature/i,
|
|
47
|
+
/WebhookSignature/i,
|
|
48
|
+
];
|
|
49
|
+
export function isWebhookHandler(bodyText) {
|
|
50
|
+
return WEBHOOK_PATTERNS.some((re) => re.test(bodyText));
|
|
51
|
+
}
|
|
52
|
+
// ─── Internal Helpers ─────────────────────────────────────────────────────────
|
|
53
|
+
/**
|
|
54
|
+
* Parses middleware from a middleware() call argument string.
|
|
55
|
+
* Handles both string and array forms:
|
|
56
|
+
* 'auth'
|
|
57
|
+
* ['auth', 'can:view-admin', 'verified']
|
|
58
|
+
*/
|
|
59
|
+
function parseMiddlewareArg(arg) {
|
|
60
|
+
arg = arg.trim();
|
|
61
|
+
// Array form: ['auth', 'can:view-admin']
|
|
62
|
+
if (arg.startsWith('[')) {
|
|
63
|
+
const inner = arg.slice(1, arg.lastIndexOf(']'));
|
|
64
|
+
return inner.match(/['"]([^'"]+)['"]/g)?.map((s) => s.replace(/['"]/g, '')) ?? [];
|
|
65
|
+
}
|
|
66
|
+
// String form: 'auth' or "auth"
|
|
67
|
+
const m = arg.match(/^['"]([^'"]+)['"]$/);
|
|
68
|
+
if (m)
|
|
69
|
+
return [m[1]];
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Extract the controller class name (short name) and action from a route action string.
|
|
74
|
+
*
|
|
75
|
+
* Supported formats:
|
|
76
|
+
* [PostController::class, 'index']
|
|
77
|
+
* 'PostController@index'
|
|
78
|
+
* PostController::class (resource)
|
|
79
|
+
*/
|
|
80
|
+
function parseControllerAction(actionStr) {
|
|
81
|
+
actionStr = actionStr.trim();
|
|
82
|
+
// Array syntax: [PostController::class, 'index']
|
|
83
|
+
const arrayMatch = actionStr.match(/\[?\s*([A-Za-z_\\]+)(?:::class)?\s*,\s*['"]([^'"]+)['"]\s*\]?/);
|
|
84
|
+
if (arrayMatch) {
|
|
85
|
+
const parts = arrayMatch[1].split('\\');
|
|
86
|
+
return { controller: parts[parts.length - 1], action: arrayMatch[2] };
|
|
87
|
+
}
|
|
88
|
+
// String syntax: 'PostController@index'
|
|
89
|
+
const atMatch = actionStr.match(/['"]?([A-Za-z_\\]+)@([A-Za-z_]+)['"]?/);
|
|
90
|
+
if (atMatch) {
|
|
91
|
+
const parts = atMatch[1].split('\\');
|
|
92
|
+
return { controller: parts[parts.length - 1], action: atMatch[2] };
|
|
93
|
+
}
|
|
94
|
+
// Resource: PostController::class (no explicit action)
|
|
95
|
+
const resourceMatch = actionStr.match(/([A-Za-z_\\]+)::class/);
|
|
96
|
+
if (resourceMatch) {
|
|
97
|
+
const parts = resourceMatch[1].split('\\');
|
|
98
|
+
return { controller: parts[parts.length - 1], action: '*' };
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/** Standard Laravel resource controller actions */
|
|
103
|
+
const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'];
|
|
104
|
+
// ─── Route Parser ─────────────────────────────────────────────────────────────
|
|
105
|
+
/**
|
|
106
|
+
* Regex-based Laravel route file parser.
|
|
107
|
+
*
|
|
108
|
+
* Handles:
|
|
109
|
+
* - Route::get/post/put/patch/delete/any('uri', ...)
|
|
110
|
+
* - Route::resource('resource', Controller::class)
|
|
111
|
+
* - Route::middleware([...])->group(function () { ... })
|
|
112
|
+
* - Nested groups
|
|
113
|
+
*/
|
|
114
|
+
export class RouteParser {
|
|
115
|
+
/**
|
|
116
|
+
* Parse a single route file's content and return extracted routes.
|
|
117
|
+
*/
|
|
118
|
+
parseContent(content, filePath = '') {
|
|
119
|
+
const routes = [];
|
|
120
|
+
this.parseBlock(content, [], routes);
|
|
121
|
+
return { path: filePath, routes };
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse a route file from disk.
|
|
125
|
+
*/
|
|
126
|
+
async parseFile(filePath, rootDir) {
|
|
127
|
+
const content = await readFile(filePath, 'utf-8').catch(() => '');
|
|
128
|
+
const relativePath = rootDir ? relative(rootDir, filePath) : filePath;
|
|
129
|
+
return this.parseContent(content, relativePath);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Parse all route files in a Laravel project's routes/ directory.
|
|
133
|
+
*/
|
|
134
|
+
async parseDirectory(projectRoot) {
|
|
135
|
+
const absRoot = resolve(projectRoot);
|
|
136
|
+
const files = await glob(`${absRoot}/routes/**/*.php`, { nodir: true });
|
|
137
|
+
return Promise.all(files.map((f) => this.parseFile(f, absRoot)));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Recursively parse a block of PHP route code, tracking inherited middleware.
|
|
141
|
+
*/
|
|
142
|
+
parseBlock(content, inheritedMiddleware, routes) {
|
|
143
|
+
let pos = 0;
|
|
144
|
+
while (pos < content.length) {
|
|
145
|
+
const routeIdx = content.indexOf('Route::', pos);
|
|
146
|
+
if (routeIdx === -1)
|
|
147
|
+
break;
|
|
148
|
+
const chain = this.extractChain(content, routeIdx);
|
|
149
|
+
if (!chain) {
|
|
150
|
+
pos = routeIdx + 7;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const { text, end } = chain;
|
|
154
|
+
// Check if this is a group
|
|
155
|
+
const groupMiddleware = this.extractGroupMiddleware(text);
|
|
156
|
+
if (groupMiddleware !== null) {
|
|
157
|
+
const groupBody = this.extractGroupBody(content, routeIdx);
|
|
158
|
+
if (groupBody) {
|
|
159
|
+
const combined = [...inheritedMiddleware, ...groupMiddleware];
|
|
160
|
+
this.parseBlock(groupBody.body, combined, routes);
|
|
161
|
+
pos = groupBody.end;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const extracted = this.extractRoute(text, inheritedMiddleware);
|
|
166
|
+
if (extracted.length > 0) {
|
|
167
|
+
routes.push(...extracted);
|
|
168
|
+
}
|
|
169
|
+
pos = end;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Extract the full chained call text starting at a Route:: position.
|
|
174
|
+
* Stops at semicolons not inside brackets/parens.
|
|
175
|
+
*/
|
|
176
|
+
extractChain(content, start) {
|
|
177
|
+
let depth = 0;
|
|
178
|
+
let i = start;
|
|
179
|
+
let inString = false;
|
|
180
|
+
let stringChar = '';
|
|
181
|
+
while (i < content.length) {
|
|
182
|
+
const ch = content[i];
|
|
183
|
+
if (inString) {
|
|
184
|
+
if (ch === stringChar && content[i - 1] !== '\\')
|
|
185
|
+
inString = false;
|
|
186
|
+
}
|
|
187
|
+
else if (ch === '"' || ch === "'") {
|
|
188
|
+
inString = true;
|
|
189
|
+
stringChar = ch;
|
|
190
|
+
}
|
|
191
|
+
else if (ch === '(' || ch === '[') {
|
|
192
|
+
depth++;
|
|
193
|
+
}
|
|
194
|
+
else if (ch === ')' || ch === ']') {
|
|
195
|
+
depth--;
|
|
196
|
+
}
|
|
197
|
+
else if (ch === '{' && depth === 0) {
|
|
198
|
+
// Entering a closure body — stop here (group handling takes over)
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
else if (ch === '}' && depth < 0) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
else if (ch === ';' && depth === 0) {
|
|
205
|
+
return { text: content.slice(start, i + 1), end: i + 1 };
|
|
206
|
+
}
|
|
207
|
+
i++;
|
|
208
|
+
}
|
|
209
|
+
if (i > start) {
|
|
210
|
+
return { text: content.slice(start, i), end: i };
|
|
211
|
+
}
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* If the chain is a Route::middleware(...)->group(...), extract the middleware list.
|
|
216
|
+
* Returns null if this is not a group.
|
|
217
|
+
*/
|
|
218
|
+
extractGroupMiddleware(chain) {
|
|
219
|
+
const mwMatch = chain.match(/Route::(?:\w+\([^)]*\)->)*middleware\s*\(([^)]+)\)/);
|
|
220
|
+
if (!mwMatch)
|
|
221
|
+
return null;
|
|
222
|
+
if (!chain.includes('->group('))
|
|
223
|
+
return null;
|
|
224
|
+
return parseMiddlewareArg(mwMatch[1]);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Find the closure body of a ->group(function () { ... }) call.
|
|
228
|
+
*/
|
|
229
|
+
extractGroupBody(content, routeStart) {
|
|
230
|
+
const groupIdx = content.indexOf('->group(', routeStart);
|
|
231
|
+
if (groupIdx === -1)
|
|
232
|
+
return null;
|
|
233
|
+
const braceIdx = content.indexOf('{', groupIdx);
|
|
234
|
+
if (braceIdx === -1)
|
|
235
|
+
return null;
|
|
236
|
+
let depth = 1;
|
|
237
|
+
let i = braceIdx + 1;
|
|
238
|
+
while (i < content.length && depth > 0) {
|
|
239
|
+
const ch = content[i];
|
|
240
|
+
if (ch === '{')
|
|
241
|
+
depth++;
|
|
242
|
+
else if (ch === '}')
|
|
243
|
+
depth--;
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
return { body: content.slice(braceIdx + 1, i - 1), end: i };
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Extract route info from a single Route::method() chain.
|
|
250
|
+
*/
|
|
251
|
+
extractRoute(chain, inheritedMiddleware) {
|
|
252
|
+
const results = [];
|
|
253
|
+
// Extract inline middleware from ->middleware(...) in this chain
|
|
254
|
+
const inlineMiddleware = [];
|
|
255
|
+
const mwRegex = /->middleware\s*\(([^)]+)\)/g;
|
|
256
|
+
let mwMatch;
|
|
257
|
+
while ((mwMatch = mwRegex.exec(chain)) !== null) {
|
|
258
|
+
inlineMiddleware.push(...parseMiddlewareArg(mwMatch[1]));
|
|
259
|
+
}
|
|
260
|
+
const allMiddleware = [...inheritedMiddleware, ...inlineMiddleware];
|
|
261
|
+
// Route::resource('posts', PostController::class)
|
|
262
|
+
const resourceMatch = chain.match(/Route::resource\s*\(\s*['"]([^'"]+)['"]\s*,\s*([^,)]+)/);
|
|
263
|
+
if (resourceMatch) {
|
|
264
|
+
const uri = resourceMatch[1];
|
|
265
|
+
const ctrlRaw = resourceMatch[2].trim();
|
|
266
|
+
const parsed = parseControllerAction(ctrlRaw)
|
|
267
|
+
?? { controller: ctrlRaw.replace(/::class.*/, '').split('\\').pop() ?? ctrlRaw, action: '*' };
|
|
268
|
+
for (const action of RESOURCE_ACTIONS) {
|
|
269
|
+
results.push({ method: 'resource', uri, controller: parsed.controller, action, middleware: allMiddleware });
|
|
270
|
+
}
|
|
271
|
+
return results;
|
|
272
|
+
}
|
|
273
|
+
// Route::apiResource(...)
|
|
274
|
+
const apiResourceMatch = chain.match(/Route::apiResource\s*\(\s*['"]([^'"]+)['"]\s*,\s*([^,)]+)/);
|
|
275
|
+
if (apiResourceMatch) {
|
|
276
|
+
const uri = apiResourceMatch[1];
|
|
277
|
+
const ctrlRaw = apiResourceMatch[2].trim();
|
|
278
|
+
const parsed = parseControllerAction(ctrlRaw)
|
|
279
|
+
?? { controller: ctrlRaw.replace(/::class.*/, '').split('\\').pop() ?? ctrlRaw, action: '*' };
|
|
280
|
+
for (const action of ['index', 'store', 'show', 'update', 'destroy']) {
|
|
281
|
+
results.push({ method: 'apiResource', uri, controller: parsed.controller, action, middleware: allMiddleware });
|
|
282
|
+
}
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
// Route::get/post/put/patch/delete/any(...)
|
|
286
|
+
const verbMatch = chain.match(/Route::(get|post|put|patch|delete|any|options)\s*\(\s*['"]([^'"]+)['"]\s*,\s*([\s\S]+?)\s*\)/);
|
|
287
|
+
if (verbMatch) {
|
|
288
|
+
const method = verbMatch[1].toUpperCase();
|
|
289
|
+
const uri = verbMatch[2];
|
|
290
|
+
const actionStr = verbMatch[3];
|
|
291
|
+
const parsed = parseControllerAction(actionStr);
|
|
292
|
+
if (parsed) {
|
|
293
|
+
results.push({ method, uri, controller: parsed.controller, action: parsed.action, middleware: allMiddleware });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// ─── Route Map Helpers ────────────────────────────────────────────────────────
|
|
300
|
+
/**
|
|
301
|
+
* Build a lookup map: "ControllerName::action" → RouteInfo[]
|
|
302
|
+
* Used by analyzers for O(1) route lookups.
|
|
303
|
+
*/
|
|
304
|
+
export function buildRouteMap(routeFiles) {
|
|
305
|
+
const map = new Map();
|
|
306
|
+
for (const file of routeFiles) {
|
|
307
|
+
for (const route of file.routes) {
|
|
308
|
+
const key = `${route.controller}::${route.action}`;
|
|
309
|
+
if (!map.has(key))
|
|
310
|
+
map.set(key, []);
|
|
311
|
+
map.get(key).push(route);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return map;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get all middleware for a given controller action from the route map.
|
|
318
|
+
* Returns an empty array if the action is not found in any route.
|
|
319
|
+
*/
|
|
320
|
+
export function getRouteMiddleware(routeMap, controller, action) {
|
|
321
|
+
const key = `${controller}::${action}`;
|
|
322
|
+
const routes = routeMap.get(key) ?? [];
|
|
323
|
+
if (routes.length === 0)
|
|
324
|
+
return [];
|
|
325
|
+
return [...new Set(routes.flatMap((r) => r.middleware))];
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=RouteParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RouteParser.js","sourceRoot":"","sources":["../../src/parsers/RouteParser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HAVOC Rule Definitions
|
|
3
|
+
*
|
|
4
|
+
* Rules define patterns and thresholds for each analyzer.
|
|
5
|
+
* Full rule set implemented in Sprint 2.
|
|
6
|
+
*/
|
|
7
|
+
export interface Rule {
|
|
8
|
+
id: string;
|
|
9
|
+
analyzer: string;
|
|
10
|
+
description: string;
|
|
11
|
+
cwe?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const rules: Rule[];
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAGD,eAAO,MAAM,KAAK,EAAE,IAAI,EAAO,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/rules/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,wCAAwC;AACxC,MAAM,CAAC,MAAM,KAAK,GAAW,EAAE,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HAVOC Scanner — Core Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
export declare enum Severity {
|
|
5
|
+
Critical = "critical",
|
|
6
|
+
High = "high",
|
|
7
|
+
Medium = "medium",
|
|
8
|
+
Low = "low",
|
|
9
|
+
Info = "info"
|
|
10
|
+
}
|
|
11
|
+
export interface Finding {
|
|
12
|
+
/** Unique identifier for this finding (e.g., "HAVOC-001") */
|
|
13
|
+
id: string;
|
|
14
|
+
/** Severity level */
|
|
15
|
+
severity: Severity;
|
|
16
|
+
/** Name of the analyzer that produced this finding */
|
|
17
|
+
analyzer: string;
|
|
18
|
+
/** Short, descriptive title */
|
|
19
|
+
title: string;
|
|
20
|
+
/** Full description of the vulnerability */
|
|
21
|
+
description: string;
|
|
22
|
+
/** Relative file path where the issue was found */
|
|
23
|
+
file: string;
|
|
24
|
+
/** Line number (1-indexed) */
|
|
25
|
+
line: number;
|
|
26
|
+
/** Actionable recommendation for the developer */
|
|
27
|
+
recommendation: string;
|
|
28
|
+
/** CWE identifier (e.g., "CWE-89" for SQL Injection) */
|
|
29
|
+
cwe?: string;
|
|
30
|
+
/** Optional code snippet for context */
|
|
31
|
+
snippet?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ParsedFile {
|
|
34
|
+
/** Relative path to the file */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Raw file contents */
|
|
37
|
+
content: string;
|
|
38
|
+
/** AST representation (framework-specific) */
|
|
39
|
+
ast?: unknown;
|
|
40
|
+
}
|
|
41
|
+
export interface Analyzer {
|
|
42
|
+
/** Unique analyzer name (e.g., "authorization-coverage") */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Human-readable description of what this analyzer checks */
|
|
45
|
+
description: string;
|
|
46
|
+
/**
|
|
47
|
+
* Analyze the given parsed files and return any findings.
|
|
48
|
+
* @param files - Parsed PHP files to analyze
|
|
49
|
+
* @param config - Scanner configuration
|
|
50
|
+
*/
|
|
51
|
+
analyze(files: ParsedFile[], config: HavocConfig): Promise<Finding[]>;
|
|
52
|
+
}
|
|
53
|
+
export interface CoverageStats {
|
|
54
|
+
/** Total routes/endpoints found */
|
|
55
|
+
totalRoutes: number;
|
|
56
|
+
/** Routes with authorization middleware */
|
|
57
|
+
coveredRoutes: number;
|
|
58
|
+
/** Coverage percentage (0–100) */
|
|
59
|
+
coveragePercent: number;
|
|
60
|
+
/** Models scanned for mass assignment */
|
|
61
|
+
totalModels: number;
|
|
62
|
+
/** Models with $fillable or $guarded properly set */
|
|
63
|
+
protectedModels: number;
|
|
64
|
+
}
|
|
65
|
+
export interface ScanResult {
|
|
66
|
+
/** All findings from all analyzers */
|
|
67
|
+
findings: Finding[];
|
|
68
|
+
/** Coverage statistics */
|
|
69
|
+
coverage: CoverageStats;
|
|
70
|
+
/** Metadata about the scan */
|
|
71
|
+
metadata: ScanMetadata;
|
|
72
|
+
}
|
|
73
|
+
export interface ScanMetadata {
|
|
74
|
+
/** When the scan was performed */
|
|
75
|
+
scannedAt: string;
|
|
76
|
+
/** Framework detected (e.g., "laravel", "symfony") */
|
|
77
|
+
framework: string;
|
|
78
|
+
/** Framework version if detected */
|
|
79
|
+
frameworkVersion?: string;
|
|
80
|
+
/** Total files scanned */
|
|
81
|
+
filesScanned: number;
|
|
82
|
+
/** Duration in milliseconds */
|
|
83
|
+
durationMs: number;
|
|
84
|
+
/** HAVOC scanner version */
|
|
85
|
+
scannerVersion: string;
|
|
86
|
+
}
|
|
87
|
+
export interface HavocConfig {
|
|
88
|
+
/**
|
|
89
|
+
* Framework to use for analysis.
|
|
90
|
+
* @default "laravel"
|
|
91
|
+
*/
|
|
92
|
+
framework: 'laravel' | 'symfony' | 'auto';
|
|
93
|
+
/**
|
|
94
|
+
* Severity threshold for failing CI.
|
|
95
|
+
* Findings at or above this level will fail the build.
|
|
96
|
+
* @default "high"
|
|
97
|
+
*/
|
|
98
|
+
failOn: Severity;
|
|
99
|
+
/** List of file/directory patterns to exclude */
|
|
100
|
+
exclude?: string[];
|
|
101
|
+
/** Analyzer-specific configuration */
|
|
102
|
+
analyzers?: AnalyzerConfig;
|
|
103
|
+
/** Baseline file path for suppressing known findings */
|
|
104
|
+
baseline?: string;
|
|
105
|
+
/**
|
|
106
|
+
* Output format.
|
|
107
|
+
* @default "text"
|
|
108
|
+
*/
|
|
109
|
+
output?: 'text' | 'json' | 'sarif' | 'github';
|
|
110
|
+
}
|
|
111
|
+
export interface AnalyzerConfig {
|
|
112
|
+
authorizationCoverage?: {
|
|
113
|
+
enabled: boolean;
|
|
114
|
+
/** Minimum acceptable coverage percent */
|
|
115
|
+
minCoverage?: number;
|
|
116
|
+
/** Route prefixes to exclude (e.g., ["api/public"]) */
|
|
117
|
+
excludePrefixes?: string[];
|
|
118
|
+
};
|
|
119
|
+
massAssignment?: {
|
|
120
|
+
enabled: boolean;
|
|
121
|
+
};
|
|
122
|
+
xssSurface?: {
|
|
123
|
+
enabled: boolean;
|
|
124
|
+
};
|
|
125
|
+
sqlInjection?: {
|
|
126
|
+
enabled: boolean;
|
|
127
|
+
};
|
|
128
|
+
dependencyAudit?: {
|
|
129
|
+
enabled: boolean;
|
|
130
|
+
/** Max allowed days since last security update */
|
|
131
|
+
maxAgeDays?: number;
|
|
132
|
+
};
|
|
133
|
+
idor?: {
|
|
134
|
+
enabled: boolean;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HAVOC Scanner — Core Type Definitions
|
|
3
|
+
*/
|
|
4
|
+
// ─── Severity ────────────────────────────────────────────────────────────────
|
|
5
|
+
export var Severity;
|
|
6
|
+
(function (Severity) {
|
|
7
|
+
Severity["Critical"] = "critical";
|
|
8
|
+
Severity["High"] = "high";
|
|
9
|
+
Severity["Medium"] = "medium";
|
|
10
|
+
Severity["Low"] = "low";
|
|
11
|
+
Severity["Info"] = "info";
|
|
12
|
+
})(Severity || (Severity = {}));
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,gFAAgF;AAEhF,MAAM,CAAN,IAAY,QAMX;AAND,WAAY,QAAQ;IAClB,iCAAqB,CAAA;IACrB,yBAAa,CAAA;IACb,6BAAiB,CAAA;IACjB,uBAAW,CAAA;IACX,yBAAa,CAAA;AACf,CAAC,EANW,QAAQ,KAAR,QAAQ,QAMnB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@havoc-security/scanner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HAVOC core scanning engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"lint": "eslint src tests"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^25.3.3",
|
|
21
|
+
"vitest": "^2.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"glob": "^13.0.6",
|
|
25
|
+
"php-parser": "^3.4.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json.bak
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@havoc/scanner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "HAVOC core scanning engine",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"lint": "eslint src tests"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^25.3.3",
|
|
21
|
+
"vitest": "^2.0.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"glob": "^13.0.6",
|
|
25
|
+
"php-parser": "^3.4.0"
|
|
26
|
+
}
|
|
27
|
+
}
|