jaku.sh 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +636 -0
  3. package/action.yml +264 -0
  4. package/bin/jaku +2 -0
  5. package/package.json +62 -0
  6. package/src/agents/ai-agent.js +175 -0
  7. package/src/agents/api-agent.js +95 -0
  8. package/src/agents/base-agent.js +158 -0
  9. package/src/agents/crawl-agent.js +175 -0
  10. package/src/agents/event-bus.js +59 -0
  11. package/src/agents/findings-ledger.js +410 -0
  12. package/src/agents/logic-agent.js +144 -0
  13. package/src/agents/orchestrator.js +323 -0
  14. package/src/agents/qa-agent.js +149 -0
  15. package/src/agents/security-agent.js +211 -0
  16. package/src/cli.js +423 -0
  17. package/src/core/accessibility-checker.js +171 -0
  18. package/src/core/ai/ai-endpoint-detector.js +227 -0
  19. package/src/core/ai/guardrail-prober.js +362 -0
  20. package/src/core/ai/indirect-injector.js +106 -0
  21. package/src/core/ai/jailbreak-tester.js +212 -0
  22. package/src/core/ai/model-dos-tester.js +174 -0
  23. package/src/core/ai/model-fingerprinter.js +246 -0
  24. package/src/core/ai/multi-turn-attacker.js +297 -0
  25. package/src/core/ai/output-analyzer.js +182 -0
  26. package/src/core/ai/prompt-injector.js +543 -0
  27. package/src/core/ai/system-prompt-extractor.js +244 -0
  28. package/src/core/api/api-key-auditor.js +266 -0
  29. package/src/core/api/auth-flow-tester.js +430 -0
  30. package/src/core/api/cors-ws-tester.js +263 -0
  31. package/src/core/api/graphql-tester.js +287 -0
  32. package/src/core/api/oauth-prober.js +343 -0
  33. package/src/core/auth-manager.js +902 -0
  34. package/src/core/broken-flow-detector.js +207 -0
  35. package/src/core/browser-manager.js +119 -0
  36. package/src/core/console-monitor.js +111 -0
  37. package/src/core/crawler.js +430 -0
  38. package/src/core/csr-waiter.js +410 -0
  39. package/src/core/form-validator.js +240 -0
  40. package/src/core/logic/abuse-pattern-scanner.js +291 -0
  41. package/src/core/logic/access-boundary-tester.js +448 -0
  42. package/src/core/logic/business-rule-inferrer.js +196 -0
  43. package/src/core/logic/graphql-auditor.js +298 -0
  44. package/src/core/logic/parameter-polluter.js +212 -0
  45. package/src/core/logic/pricing-exploiter.js +299 -0
  46. package/src/core/logic/race-condition-detector.js +222 -0
  47. package/src/core/logic/workflow-enforcer.js +284 -0
  48. package/src/core/performance-checker.js +204 -0
  49. package/src/core/responsive-checker.js +228 -0
  50. package/src/core/security/cors-prober.js +150 -0
  51. package/src/core/security/csrf-prober.js +217 -0
  52. package/src/core/security/dependency-auditor.js +182 -0
  53. package/src/core/security/file-upload-tester.js +340 -0
  54. package/src/core/security/header-analyzer.js +324 -0
  55. package/src/core/security/infra-scanner.js +391 -0
  56. package/src/core/security/path-traversal.js +112 -0
  57. package/src/core/security/prototype-pollution.js +147 -0
  58. package/src/core/security/secret-detector.js +517 -0
  59. package/src/core/security/sqli-prober.js +257 -0
  60. package/src/core/security/tls-checker.js +223 -0
  61. package/src/core/security/xss-scanner.js +225 -0
  62. package/src/core/test-generator.js +339 -0
  63. package/src/core/test-runner.js +398 -0
  64. package/src/reporting/diff-reporter.js +172 -0
  65. package/src/reporting/report-generator.js +408 -0
  66. package/src/reporting/sarif-generator.js +190 -0
  67. package/src/utils/config.js +57 -0
  68. package/src/utils/finding.js +67 -0
  69. package/src/utils/logger.js +50 -0
