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.
- package/LICENSE +52 -0
- package/README.md +636 -0
- package/action.yml +264 -0
- package/bin/jaku +2 -0
- package/package.json +62 -0
- package/src/agents/ai-agent.js +175 -0
- package/src/agents/api-agent.js +95 -0
- package/src/agents/base-agent.js +158 -0
- package/src/agents/crawl-agent.js +175 -0
- package/src/agents/event-bus.js +59 -0
- package/src/agents/findings-ledger.js +410 -0
- package/src/agents/logic-agent.js +144 -0
- package/src/agents/orchestrator.js +323 -0
- package/src/agents/qa-agent.js +149 -0
- package/src/agents/security-agent.js +211 -0
- package/src/cli.js +423 -0
- package/src/core/accessibility-checker.js +171 -0
- package/src/core/ai/ai-endpoint-detector.js +227 -0
- package/src/core/ai/guardrail-prober.js +362 -0
- package/src/core/ai/indirect-injector.js +106 -0
- package/src/core/ai/jailbreak-tester.js +212 -0
- package/src/core/ai/model-dos-tester.js +174 -0
- package/src/core/ai/model-fingerprinter.js +246 -0
- package/src/core/ai/multi-turn-attacker.js +297 -0
- package/src/core/ai/output-analyzer.js +182 -0
- package/src/core/ai/prompt-injector.js +543 -0
- package/src/core/ai/system-prompt-extractor.js +244 -0
- package/src/core/api/api-key-auditor.js +266 -0
- package/src/core/api/auth-flow-tester.js +430 -0
- package/src/core/api/cors-ws-tester.js +263 -0
- package/src/core/api/graphql-tester.js +287 -0
- package/src/core/api/oauth-prober.js +343 -0
- package/src/core/auth-manager.js +902 -0
- package/src/core/broken-flow-detector.js +207 -0
- package/src/core/browser-manager.js +119 -0
- package/src/core/console-monitor.js +111 -0
- package/src/core/crawler.js +430 -0
- package/src/core/csr-waiter.js +410 -0
- package/src/core/form-validator.js +240 -0
- package/src/core/logic/abuse-pattern-scanner.js +291 -0
- package/src/core/logic/access-boundary-tester.js +448 -0
- package/src/core/logic/business-rule-inferrer.js +196 -0
- package/src/core/logic/graphql-auditor.js +298 -0
- package/src/core/logic/parameter-polluter.js +212 -0
- package/src/core/logic/pricing-exploiter.js +299 -0
- package/src/core/logic/race-condition-detector.js +222 -0
- package/src/core/logic/workflow-enforcer.js +284 -0
- package/src/core/performance-checker.js +204 -0
- package/src/core/responsive-checker.js +228 -0
- package/src/core/security/cors-prober.js +150 -0
- package/src/core/security/csrf-prober.js +217 -0
- package/src/core/security/dependency-auditor.js +182 -0
- package/src/core/security/file-upload-tester.js +340 -0
- package/src/core/security/header-analyzer.js +324 -0
- package/src/core/security/infra-scanner.js +391 -0
- package/src/core/security/path-traversal.js +112 -0
- package/src/core/security/prototype-pollution.js +147 -0
- package/src/core/security/secret-detector.js +517 -0
- package/src/core/security/sqli-prober.js +257 -0
- package/src/core/security/tls-checker.js +223 -0
- package/src/core/security/xss-scanner.js +225 -0
- package/src/core/test-generator.js +339 -0
- package/src/core/test-runner.js +398 -0
- package/src/reporting/diff-reporter.js +172 -0
- package/src/reporting/report-generator.js +408 -0
- package/src/reporting/sarif-generator.js +190 -0
- package/src/utils/config.js +57 -0
- package/src/utils/finding.js +67 -0
- 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;
|