jaku.sh 1.0.3 → 1.2.1
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/README.md +218 -18
- package/action.yml +32 -1
- package/package.json +6 -5
- package/scripts/postinstall.js +73 -0
- package/src/agents/ai-agent.js +47 -1
- package/src/agents/api-agent.js +9 -0
- package/src/agents/logic-agent.js +158 -90
- package/src/agents/orchestrator.js +56 -1
- package/src/agents/security-agent.js +86 -54
- package/src/cli.js +79 -6
- package/src/core/ai/ai-endpoint-detector.js +28 -4
- package/src/core/ai/prompt-injector.js +34 -0
- package/src/core/api/api-key-auditor.js +1 -1
- package/src/core/api/cors-ws-tester.js +1 -1
- package/src/core/llm/augmentations.js +210 -0
- package/src/core/llm/llm-client.js +184 -0
- package/src/core/llm/providers/anthropic-provider.js +46 -0
- package/src/core/llm/providers/base-provider.js +44 -0
- package/src/core/llm/providers/null-provider.js +21 -0
- package/src/core/llm/providers/openai-provider.js +47 -0
- package/src/core/logic/access-boundary-tester.js +1 -1
- package/src/core/logic/business-rule-inferrer.js +50 -1
- package/src/core/security/sqli-prober.js +312 -43
- package/src/core/security/xss-scanner.js +26 -2
- package/src/reporting/report-generator.js +96 -9
- package/src/reporting/sarif-generator.js +81 -5
- package/src/utils/browser.js +62 -0
- package/src/utils/config.js +196 -2
- package/src/utils/finding.js +3 -0
- package/src/utils/logger.js +33 -0
- package/src/utils/param-discovery.js +93 -0
- package/src/utils/safety.js +44 -0
- package/src/utils/version.js +30 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseAgent } from './base-agent.js';
|
|
2
|
+
import { allows, getSafetyMode } from '../utils/safety.js';
|
|
2
3
|
import { BusinessRuleInferrer } from '../core/logic/business-rule-inferrer.js';
|
|
3
4
|
import { PricingExploiter } from '../core/logic/pricing-exploiter.js';
|
|
4
5
|
import { AccessBoundaryTester } from '../core/logic/access-boundary-tester.js';
|
|
@@ -40,12 +41,39 @@ export class LogicAgent extends BaseAgent {
|
|
|
40
41
|
throw new Error('No surface inventory available — JAKU-CRAWL must run first');
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
const safetyMode = getSafetyMode(config);
|
|
45
|
+
this._log(`Safety mode: ${safetyMode}`);
|
|
46
|
+
|
|
47
|
+
// Tests that issue real state-changing/mutating requests (or that the
|
|
48
|
+
// user has classified as destructive) require --aggressive. Under
|
|
49
|
+
// passive/safe-active they are skipped with a clear log line.
|
|
50
|
+
const allowDestructive = allows(config, 'aggressive');
|
|
51
|
+
// Active but non-destructive probing requires at least safe-active.
|
|
52
|
+
const allowActive = allows(config, 'safe-active');
|
|
53
|
+
|
|
54
|
+
const skipDestructive = (label, phase, pct) => {
|
|
55
|
+
this._log(`${label} skipped — requires --aggressive safety mode (current: ${safetyMode})`);
|
|
56
|
+
this.progress(phase, `${label} skipped (safe mode)`, pct);
|
|
57
|
+
};
|
|
58
|
+
const skipActive = (label, phase, pct) => {
|
|
59
|
+
this._log(`${label} skipped — requires active probing (current: ${safetyMode} mode)`);
|
|
60
|
+
this.progress(phase, `${label} skipped (passive mode)`, pct);
|
|
61
|
+
};
|
|
62
|
+
|
|
43
63
|
// Phase 1: Infer business rules
|
|
44
64
|
this.progress('infer', 'Inferring business rules from surface inventory...', 0);
|
|
45
65
|
|
|
46
66
|
const inferrer = new BusinessRuleInferrer(logger);
|
|
47
67
|
const businessContext = inferrer.infer(surfaceInventory);
|
|
48
68
|
|
|
69
|
+
// Optional, additive LLM domain/invariant inference (no-op when disabled).
|
|
70
|
+
if (context.llmClient?.isEnabled?.()) {
|
|
71
|
+
await inferrer.augmentWithLLM(businessContext, surfaceInventory, context.llmClient);
|
|
72
|
+
if (businessContext.llmInsights?.invariants?.length) {
|
|
73
|
+
this._log(`LLM inferred ${businessContext.llmInsights.invariants.length} candidate business invariants`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
49
77
|
const activeDomains = Object.entries(businessContext.domains)
|
|
50
78
|
.filter(([, items]) => items.length > 0)
|
|
51
79
|
.map(([domain]) => domain);
|
|
@@ -62,123 +90,163 @@ export class LogicAgent extends BaseAgent {
|
|
|
62
90
|
const totalPhases = 11;
|
|
63
91
|
let completed = 1;
|
|
64
92
|
|
|
65
|
-
// Phase 2: Pricing exploitation
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
93
|
+
// Phase 2: Pricing exploitation (DESTRUCTIVE — sends mutating requests)
|
|
94
|
+
if (allowDestructive) {
|
|
95
|
+
this.progress('pricing', 'Testing pricing & payment logic...', (completed / totalPhases) * 100);
|
|
96
|
+
try {
|
|
97
|
+
const exploiter = new PricingExploiter(logger);
|
|
98
|
+
const pricingFindings = await exploiter.exploit(businessContext);
|
|
99
|
+
this.addFindings(pricingFindings);
|
|
100
|
+
this._log(`Pricing: ${pricingFindings.length} issues`);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
this._log(`Pricing testing failed: ${err.message}`, 'error');
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
skipDestructive('Pricing exploitation', 'pricing', (completed / totalPhases) * 100);
|
|
74
106
|
}
|
|
75
107
|
completed++;
|
|
76
108
|
|
|
77
|
-
// Phase 3: Access boundary testing
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
109
|
+
// Phase 3: Access boundary testing (active, non-destructive)
|
|
110
|
+
if (allowActive) {
|
|
111
|
+
this.progress('access', 'Testing access control boundaries...', (completed / totalPhases) * 100);
|
|
112
|
+
try {
|
|
113
|
+
const tester = new AccessBoundaryTester(logger);
|
|
114
|
+
const accessFindings = await tester.test(businessContext, surfaceInventory);
|
|
115
|
+
this.addFindings(accessFindings);
|
|
116
|
+
this._log(`Access: ${accessFindings.length} issues`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
this._log(`Access boundary testing failed: ${err.message}`, 'error');
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
skipActive('Access boundary testing', 'access', (completed / totalPhases) * 100);
|
|
86
122
|
}
|
|
87
123
|
completed++;
|
|
88
124
|
|
|
89
|
-
// Phase 4: Workflow enforcement
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
// Phase 4: Workflow enforcement (active, non-destructive)
|
|
126
|
+
if (allowActive) {
|
|
127
|
+
this.progress('workflow', 'Testing workflow enforcement...', (completed / totalPhases) * 100);
|
|
128
|
+
try {
|
|
129
|
+
const enforcer = new WorkflowEnforcer(logger);
|
|
130
|
+
const workflowFindings = await enforcer.enforce(businessContext, surfaceInventory);
|
|
131
|
+
this.addFindings(workflowFindings);
|
|
132
|
+
this._log(`Workflow: ${workflowFindings.length} issues`);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
this._log(`Workflow testing failed: ${err.message}`, 'error');
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
skipActive('Workflow enforcement', 'workflow', (completed / totalPhases) * 100);
|
|
98
138
|
}
|
|
99
139
|
completed++;
|
|
100
140
|
|
|
101
|
-
// Phase 5: Race condition detection
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
// Phase 5: Race condition detection (DESTRUCTIVE — fires concurrent mutating requests)
|
|
142
|
+
if (allowDestructive) {
|
|
143
|
+
this.progress('race', 'Testing for race conditions...', (completed / totalPhases) * 100);
|
|
144
|
+
try {
|
|
145
|
+
const detector = new RaceConditionDetector(logger);
|
|
146
|
+
const raceFindings = await detector.detect(businessContext, surfaceInventory);
|
|
147
|
+
this.addFindings(raceFindings);
|
|
148
|
+
this._log(`Race conditions: ${raceFindings.length} issues`);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
this._log(`Race condition testing failed: ${err.message}`, 'error');
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
skipDestructive('Race condition detection', 'race', (completed / totalPhases) * 100);
|
|
110
154
|
}
|
|
111
155
|
completed++;
|
|
112
156
|
|
|
113
|
-
// Phase 6: Abuse pattern scanning
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
157
|
+
// Phase 6: Abuse pattern scanning (active, non-destructive)
|
|
158
|
+
if (allowActive) {
|
|
159
|
+
this.progress('abuse', 'Scanning for abuse patterns...', (completed / totalPhases) * 100);
|
|
160
|
+
try {
|
|
161
|
+
const scanner = new AbusePatternScanner(logger);
|
|
162
|
+
const abuseFindings = await scanner.scan(businessContext, surfaceInventory);
|
|
163
|
+
this.addFindings(abuseFindings);
|
|
164
|
+
this._log(`Abuse patterns: ${abuseFindings.length} issues`);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
this._log(`Abuse pattern scanning failed: ${err.message}`, 'error');
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
skipActive('Abuse pattern scanning', 'abuse', (completed / totalPhases) * 100);
|
|
122
170
|
}
|
|
123
171
|
completed++;
|
|
124
172
|
|
|
125
|
-
// Phase 7: Coupon/promo abuse testing
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
173
|
+
// Phase 7: Coupon/promo abuse testing (gated to aggressive per safety policy)
|
|
174
|
+
if (allowDestructive) {
|
|
175
|
+
this.progress('coupon', 'Testing for coupon/promo abuse...', (completed / totalPhases) * 100);
|
|
176
|
+
try {
|
|
177
|
+
const tester = new CouponAbuseTester(logger);
|
|
178
|
+
const couponFindings = await tester.test(businessContext, surfaceInventory);
|
|
179
|
+
this.addFindings(couponFindings);
|
|
180
|
+
this._log(`Coupon abuse: ${couponFindings.length} issues`);
|
|
181
|
+
} catch (err) {
|
|
182
|
+
this._log(`Coupon abuse testing failed: ${err.message}`, 'error');
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
skipDestructive('Coupon/promo abuse testing', 'coupon', (completed / totalPhases) * 100);
|
|
134
186
|
}
|
|
135
187
|
completed++;
|
|
136
188
|
|
|
137
|
-
// Phase 8: Cart manipulation testing
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
189
|
+
// Phase 8: Cart manipulation testing (gated to aggressive per safety policy)
|
|
190
|
+
if (allowDestructive) {
|
|
191
|
+
this.progress('cart', 'Testing for cart manipulation...', (completed / totalPhases) * 100);
|
|
192
|
+
try {
|
|
193
|
+
const tester = new CartManipulationTester(logger);
|
|
194
|
+
const cartFindings = await tester.test(businessContext, surfaceInventory);
|
|
195
|
+
this.addFindings(cartFindings);
|
|
196
|
+
this._log(`Cart manipulation: ${cartFindings.length} issues`);
|
|
197
|
+
} catch (err) {
|
|
198
|
+
this._log(`Cart manipulation testing failed: ${err.message}`, 'error');
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
skipDestructive('Cart manipulation testing', 'cart', (completed / totalPhases) * 100);
|
|
146
202
|
}
|
|
147
203
|
completed++;
|
|
148
204
|
|
|
149
|
-
// Phase 9: Email enumeration testing
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
205
|
+
// Phase 9: Email enumeration testing (active, non-destructive)
|
|
206
|
+
if (allowActive) {
|
|
207
|
+
this.progress('email-enum', 'Testing for email enumeration...', (completed / totalPhases) * 100);
|
|
208
|
+
try {
|
|
209
|
+
const tester = new EmailEnumerationTester(logger);
|
|
210
|
+
const emailFindings = await tester.test(businessContext, surfaceInventory);
|
|
211
|
+
this.addFindings(emailFindings);
|
|
212
|
+
this._log(`Email enumeration: ${emailFindings.length} issues`);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
this._log(`Email enumeration testing failed: ${err.message}`, 'error');
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
skipActive('Email enumeration testing', 'email-enum', (completed / totalPhases) * 100);
|
|
158
218
|
}
|
|
159
219
|
completed++;
|
|
160
220
|
|
|
161
|
-
// Phase 10: Account takeover testing
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
221
|
+
// Phase 10: Account takeover testing (gated to aggressive per safety policy)
|
|
222
|
+
if (allowDestructive) {
|
|
223
|
+
this.progress('account-takeover', 'Testing account takeover flows...', (completed / totalPhases) * 100);
|
|
224
|
+
try {
|
|
225
|
+
const tester = new AccountTakeoverTester(logger);
|
|
226
|
+
const atoFindings = await tester.test(businessContext, surfaceInventory);
|
|
227
|
+
this.addFindings(atoFindings);
|
|
228
|
+
this._log(`Account takeover: ${atoFindings.length} issues`);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
this._log(`Account takeover testing failed: ${err.message}`, 'error');
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
skipDestructive('Account takeover testing', 'account-takeover', (completed / totalPhases) * 100);
|
|
170
234
|
}
|
|
171
235
|
completed++;
|
|
172
236
|
|
|
173
|
-
// Phase 11: Feature flag bypass testing
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
237
|
+
// Phase 11: Feature flag bypass testing (active, non-destructive)
|
|
238
|
+
if (allowActive) {
|
|
239
|
+
this.progress('feature-flags', 'Testing for feature flag bypass...', (completed / totalPhases) * 100);
|
|
240
|
+
try {
|
|
241
|
+
const tester = new FeatureFlagBypassTester(logger);
|
|
242
|
+
const flagFindings = await tester.test(businessContext, surfaceInventory);
|
|
243
|
+
this.addFindings(flagFindings);
|
|
244
|
+
this._log(`Feature flag bypass: ${flagFindings.length} issues`);
|
|
245
|
+
} catch (err) {
|
|
246
|
+
this._log(`Feature flag bypass testing failed: ${err.message}`, 'error');
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
skipActive('Feature flag bypass testing', 'feature-flags', (completed / totalPhases) * 100);
|
|
182
250
|
}
|
|
183
251
|
completed++;
|
|
184
252
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { EventBus } from './event-bus.js';
|
|
2
2
|
import { FindingsLedger } from './findings-ledger.js';
|
|
3
|
+
import { getVersion } from '../utils/version.js';
|
|
4
|
+
import { LLMClient } from '../core/llm/llm-client.js';
|
|
5
|
+
import { enrichCorrelation, triageFinding } from '../core/llm/augmentations.js';
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Orchestrator — Central coordinator for the JAKU multi-agent system.
|
|
@@ -18,6 +21,11 @@ export class Orchestrator {
|
|
|
18
21
|
this.eventBus = new EventBus();
|
|
19
22
|
this.ledger = new FindingsLedger(this.eventBus);
|
|
20
23
|
|
|
24
|
+
// Optional LLM augmentation client (default OFF). Constructed once and
|
|
25
|
+
// shared with all agents via context so any agent can opt in. Returns
|
|
26
|
+
// null on every call when disabled/unavailable → callers degrade.
|
|
27
|
+
this.llmClient = new LLMClient(config, logger);
|
|
28
|
+
|
|
21
29
|
this._agents = new Map(); // name → agent instance
|
|
22
30
|
this._sharedContext = { // passed to all agents
|
|
23
31
|
config,
|
|
@@ -25,6 +33,7 @@ export class Orchestrator {
|
|
|
25
33
|
eventBus: this.eventBus,
|
|
26
34
|
ledger: this.ledger,
|
|
27
35
|
surfaceInventory: null, // set by JAKU-CRAWL
|
|
36
|
+
llmClient: this.llmClient, // optional; null-equivalent when disabled
|
|
28
37
|
};
|
|
29
38
|
|
|
30
39
|
this._startTime = null;
|
|
@@ -123,6 +132,10 @@ export class Orchestrator {
|
|
|
123
132
|
const duration = Date.now() - this._startTime;
|
|
124
133
|
const results = this._synthesize(duration);
|
|
125
134
|
|
|
135
|
+
// Optional LLM augmentation of synthesized results (additive, best-effort).
|
|
136
|
+
// The deterministic correlate()/dedup output above is the source of truth.
|
|
137
|
+
await this._llmAugment(results);
|
|
138
|
+
|
|
126
139
|
this.eventBus.emit('scan:completed', {
|
|
127
140
|
timestamp: new Date().toISOString(),
|
|
128
141
|
duration,
|
|
@@ -230,6 +243,48 @@ export class Orchestrator {
|
|
|
230
243
|
};
|
|
231
244
|
}
|
|
232
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Phase 2 — LLM augmentation of synthesized results. STRICTLY ADDITIVE:
|
|
248
|
+
* - Enriches attack-chain correlation narratives (keeps original as fallback).
|
|
249
|
+
* - Triages borderline findings (advisory `llm_triage` note only; never
|
|
250
|
+
* mutates the deterministic severity or pass/fail).
|
|
251
|
+
* No-op when the LLM client is disabled/over-budget (returns immediately).
|
|
252
|
+
*/
|
|
253
|
+
async _llmAugment(results) {
|
|
254
|
+
const client = this.llmClient;
|
|
255
|
+
if (!client?.isEnabled?.()) return;
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
// Enrich correlation narratives.
|
|
259
|
+
for (const c of results.correlations || []) {
|
|
260
|
+
const enriched = await enrichCorrelation(client, c);
|
|
261
|
+
if (enriched) {
|
|
262
|
+
c.narrative_llm = enriched;
|
|
263
|
+
c.narrative_source = 'llm';
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Triage borderline findings only (limit egress + budget).
|
|
268
|
+
const borderline = (results.deduplicated || results.findings || [])
|
|
269
|
+
.filter(f => this._isBorderline(f))
|
|
270
|
+
.slice(0, 15);
|
|
271
|
+
for (const f of borderline) {
|
|
272
|
+
const triage = await triageFinding(client, f);
|
|
273
|
+
if (triage) f.llm_triage = triage;
|
|
274
|
+
}
|
|
275
|
+
} catch (err) {
|
|
276
|
+
this.logger?.debug?.(`[LLM] augmentation skipped: ${err.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/** Heuristic: which findings are "borderline" and worth LLM triage. */
|
|
281
|
+
_isBorderline(f) {
|
|
282
|
+
if (!f) return false;
|
|
283
|
+
if (f.severity === 'medium' || f.severity === 'low') return true;
|
|
284
|
+
const text = `${f.title || ''} ${f.description || ''}`.toLowerCase();
|
|
285
|
+
return /\b(potential|possible|may|might|suspected|likely|reflected)\b/.test(text);
|
|
286
|
+
}
|
|
287
|
+
|
|
233
288
|
/**
|
|
234
289
|
* Get the status of all agents.
|
|
235
290
|
*/
|
|
@@ -255,7 +310,7 @@ export class Orchestrator {
|
|
|
255
310
|
try {
|
|
256
311
|
const payload = {
|
|
257
312
|
agent: 'JAKU',
|
|
258
|
-
version:
|
|
313
|
+
version: getVersion(),
|
|
259
314
|
target: this.config.target_url,
|
|
260
315
|
timestamp: new Date().toISOString(),
|
|
261
316
|
duration: results.duration,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseAgent } from './base-agent.js';
|
|
2
|
+
import { allows, getSafetyMode } from '../utils/safety.js';
|
|
2
3
|
import { HeaderAnalyzer } from '../core/security/header-analyzer.js';
|
|
3
4
|
import { SecretDetector } from '../core/security/secret-detector.js';
|
|
4
5
|
import { XSSScanner } from '../core/security/xss-scanner.js';
|
|
@@ -37,6 +38,13 @@ export class SecurityAgent extends BaseAgent {
|
|
|
37
38
|
throw new Error('No surface inventory available — JAKU-CRAWL must run first');
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// Active attack probing (injecting payloads) requires at least
|
|
42
|
+
// safe-active. In passive mode we only run read-only/analysis checks.
|
|
43
|
+
const allowActive = allows(config, 'safe-active');
|
|
44
|
+
const safetyMode = getSafetyMode(config);
|
|
45
|
+
const skipActive = (label) =>
|
|
46
|
+
this._log(`${label} skipped — requires active probing (current: ${safetyMode} mode)`);
|
|
47
|
+
|
|
40
48
|
const phases = [
|
|
41
49
|
{ name: 'headers', label: 'Analyzing security headers' },
|
|
42
50
|
{ name: 'secrets', label: 'Scanning for exposed secrets' },
|
|
@@ -82,27 +90,35 @@ export class SecurityAgent extends BaseAgent {
|
|
|
82
90
|
}
|
|
83
91
|
completedPhases++;
|
|
84
92
|
|
|
85
|
-
// Phase 3: XSS Scanning
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
93
|
+
// Phase 3: XSS Scanning (active probing)
|
|
94
|
+
if (allowActive) {
|
|
95
|
+
this.progress(phases[2].name, phases[2].label, (completedPhases / phases.length) * 100);
|
|
96
|
+
try {
|
|
97
|
+
const scanner = new XSSScanner(logger);
|
|
98
|
+
const findings = await scanner.scan(surfaceInventory);
|
|
99
|
+
this.addFindings(findings);
|
|
100
|
+
this._log(`XSS: ${findings.length} vulnerabilities`);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
this._log(`XSS scanning failed: ${err.message}`, 'error');
|
|
103
|
+
}
|
|
104
|
+
} else {
|
|
105
|
+
skipActive('XSS scanning');
|
|
94
106
|
}
|
|
95
107
|
completedPhases++;
|
|
96
108
|
|
|
97
|
-
// Phase 4: SQL Injection Probing
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
// Phase 4: SQL Injection Probing (active probing)
|
|
110
|
+
if (allowActive) {
|
|
111
|
+
this.progress(phases[3].name, phases[3].label, (completedPhases / phases.length) * 100);
|
|
112
|
+
try {
|
|
113
|
+
const prober = new SQLiProber(logger);
|
|
114
|
+
const findings = await prober.probe(surfaceInventory);
|
|
115
|
+
this.addFindings(findings);
|
|
116
|
+
this._log(`SQLi: ${findings.length} vulnerabilities`);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
this._log(`SQLi probing failed: ${err.message}`, 'error');
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
skipActive('SQLi probing');
|
|
106
122
|
}
|
|
107
123
|
completedPhases++;
|
|
108
124
|
|
|
@@ -130,27 +146,35 @@ export class SecurityAgent extends BaseAgent {
|
|
|
130
146
|
}
|
|
131
147
|
completedPhases++;
|
|
132
148
|
|
|
133
|
-
// Phase 7: Infrastructure Scan
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
// Phase 7: Infrastructure Scan (active probing of admin/debug endpoints)
|
|
150
|
+
if (allowActive) {
|
|
151
|
+
this.progress(phases[6].name, phases[6].label, (completedPhases / phases.length) * 100);
|
|
152
|
+
try {
|
|
153
|
+
const scanner = new InfraScanner(logger);
|
|
154
|
+
const findings = await scanner.scan(surfaceInventory);
|
|
155
|
+
this.addFindings(findings);
|
|
156
|
+
this._log(`Infrastructure: ${findings.length} issues`);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this._log(`Infrastructure scan failed: ${err.message}`, 'error');
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
skipActive('Infrastructure scan');
|
|
142
162
|
}
|
|
143
163
|
completedPhases++;
|
|
144
164
|
|
|
145
|
-
// Phase 8: File Upload Testing
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
// Phase 8: File Upload Testing (active probing)
|
|
166
|
+
if (allowActive) {
|
|
167
|
+
this.progress(phases[7].name, phases[7].label, (completedPhases / phases.length) * 100);
|
|
168
|
+
try {
|
|
169
|
+
const uploader = new FileUploadTester(logger);
|
|
170
|
+
const findings = await uploader.test(surfaceInventory);
|
|
171
|
+
this.addFindings(findings);
|
|
172
|
+
this._log(`File uploads: ${findings.length} issues`);
|
|
173
|
+
} catch (err) {
|
|
174
|
+
this._log(`File upload testing failed: ${err.message}`, 'error');
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
skipActive('File upload testing');
|
|
154
178
|
}
|
|
155
179
|
completedPhases++;
|
|
156
180
|
|
|
@@ -166,15 +190,19 @@ export class SecurityAgent extends BaseAgent {
|
|
|
166
190
|
}
|
|
167
191
|
completedPhases++;
|
|
168
192
|
|
|
169
|
-
// Phase 10: Open Redirect Detection
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
193
|
+
// Phase 10: Open Redirect Detection (active probing)
|
|
194
|
+
if (allowActive) {
|
|
195
|
+
this.progress(phases[9].name, phases[9].label, (completedPhases / phases.length) * 100);
|
|
196
|
+
try {
|
|
197
|
+
const detector = new OpenRedirectDetector(logger);
|
|
198
|
+
const findings = await detector.detect(surfaceInventory);
|
|
199
|
+
this.addFindings(findings);
|
|
200
|
+
this._log(`Open redirects: ${findings.length} issues`);
|
|
201
|
+
} catch (err) {
|
|
202
|
+
this._log(`Open redirect detection failed: ${err.message}`, 'error');
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
skipActive('Open redirect detection');
|
|
178
206
|
}
|
|
179
207
|
completedPhases++;
|
|
180
208
|
|
|
@@ -226,15 +254,19 @@ export class SecurityAgent extends BaseAgent {
|
|
|
226
254
|
}
|
|
227
255
|
completedPhases++;
|
|
228
256
|
|
|
229
|
-
// Phase 15: SSRF Probing
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
// Phase 15: SSRF Probing (active probing)
|
|
258
|
+
if (allowActive) {
|
|
259
|
+
this.progress(phases[14].name, phases[14].label, (completedPhases / phases.length) * 100);
|
|
260
|
+
try {
|
|
261
|
+
const prober = new SSRFProber(logger);
|
|
262
|
+
const findings = await prober.probe(surfaceInventory);
|
|
263
|
+
this.addFindings(findings);
|
|
264
|
+
this._log(`SSRF: ${findings.length} issues`);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
this._log(`SSRF probing failed: ${err.message}`, 'error');
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
skipActive('SSRF probing');
|
|
238
270
|
}
|
|
239
271
|
completedPhases++;
|
|
240
272
|
|