@@ -0,0 +1,324 @@
1
+ import { createFinding } from '../../utils/finding.js';
2
+
3
+ /**
4
+ * Header Analyzer — Checks HTTP response headers against security best practices.
5
+ * Tests CSP, HSTS, X-Frame-Options, X-Content-Type-Options, CORS, and information disclosure.
6
+ */
7
+ export class HeaderAnalyzer {
8
+ constructor(logger) {
9
+ this.logger = logger;
10
+ this.findings = [];
11
+ }
12
+
13
+ /**
14
+ * Analyze headers from all crawled pages.
15
+ */
16
+ async analyze(surfaceInventory) {
17
+ const analyzedOrigins = new Set();
18
+
19
+ for (const page of surfaceInventory.pages) {
20
+ if (typeof page.status !== 'number') continue;
21
+
22
+ // Only analyze headers once per origin
23
+ const origin = this._getOrigin(page.url);
24
+ if (analyzedOrigins.has(origin)) continue;
25
+ analyzedOrigins.add(origin);
26
+
27
+ try {
28
+ const headers = await this._fetchHeaders(page.url);
29
+ if (headers) {
30
+ this._checkCSP(headers, page.url);
31
+ this._checkHSTS(headers, page.url);
32
+ this._checkXFrameOptions(headers, page.url);
33
+ this._checkXContentTypeOptions(headers, page.url);
34
+ this._checkReferrerPolicy(headers, page.url);
35
+ this._checkPermissionsPolicy(headers, page.url);
36
+ this._checkCORS(headers, page.url);
37
+ this._checkInfoDisclosure(headers, page.url);
38
+ }
39
+ } catch (err) {
40
+ this.logger?.debug?.(`Header analysis failed for ${page.url}: ${err.message}`);
41
+ }
42
+ }
43
+
44
+ this.logger?.info?.(`Header analyzer found ${this.findings.length} issues`);
45
+ return this.findings;
46
+ }
47
+
48
+ async _fetchHeaders(url) {
49
+ try {
50
+ const response = await fetch(url, {
51
+ method: 'HEAD',
52
+ redirect: 'follow',
53
+ signal: AbortSignal.timeout(10000),
54
+ });
55
+ const headers = {};
56
+ response.headers.forEach((value, key) => {
57
+ headers[key.toLowerCase()] = value;
58
+ });
59
+ return headers;
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ _checkCSP(headers, url) {
66
+ const csp = headers['content-security-policy'];
67
+
68
+ if (!csp) {
69
+ this.findings.push(createFinding({
70
+ module: 'security',
71
+ title: 'Missing Content-Security-Policy Header',
72
+ severity: 'medium',
73
+ affected_surface: url,
74
+ description: 'No Content-Security-Policy (CSP) header is set. CSP is a critical defense against XSS attacks, clickjacking, and other code injection attacks. Without it, the browser allows all sources of content.',
75
+ reproduction: [
76
+ `1. Send a HEAD request to ${url}`,
77
+ '2. Observe the response headers',
78
+ '3. No Content-Security-Policy header is present',
79
+ ],
80
+ remediation: "Add a Content-Security-Policy header. Start with a restrictive policy like `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self';` and refine as needed.",
81
+ references: ['https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP'],
82
+ }));
83
+ return;
84
+ }
85
+
86
+ if (csp.includes("'unsafe-inline'") && csp.includes('script-src')) {
87
+ this.findings.push(createFinding({
88
+ module: 'security',
89
+ title: "CSP allows 'unsafe-inline' Scripts",
90
+ severity: 'medium',
91
+ affected_surface: url,
92
+ description: `The Content-Security-Policy allows 'unsafe-inline' in the script-src directive. This significantly weakens XSS protection as inline scripts can still be injected.\n\nCurrent CSP: ${csp}`,
93
+ reproduction: [
94
+ `1. Check CSP header at ${url}`,
95
+ `2. CSP contains: ${csp}`,
96
+ "3. Note 'unsafe-inline' in script-src",
97
+ ],
98
+ remediation: "Remove 'unsafe-inline' from script-src. Use nonces or hashes for legitimate inline scripts instead.",
99
+ references: ['https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src'],
100
+ }));
101
+ }
102
+
103
+ if (csp.includes("'unsafe-eval'")) {
104
+ this.findings.push(createFinding({
105
+ module: 'security',
106
+ title: "CSP allows 'unsafe-eval'",
107
+ severity: 'medium',
108
+ affected_surface: url,
109
+ description: `The Content-Security-Policy allows 'unsafe-eval', which permits eval() and similar dynamic code execution. This is a common XSS exploitation vector.\n\nCurrent CSP: ${csp}`,
110
+ reproduction: [
111
+ `1. Check CSP header at ${url}`,
112
+ "2. Note 'unsafe-eval' directive present",
113
+ ],
114
+ remediation: "Remove 'unsafe-eval' from CSP. Refactor code to not rely on eval(), new Function(), or setTimeout/setInterval with string arguments.",
115
+ }));
116
+ }
117
+ }
118
+
119
+ _checkHSTS(headers, url) {
120
+ if (!url.startsWith('https://')) return;
121
+
122
+ const hsts = headers['strict-transport-security'];
123
+
124
+ if (!hsts) {
125
+ this.findings.push(createFinding({
126
+ module: 'security',
127
+ title: 'Missing Strict-Transport-Security Header',
128
+ severity: 'medium',
129
+ affected_surface: url,
130
+ description: 'No HSTS header is set for this HTTPS site. Without HSTS, users could be subject to protocol downgrade attacks and cookie hijacking via MITM.',
131
+ reproduction: [
132
+ `1. Check response headers for ${url}`,
133
+ '2. No Strict-Transport-Security header present',
134
+ ],
135
+ remediation: 'Add `Strict-Transport-Security: max-age=31536000; includeSubDomains; preload` to all HTTPS responses.',
136
+ references: ['https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security'],
137
+ }));
138
+ return;
139
+ }
140
+
141
+ const maxAgeMatch = hsts.match(/max-age=(\d+)/);
142
+ if (maxAgeMatch && parseInt(maxAgeMatch[1]) < 15768000) {
143
+ this.findings.push(createFinding({
144
+ module: 'security',
145
+ title: 'HSTS max-age Too Low',
146
+ severity: 'low',
147
+ affected_surface: url,
148
+ description: `HSTS max-age is set to ${maxAgeMatch[1]} seconds (${(parseInt(maxAgeMatch[1]) / 86400).toFixed(0)} days). This should be at least 6 months (15768000 seconds) for adequate protection.`,
149
+ reproduction: [
150
+ `1. HSTS header: ${hsts}`,
151
+ `2. max-age=${maxAgeMatch[1]} is below the recommended minimum`,
152
+ ],
153
+ remediation: 'Increase HSTS max-age to at least 15768000 (6 months), ideally 31536000 (1 year).',
154
+ }));
155
+ }
156
+ }
157
+
158
+ _checkXFrameOptions(headers, url) {
159
+ const xfo = headers['x-frame-options'];
160
+ if (!xfo) {
161
+ this.findings.push(createFinding({
162
+ module: 'security',
163
+ title: 'Missing X-Frame-Options Header',
164
+ severity: 'low',
165
+ affected_surface: url,
166
+ description: 'No X-Frame-Options header is set. The site may be vulnerable to clickjacking attacks where it is embedded in a malicious iframe.',
167
+ reproduction: [
168
+ `1. Check headers for ${url}`,
169
+ '2. X-Frame-Options header is missing',
170
+ '3. Page can be embedded in an iframe on any domain',
171
+ ],
172
+ remediation: "Add `X-Frame-Options: DENY` or `X-Frame-Options: SAMEORIGIN`. Alternatively, use CSP's frame-ancestors directive.",
173
+ references: ['https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options'],
174
+ }));
175
+ }
176
+ }
177
+
178
+ _checkXContentTypeOptions(headers, url) {
179
+ const xcto = headers['x-content-type-options'];
180
+ if (!xcto || xcto.toLowerCase() !== 'nosniff') {
181
+ this.findings.push(createFinding({
182
+ module: 'security',
183
+ title: 'Missing X-Content-Type-Options: nosniff',
184
+ severity: 'low',
185
+ affected_surface: url,
186
+ description: 'The X-Content-Type-Options header is missing or not set to "nosniff". Browsers may MIME-sniff the content type, potentially executing malicious content uploaded with a non-executable MIME type.',
187
+ reproduction: [
188
+ `1. Check headers for ${url}`,
189
+ '2. X-Content-Type-Options is missing or not "nosniff"',
190
+ ],
191
+ remediation: 'Add `X-Content-Type-Options: nosniff` to all responses.',
192
+ }));
193
+ }
194
+ }
195
+
196
+ _checkReferrerPolicy(headers, url) {
197
+ const rp = headers['referrer-policy'];
198
+ if (!rp) {
199
+ this.findings.push(createFinding({
200
+ module: 'security',
201
+ title: 'Missing Referrer-Policy Header',
202
+ severity: 'low',
203
+ affected_surface: url,
204
+ description: 'No Referrer-Policy header is set. The browser will use its default policy which may leak sensitive URL paths and query parameters to third parties.',
205
+ reproduction: [
206
+ `1. Check headers for ${url}`,
207
+ '2. Referrer-Policy header is missing',
208
+ ],
209
+ remediation: 'Add `Referrer-Policy: strict-origin-when-cross-origin` or `no-referrer` to all responses.',
210
+ }));
211
+ } else if (rp.toLowerCase() === 'unsafe-url') {
212
+ this.findings.push(createFinding({
213
+ module: 'security',
214
+ title: 'Referrer-Policy Set to unsafe-url',
215
+ severity: 'medium',
216
+ affected_surface: url,
217
+ description: 'Referrer-Policy is set to "unsafe-url" which sends the full URL (including path and query string) in the Referer header to all origins, potentially leaking sensitive data.',
218
+ reproduction: [
219
+ `1. Referrer-Policy: ${rp}`,
220
+ '2. Full URL including query params leaked to third parties',
221
+ ],
222
+ remediation: 'Change Referrer-Policy to "strict-origin-when-cross-origin" or "no-referrer".',
223
+ }));
224
+ }
225
+ }
226
+
227
+ _checkPermissionsPolicy(headers, url) {
228
+ const pp = headers['permissions-policy'] || headers['feature-policy'];
229
+ if (!pp) {
230
+ this.findings.push(createFinding({
231
+ module: 'security',
232
+ title: 'Missing Permissions-Policy Header',
233
+ severity: 'info',
234
+ affected_surface: url,
235
+ description: 'No Permissions-Policy (formerly Feature-Policy) header is set. This header controls which browser features and APIs can be used, reducing the attack surface.',
236
+ reproduction: [
237
+ `1. Check headers for ${url}`,
238
+ '2. Permissions-Policy header is missing',
239
+ ],
240
+ remediation: 'Add a Permissions-Policy header to restrict unused browser features. Example: `Permissions-Policy: camera=(), microphone=(), geolocation=()`.',
241
+ }));
242
+ }
243
+ }
244
+
245
+ _checkCORS(headers, url) {
246
+ const acao = headers['access-control-allow-origin'];
247
+ const acac = headers['access-control-allow-credentials'];
248
+
249
+ if (acao === '*' && acac?.toLowerCase() === 'true') {
250
+ this.findings.push(createFinding({
251
+ module: 'security',
252
+ title: 'CORS: Wildcard Origin with Credentials',
253
+ severity: 'high',
254
+ affected_surface: url,
255
+ description: 'The server sets Access-Control-Allow-Origin to "*" with Access-Control-Allow-Credentials: true. This is a dangerous misconfiguration that allows any website to make authenticated cross-origin requests, potentially stealing user data.',
256
+ reproduction: [
257
+ `1. Send request to ${url} with Origin: https://evil.com`,
258
+ '2. Response contains: Access-Control-Allow-Origin: *',
259
+ '3. Response also contains: Access-Control-Allow-Credentials: true',
260
+ ],
261
+ remediation: 'Never use wildcard (*) with credentials. Whitelist specific trusted origins and validate the Origin header dynamically.',
262
+ references: ['https://portswigger.net/web-security/cors'],
263
+ }));
264
+ } else if (acao === '*') {
265
+ this.findings.push(createFinding({
266
+ module: 'security',
267
+ title: 'CORS: Wildcard Allow-Origin',
268
+ severity: 'low',
269
+ affected_surface: url,
270
+ description: 'Access-Control-Allow-Origin is set to "*", allowing any website to read responses. While not directly exploitable without credentials, it may expose public API data to unintended consumers.',
271
+ reproduction: [
272
+ `1. Check CORS headers for ${url}`,
273
+ '2. Access-Control-Allow-Origin: * (allows all origins)',
274
+ ],
275
+ remediation: 'If the API is not intended to be fully public, restrict CORS to specific trusted origins.',
276
+ }));
277
+ }
278
+ }
279
+
280
+ _checkInfoDisclosure(headers, url) {
281
+ const server = headers['server'];
282
+ const poweredBy = headers['x-powered-by'];
283
+
284
+ if (server && /\d/.test(server)) {
285
+ this.findings.push(createFinding({
286
+ module: 'security',
287
+ title: `Server Version Disclosure: ${server}`,
288
+ severity: 'low',
289
+ affected_surface: url,
290
+ description: `The Server header discloses technology and version information: "${server}". Attackers can use this to search for known vulnerabilities specific to this version.`,
291
+ reproduction: [
292
+ `1. Check Server header for ${url}`,
293
+ `2. Server: ${server}`,
294
+ ],
295
+ remediation: 'Remove or obfuscate the Server header. Most web servers have configuration options to suppress version information.',
296
+ }));
297
+ }
298
+
299
+ if (poweredBy) {
300
+ this.findings.push(createFinding({
301
+ module: 'security',
302
+ title: `X-Powered-By Disclosure: ${poweredBy}`,
303
+ severity: 'low',
304
+ affected_surface: url,
305
+ description: `The X-Powered-By header reveals the backend technology: "${poweredBy}". This aids attackers in fingerprinting and targeting technology-specific exploits.`,
306
+ reproduction: [
307
+ `1. Check X-Powered-By header for ${url}`,
308
+ `2. X-Powered-By: ${poweredBy}`,
309
+ ],
310
+ remediation: 'Remove the X-Powered-By header. In Express.js: `app.disable("x-powered-by")`. In PHP: set `expose_php = Off` in php.ini.',
311
+ }));
312
+ }
313
+ }
314
+
315
+ _getOrigin(url) {
316
+ try {
317
+ return new URL(url).origin;
318
+ } catch {
319
+ return url;
320
+ }
321
+ }
322
+ }
323
+
324
+ export default HeaderAnalyzer;