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,323 @@
1
+ import { EventBus } from './event-bus.js';
2
+ import { FindingsLedger } from './findings-ledger.js';
3
+ import { BrowserManager } from '../core/browser-manager.js';
4
+
5
+ /**
6
+ * Orchestrator — Central coordinator for the JAKU multi-agent system.
7
+ *
8
+ * Responsibilities:
9
+ * 1. Register and manage agent lifecycle
10
+ * 2. Resolve dependencies via topological sort
11
+ * 3. Execute independent agents in parallel
12
+ * 4. Provide shared context (config, logger, event bus, surface inventory)
13
+ * 5. Synthesize final results (dedup, correlate, score)
14
+ */
15
+ export class Orchestrator {
16
+ constructor(config, logger) {
17
+ this.config = config;
18
+ this.logger = logger;
19
+ this.eventBus = new EventBus();
20
+ this.ledger = new FindingsLedger(this.eventBus);
21
+
22
+ this._agents = new Map(); // name → agent instance
23
+ this._sharedContext = { // passed to all agents
24
+ config,
25
+ logger,
26
+ eventBus: this.eventBus,
27
+ ledger: this.ledger,
28
+ surfaceInventory: null, // set by JAKU-CRAWL
29
+ };
30
+
31
+ this._startTime = null;
32
+ this._haltedOnCritical = false;
33
+ }
34
+
35
+ /**
36
+ * Register an agent with the orchestrator.
37
+ */
38
+ register(agent) {
39
+ if (this._agents.has(agent.name)) {
40
+ this.logger?.warn?.(`Agent "${agent.name}" already registered, skipping`);
41
+ return this;
42
+ }
43
+ this._agents.set(agent.name, agent);
44
+ this.logger?.info?.(`Registered agent: ${agent.name}`);
45
+ return this; // chainable
46
+ }
47
+
48
+ /**
49
+ * Subscribe to orchestrator events.
50
+ */
51
+ on(event, handler) {
52
+ this.eventBus.on(event, handler);
53
+ return this;
54
+ }
55
+
56
+ /**
57
+ * Run all registered agents with dependency resolution and parallel execution.
58
+ */
59
+ async run() {
60
+ this._startTime = Date.now();
61
+ const executionOrder = this._resolveExecutionOrder();
62
+
63
+ this.eventBus.emit('scan:started', {
64
+ timestamp: new Date().toISOString(),
65
+ modules: [...this._agents.keys()],
66
+ });
67
+
68
+ this.logger?.info?.(`Orchestrator starting with ${this._agents.size} agents`);
69
+ this.logger?.info?.(`Execution order: ${executionOrder.map(g => g.map(a => a.name).join(' + ')).join(' → ')}`);
70
+
71
+ // Initialize all agents
72
+ for (const [, agent] of this._agents) {
73
+ await agent.init(this._sharedContext);
74
+ }
75
+
76
+ // Listen for critical findings if halt_on_critical is enabled
77
+ if (this.config.halt_on_critical) {
78
+ this.eventBus.on('finding:new', ({ finding }) => {
79
+ if (finding?.severity === 'critical') {
80
+ this._haltedOnCritical = true;
81
+ this.logger?.warn?.(`\n⛔ CRITICAL finding detected — halting scan (halt_on_critical=true)`);
82
+ this.logger?.warn?.(` ${finding.title}`);
83
+ }
84
+ });
85
+ }
86
+
87
+ // Execute in dependency-resolved waves
88
+ for (const wave of executionOrder) {
89
+ // Fix 4: Session heartbeat — re-authenticate if session expired before each wave
90
+ await this._checkSessionHealth();
91
+
92
+ if (wave.length === 1) {
93
+ // Single agent — run sequentially
94
+ await this._runAgent(wave[0]);
95
+ } else {
96
+ // Multiple agents — run in parallel
97
+ this.logger?.info?.(`Running ${wave.length} agents in parallel: ${wave.map(a => a.name).join(', ')}`);
98
+ const results = await Promise.allSettled(
99
+ wave.map(agent => this._runAgent(agent))
100
+ );
101
+
102
+ // Log any failures
103
+ for (let i = 0; i < results.length; i++) {
104
+ if (results[i].status === 'rejected') {
105
+ this.logger?.error?.(`Agent ${wave[i].name} failed: ${results[i].reason?.message}`);
106
+ }
107
+ }
108
+ }
109
+
110
+ // Check halt_on_critical after each wave
111
+ if (this._haltedOnCritical) {
112
+ this.logger?.warn?.('Scan halted after critical finding. Proceeding to synthesis.');
113
+ break;
114
+ }
115
+ }
116
+
117
+ // Cleanup all agents
118
+ for (const [, agent] of this._agents) {
119
+ try {
120
+ await agent.cleanup();
121
+ } catch (err) {
122
+ this.logger?.debug?.(`Cleanup for ${agent.name}: ${err.message}`);
123
+ }
124
+ }
125
+
126
+ // Fix 2: Ensure all browsers are closed via BrowserManager
127
+ await BrowserManager.closeAll().catch(() => { });
128
+
129
+ // Synthesis phase
130
+ const duration = Date.now() - this._startTime;
131
+ const results = this._synthesize(duration);
132
+
133
+ this.eventBus.emit('scan:completed', {
134
+ timestamp: new Date().toISOString(),
135
+ duration,
136
+ totalFindings: this.ledger.count,
137
+ haltedOnCritical: this._haltedOnCritical,
138
+ });
139
+
140
+ // Send webhook notification if configured
141
+ if (this.config.notify_webhook) {
142
+ await this._sendWebhook(results);
143
+ }
144
+
145
+ return results;
146
+ }
147
+
148
+ /**
149
+ * Run a single agent with error boundary.
150
+ */
151
+ async _runAgent(agent) {
152
+ try {
153
+ await agent.run();
154
+
155
+ // If this is the crawl agent, store surface inventory for downstream agents
156
+ if (agent.name === 'JAKU-CRAWL' && this._sharedContext.surfaceInventory === null) {
157
+ // The crawl agent should have set this
158
+ this.logger?.debug?.('Surface inventory should be set by JAKU-CRAWL');
159
+ }
160
+ } catch (error) {
161
+ // Agent errors are non-fatal — other agents continue
162
+ this.logger?.error?.(`Agent ${agent.name} failed: ${error.message}`);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Resolve agents into execution waves based on dependencies.
168
+ * Uses topological sort — agents in the same wave have no inter-dependencies.
169
+ *
170
+ * Example:
171
+ * JAKU-CRAWL (no deps) → Wave 1
172
+ * JAKU-QA (dep: CRAWL) → Wave 2
173
+ * JAKU-SEC (dep: CRAWL) → Wave 2 (parallel with QA)
174
+ */
175
+ _resolveExecutionOrder() {
176
+ const waves = [];
177
+ const completed = new Set();
178
+ const remaining = new Map(this._agents);
179
+
180
+ let iterations = 0;
181
+ const maxIterations = remaining.size + 1;
182
+
183
+ while (remaining.size > 0 && iterations < maxIterations) {
184
+ iterations++;
185
+ const currentWave = [];
186
+
187
+ for (const [name, agent] of remaining) {
188
+ const depsResolved = agent.dependencies.every(dep => completed.has(dep));
189
+ if (depsResolved) {
190
+ currentWave.push(agent);
191
+ }
192
+ }
193
+
194
+ if (currentWave.length === 0) {
195
+ const stuck = [...remaining.keys()].join(', ');
196
+ throw new Error(`Circular dependency detected among agents: ${stuck}`);
197
+ }
198
+
199
+ for (const agent of currentWave) {
200
+ remaining.delete(agent.name);
201
+ completed.add(agent.name);
202
+ }
203
+
204
+ waves.push(currentWave);
205
+ }
206
+
207
+ return waves;
208
+ }
209
+
210
+ /**
211
+ * Synthesis phase — deduplicate, correlate, and build final report data.
212
+ */
213
+ _synthesize(duration) {
214
+ const exported = this.ledger.export();
215
+
216
+ // Build per-agent summary
217
+ const agentSummaries = {};
218
+ for (const [name, agent] of this._agents) {
219
+ agentSummaries[name] = {
220
+ status: agent.status,
221
+ duration: agent.duration,
222
+ findingsCount: agent.findings.length,
223
+ };
224
+ }
225
+
226
+ return {
227
+ findings: exported.findings,
228
+ deduplicated: exported.deduplicated,
229
+ summary: exported.summary,
230
+ dedupSummary: exported.dedupSummary,
231
+ dedupStats: exported.dedupStats,
232
+ correlations: exported.correlations,
233
+ agents: agentSummaries,
234
+ surfaceInventory: this._sharedContext.surfaceInventory,
235
+ duration,
236
+ eventLog: this.eventBus.getLog(),
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Get the status of all agents.
242
+ */
243
+ getStatus() {
244
+ const status = {};
245
+ for (const [name, agent] of this._agents) {
246
+ status[name] = {
247
+ status: agent.status,
248
+ duration: agent.duration,
249
+ findings: agent.findings.length,
250
+ };
251
+ }
252
+ return status;
253
+ }
254
+
255
+ /**
256
+ * Send scan results to configured webhook (Slack, Linear, PagerDuty, etc.)
257
+ */
258
+ async _sendWebhook(results) {
259
+ const webhookUrl = this.config.notify_webhook;
260
+ if (!webhookUrl) return;
261
+
262
+ try {
263
+ const payload = {
264
+ agent: 'JAKU',
265
+ version: '1.0.0',
266
+ target: this.config.target_url,
267
+ timestamp: new Date().toISOString(),
268
+ duration: results.duration,
269
+ totalFindings: results.findings?.length || 0,
270
+ summary: results.summary,
271
+ criticalCount: results.summary?.critical || 0,
272
+ highCount: results.summary?.high || 0,
273
+ haltedOnCritical: this._haltedOnCritical,
274
+ };
275
+
276
+ await fetch(webhookUrl, {
277
+ method: 'POST',
278
+ headers: { 'Content-Type': 'application/json' },
279
+ body: JSON.stringify(payload),
280
+ signal: AbortSignal.timeout(10000),
281
+ });
282
+
283
+ this.logger?.info?.(`Webhook notification sent to ${webhookUrl}`);
284
+ } catch (err) {
285
+ this.logger?.warn?.(`Webhook notification failed: ${err.message}`);
286
+ }
287
+ }
288
+ /**
289
+ * Fix 4: Session heartbeat — check if auth session is still valid.
290
+ * If the target returns 401/403, attempt re-authentication.
291
+ */
292
+ async _checkSessionHealth() {
293
+ const authManager = this.config._authManager;
294
+ if (!authManager?.isAuthenticated) return; // No auth, nothing to check
295
+
296
+ const targetUrl = this.config.target_url;
297
+
298
+ try {
299
+ const resp = await fetch(targetUrl, {
300
+ method: 'HEAD',
301
+ signal: AbortSignal.timeout(5000),
302
+ redirect: 'follow',
303
+ });
304
+
305
+ if (resp.status === 401 || resp.status === 403) {
306
+ this.logger?.warn?.('⚠ Session appears expired — re-authenticating...');
307
+ try {
308
+ await authManager.authenticate();
309
+ // Update the shared context with refreshed auth state
310
+ this._sharedContext.config._authManager = authManager;
311
+ this.logger?.info?.('✔ Re-authentication successful');
312
+ } catch (err) {
313
+ this.logger?.error?.(`Re-authentication failed: ${err.message}`);
314
+ }
315
+ }
316
+ } catch {
317
+ // Network error — skip heartbeat silently
318
+ }
319
+ }
320
+
321
+ }
322
+
323
+ export default Orchestrator;
@@ -0,0 +1,149 @@
1
+ import { BaseAgent } from './base-agent.js';
2
+ import { BrokenFlowDetector } from '../core/broken-flow-detector.js';
3
+ import { ConsoleMonitor } from '../core/console-monitor.js';
4
+ import { TestGenerator } from '../core/test-generator.js';
5
+ import { TestRunner } from '../core/test-runner.js';
6
+ import { FormValidator } from '../core/form-validator.js';
7
+ import { ResponsiveChecker } from '../core/responsive-checker.js';
8
+ import { PerformanceChecker } from '../core/performance-checker.js';
9
+ import { AccessibilityChecker } from '../core/accessibility-checker.js';
10
+
11
+ /**
12
+ * JAKU-QA — Quality Assurance & Functional Testing Agent
13
+ *
14
+ * Pipeline (7 phases):
15
+ * 1. Broken Flow Detection — 404s, redirect loops, missing auth redirects
16
+ * 2. Console Monitoring — JS errors, unhandled promises, network failures
17
+ * 3. Test Generation & Run — Smoke tests per discovered page with deduplication
18
+ * 4. Form Validation — Required fields, type checking, error messaging
19
+ * 5. Responsive Checking — Mobile/tablet/desktop layout issues
20
+ * 6. Performance Checking — Core Web Vitals: LCP, FCP, TTFB, TBT, CLS
21
+ * 7. Accessibility Checking — WCAG 2.2 via axe-core (real-browser injection)
22
+ *
23
+ * Dependencies: JAKU-CRAWL
24
+ */
25
+ export class QAAgent extends BaseAgent {
26
+ get name() { return 'JAKU-QA'; }
27
+ get dependencies() { return ['JAKU-CRAWL']; }
28
+
29
+ constructor() {
30
+ super();
31
+ this._testSummary = {};
32
+ }
33
+
34
+ get testSummary() { return this._testSummary; }
35
+
36
+ async _execute(context) {
37
+ const { config, logger, surfaceInventory } = context;
38
+
39
+ if (!surfaceInventory) {
40
+ throw new Error('No surface inventory available — JAKU-CRAWL must run first');
41
+ }
42
+
43
+ const phases = [
44
+ { name: 'broken-flows', label: 'Detecting broken flows' },
45
+ { name: 'console', label: 'Analyzing console output' },
46
+ { name: 'tests', label: 'Generating & running tests' },
47
+ { name: 'forms', label: 'Validating forms' },
48
+ { name: 'responsive', label: 'Checking responsiveness' },
49
+ { name: 'performance', label: 'Measuring Core Web Vitals' },
50
+ { name: 'accessibility', label: 'Checking WCAG 2.2 accessibility' },
51
+ ];
52
+
53
+ let completedPhases = 0;
54
+
55
+ // Phase 1: Broken Flow Detection
56
+ this.progress(phases[0].name, phases[0].label, 0);
57
+ try {
58
+ const detector = new BrokenFlowDetector(logger);
59
+ const findings = detector.analyze(surfaceInventory);
60
+ this.addFindings(findings);
61
+ this._log(`Broken flows: ${findings.length} issues`);
62
+ } catch (err) {
63
+ this._log(`Broken flow detection failed: ${err.message}`, 'error');
64
+ }
65
+ completedPhases++;
66
+ this.progress(phases[0].name, `Broken flows complete`, (completedPhases / phases.length) * 100);
67
+
68
+ // Phase 2: Console Monitoring
69
+ this.progress(phases[1].name, phases[1].label, (completedPhases / phases.length) * 100);
70
+ try {
71
+ const monitor = new ConsoleMonitor(logger);
72
+ const findings = monitor.analyze(surfaceInventory);
73
+ this.addFindings(findings);
74
+ this._log(`Console: ${findings.length} issues`);
75
+ } catch (err) {
76
+ this._log(`Console analysis failed: ${err.message}`, 'error');
77
+ }
78
+ completedPhases++;
79
+
80
+ // Phase 3: Test Generation & Execution
81
+ this.progress(phases[2].name, phases[2].label, (completedPhases / phases.length) * 100);
82
+ try {
83
+ const generator = new TestGenerator(logger);
84
+ const testCases = generator.generate(surfaceInventory);
85
+ this._log(`Generated ${testCases.length} test cases`);
86
+
87
+ const runner = new TestRunner(config, logger);
88
+ const results = await runner.run(testCases);
89
+ this._testSummary = results.summary;
90
+ this.addFindings(results.findings);
91
+ this._log(`Tests: ${results.summary.passed} passed, ${results.summary.failed} failed`);
92
+ } catch (err) {
93
+ this._log(`Test execution failed: ${err.message}`, 'error');
94
+ }
95
+ completedPhases++;
96
+
97
+ // Phase 4: Form Validation
98
+ this.progress(phases[3].name, phases[3].label, (completedPhases / phases.length) * 100);
99
+ try {
100
+ const validator = new FormValidator(config, logger);
101
+ const findings = await validator.validate(surfaceInventory);
102
+ this.addFindings(findings);
103
+ this._log(`Forms: ${findings.length} issues`);
104
+ } catch (err) {
105
+ this._log(`Form validation failed: ${err.message}`, 'error');
106
+ }
107
+ completedPhases++;
108
+
109
+ // Phase 5: Responsive Checking
110
+ this.progress(phases[4].name, phases[4].label, (completedPhases / phases.length) * 100);
111
+ try {
112
+ const checker = new ResponsiveChecker(config, logger);
113
+ const findings = await checker.check(surfaceInventory);
114
+ this.addFindings(findings);
115
+ this._log(`Responsive: ${findings.length} issues`);
116
+ } catch (err) {
117
+ this._log(`Responsive check failed: ${err.message}`, 'error');
118
+ }
119
+ completedPhases++;
120
+
121
+ // Phase 6: Performance Checking — Core Web Vitals
122
+ this.progress(phases[5].name, phases[5].label, (completedPhases / phases.length) * 100);
123
+ try {
124
+ const perfChecker = new PerformanceChecker(config, logger);
125
+ const findings = await perfChecker.check(surfaceInventory);
126
+ this.addFindings(findings);
127
+ this._log(`Performance: ${findings.length} issues`);
128
+ } catch (err) {
129
+ this._log(`Performance checking failed: ${err.message}`, 'error');
130
+ }
131
+ completedPhases++;
132
+
133
+ // Phase 7: Accessibility Checking — WCAG 2.2 / axe-core
134
+ this.progress(phases[6].name, phases[6].label, (completedPhases / phases.length) * 100);
135
+ try {
136
+ const a11yChecker = new AccessibilityChecker(config, logger);
137
+ const findings = await a11yChecker.check(surfaceInventory);
138
+ this.addFindings(findings);
139
+ this._log(`Accessibility: ${findings.length} issues`);
140
+ } catch (err) {
141
+ this._log(`Accessibility checking failed: ${err.message}`, 'error');
142
+ }
143
+ completedPhases++;
144
+
145
+ this.progress('complete', `QA complete — ${this._findings.length} total findings`, 100);
146
+ }
147
+ }
148
+
149
+ export default QAAgent;
@@ -0,0 +1,211 @@
1
+ import { BaseAgent } from './base-agent.js';
2
+ import { HeaderAnalyzer } from '../core/security/header-analyzer.js';
3
+ import { SecretDetector } from '../core/security/secret-detector.js';
4
+ import { XSSScanner } from '../core/security/xss-scanner.js';
5
+ import { SQLiProber } from '../core/security/sqli-prober.js';
6
+ import { DependencyAuditor } from '../core/security/dependency-auditor.js';
7
+ import { TLSChecker } from '../core/security/tls-checker.js';
8
+ import { InfraScanner } from '../core/security/infra-scanner.js';
9
+ import { FileUploadTester } from '../core/security/file-upload-tester.js';
10
+ import { CORSProber } from '../core/security/cors-prober.js';
11
+ import { CSRFProber } from '../core/security/csrf-prober.js';
12
+ import { PrototypePollutionScanner } from '../core/security/prototype-pollution.js';
13
+ import { PathTraversalScanner } from '../core/security/path-traversal.js';
14
+
15
+ /**
16
+ * JAKU-SEC — Security Vulnerability Scanning Agent
17
+ *
18
+ * Pipeline (12 phases):
19
+ * 1. Header Analysis — CSP, HSTS, X-Frame-Options, etc.
20
+ * 2. Secret Detection — API keys, tokens, credentials in JS/HTML
21
+ * 3. XSS Scanning — Reflected, stored, DOM, AngularJS/Vue template injection
22
+ * 4. SQL Injection — Error-based, boolean-based, time-based
23
+ * 5. Dependency Audit — CVE lookup for npm/pip/gem packages
24
+ * 6. TLS Check — Protocol version, cipher strength, cert validity
25
+ * 7. Infrastructure Scan — Open ports, service disclosure, misconfigured services
26
+ * 8. File Upload Testing — MIME bypass, polyglot files, path traversal in upload
27
+ * 9. CORS Probing — Arbitrary origin reflection, null origin, pre-flight bypass
28
+ * 10. CSRF Probing — Cookie SameSite, token absence, state-changing GET
29
+ * 11. Prototype Pollution — __proto__ and constructor.prototype injection via URL/JSON
30
+ * 12. Path Traversal / LFI — ../ variants, encoding bypasses, cloud metadata SSRF
31
+ *
32
+ * Dependencies: JAKU-CRAWL
33
+ */
34
+ export class SecurityAgent extends BaseAgent {
35
+ get name() { return 'JAKU-SEC'; }
36
+ get dependencies() { return ['JAKU-CRAWL']; }
37
+
38
+ async _execute(context) {
39
+ const { config, logger, surfaceInventory } = context;
40
+
41
+ if (!surfaceInventory) {
42
+ throw new Error('No surface inventory available — JAKU-CRAWL must run first');
43
+ }
44
+
45
+ const phases = [
46
+ { name: 'headers', label: 'Analyzing security headers' },
47
+ { name: 'secrets', label: 'Scanning for exposed secrets' },
48
+ { name: 'xss', label: 'Probing for XSS vulnerabilities' },
49
+ { name: 'sqli', label: 'Probing for SQL injection' },
50
+ { name: 'deps', label: 'Auditing dependencies' },
51
+ { name: 'tls', label: 'Checking TLS configuration' },
52
+ { name: 'infra', label: 'Scanning infrastructure' },
53
+ { name: 'upload', label: 'Testing file upload security' },
54
+ { name: 'cors', label: 'Probing CORS misconfigurations' },
55
+ { name: 'csrf', label: 'Testing CSRF protection' },
56
+ { name: 'proto', label: 'Scanning for prototype pollution' },
57
+ { name: 'traversal', label: 'Testing path traversal / LFI' },
58
+ ];
59
+
60
+ let completedPhases = 0;
61
+
62
+ // Phase 1: Header Analysis
63
+ this.progress(phases[0].name, phases[0].label, 0);
64
+ try {
65
+ const analyzer = new HeaderAnalyzer(logger);
66
+ const findings = await analyzer.analyze(surfaceInventory);
67
+ this.addFindings(findings);
68
+ this._log(`Headers: ${findings.length} issues`);
69
+ } catch (err) {
70
+ this._log(`Header analysis failed: ${err.message}`, 'error');
71
+ }
72
+ completedPhases++;
73
+ this.progress(phases[0].name, 'Header analysis complete', (completedPhases / phases.length) * 100);
74
+
75
+ // Phase 2: Secret Detection
76
+ this.progress(phases[1].name, phases[1].label, (completedPhases / phases.length) * 100);
77
+ try {
78
+ const detector = new SecretDetector(logger);
79
+ const findings = await detector.detect(surfaceInventory);
80
+ this.addFindings(findings);
81
+ this._log(`Secrets: ${findings.length} exposures`);
82
+ } catch (err) {
83
+ this._log(`Secret detection failed: ${err.message}`, 'error');
84
+ }
85
+ completedPhases++;
86
+
87
+ // Phase 3: XSS Scanning
88
+ this.progress(phases[2].name, phases[2].label, (completedPhases / phases.length) * 100);
89
+ try {
90
+ const scanner = new XSSScanner(logger);
91
+ const findings = await scanner.scan(surfaceInventory);
92
+ this.addFindings(findings);
93
+ this._log(`XSS: ${findings.length} vulnerabilities`);
94
+ } catch (err) {
95
+ this._log(`XSS scanning failed: ${err.message}`, 'error');
96
+ }
97
+ completedPhases++;
98
+
99
+ // Phase 4: SQL Injection Probing
100
+ this.progress(phases[3].name, phases[3].label, (completedPhases / phases.length) * 100);
101
+ try {
102
+ const prober = new SQLiProber(logger);
103
+ const findings = await prober.probe(surfaceInventory);
104
+ this.addFindings(findings);
105
+ this._log(`SQLi: ${findings.length} vulnerabilities`);
106
+ } catch (err) {
107
+ this._log(`SQLi probing failed: ${err.message}`, 'error');
108
+ }
109
+ completedPhases++;
110
+
111
+ // Phase 5: Dependency Audit
112
+ this.progress(phases[4].name, phases[4].label, (completedPhases / phases.length) * 100);
113
+ try {
114
+ const auditor = new DependencyAuditor(config, logger);
115
+ const findings = await auditor.audit();
116
+ this.addFindings(findings);
117
+ this._log(`Dependencies: ${findings.length} issues`);
118
+ } catch (err) {
119
+ this._log(`Dependency audit failed: ${err.message}`, 'error');
120
+ }
121
+ completedPhases++;
122
+
123
+ // Phase 6: TLS/SSL Check
124
+ this.progress(phases[5].name, phases[5].label, (completedPhases / phases.length) * 100);
125
+ try {
126
+ const checker = new TLSChecker(logger);
127
+ const findings = await checker.check(surfaceInventory);
128
+ this.addFindings(findings);
129
+ this._log(`TLS: ${findings.length} issues`);
130
+ } catch (err) {
131
+ this._log(`TLS check failed: ${err.message}`, 'error');
132
+ }
133
+ completedPhases++;
134
+
135
+ // Phase 7: Infrastructure Scan
136
+ this.progress(phases[6].name, phases[6].label, (completedPhases / phases.length) * 100);
137
+ try {
138
+ const scanner = new InfraScanner(logger);
139
+ const findings = await scanner.scan(surfaceInventory);
140
+ this.addFindings(findings);
141
+ this._log(`Infrastructure: ${findings.length} issues`);
142
+ } catch (err) {
143
+ this._log(`Infrastructure scan failed: ${err.message}`, 'error');
144
+ }
145
+ completedPhases++;
146
+
147
+ // Phase 8: File Upload Testing
148
+ this.progress(phases[7].name, phases[7].label, (completedPhases / phases.length) * 100);
149
+ try {
150
+ const uploader = new FileUploadTester(logger);
151
+ const findings = await uploader.test(surfaceInventory);
152
+ this.addFindings(findings);
153
+ this._log(`File uploads: ${findings.length} issues`);
154
+ } catch (err) {
155
+ this._log(`File upload testing failed: ${err.message}`, 'error');
156
+ }
157
+ completedPhases++;
158
+
159
+ // Phase 9: CORS Probing
160
+ this.progress(phases[8].name, phases[8].label, (completedPhases / phases.length) * 100);
161
+ try {
162
+ const corsProber = new CORSProber(logger);
163
+ const findings = await corsProber.probe(surfaceInventory);
164
+ this.addFindings(findings);
165
+ this._log(`CORS: ${findings.length} misconfigurations`);
166
+ } catch (err) {
167
+ this._log(`CORS probing failed: ${err.message}`, 'error');
168
+ }
169
+ completedPhases++;
170
+
171
+ // Phase 10: CSRF Probing
172
+ this.progress(phases[9].name, phases[9].label, (completedPhases / phases.length) * 100);
173
+ try {
174
+ const csrfProber = new CSRFProber(logger);
175
+ const findings = await csrfProber.probe(surfaceInventory);
176
+ this.addFindings(findings);
177
+ this._log(`CSRF: ${findings.length} issues`);
178
+ } catch (err) {
179
+ this._log(`CSRF probing failed: ${err.message}`, 'error');
180
+ }
181
+ completedPhases++;
182
+
183
+ // Phase 11: Prototype Pollution
184
+ this.progress(phases[10].name, phases[10].label, (completedPhases / phases.length) * 100);
185
+ try {
186
+ const ppScanner = new PrototypePollutionScanner(logger);
187
+ const findings = await ppScanner.scan(surfaceInventory);
188
+ this.addFindings(findings);
189
+ this._log(`Prototype pollution: ${findings.length} issues`);
190
+ } catch (err) {
191
+ this._log(`Prototype pollution scan failed: ${err.message}`, 'error');
192
+ }
193
+ completedPhases++;
194
+
195
+ // Phase 12: Path Traversal / LFI
196
+ this.progress(phases[11].name, phases[11].label, (completedPhases / phases.length) * 100);
197
+ try {
198
+ const traversalScanner = new PathTraversalScanner(logger);
199
+ const findings = await traversalScanner.scan(surfaceInventory);
200
+ this.addFindings(findings);
201
+ this._log(`Path traversal: ${findings.length} issues`);
202
+ } catch (err) {
203
+ this._log(`Path traversal scan failed: ${err.message}`, 'error');
204
+ }
205
+ completedPhases++;
206
+
207
+ this.progress('complete', `Security scan complete — ${this._findings.length} total findings`, 100);
208
+ }
209
+ }
210
+
211
+ export default SecurityAgent;