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,299 @@
1
+ import { createFinding } from '../../utils/finding.js';
2
+
3
+ /**
4
+ * PricingExploiter — Tests payment & pricing logic for manipulation vulnerabilities.
5
+ *
6
+ * Probes:
7
+ * - Negative price/quantity injection
8
+ * - Zero-amount order attempts
9
+ * - Coupon stacking and reuse
10
+ * - Price parameter tampering
11
+ * - Currency confusion
12
+ * - Integer overflow quantities
13
+ * - Discount manipulation
14
+ */
15
+ export class PricingExploiter {
16
+ constructor(logger) {
17
+ this.logger = logger;
18
+
19
+ this.PROBES = [
20
+ {
21
+ name: 'Negative quantity',
22
+ description: 'Submit negative quantity to reverse charges or get credits',
23
+ paramTargets: ['quantity', 'qty', 'amount', 'count'],
24
+ value: '-1',
25
+ severity: 'critical',
26
+ },
27
+ {
28
+ name: 'Negative price',
29
+ description: 'Submit negative price to reduce total or get refund',
30
+ paramTargets: ['price', 'amount', 'total', 'subtotal', 'unit_price'],
31
+ value: '-100',
32
+ severity: 'critical',
33
+ },
34
+ {
35
+ name: 'Zero amount checkout',
36
+ description: 'Attempt to complete purchase with zero total',
37
+ paramTargets: ['total', 'amount', 'price', 'charge'],
38
+ value: '0',
39
+ severity: 'high',
40
+ },
41
+ {
42
+ name: 'Fractional quantity',
43
+ description: 'Submit fractional quantity (0.001) to exploit rounding',
44
+ paramTargets: ['quantity', 'qty', 'amount', 'count'],
45
+ value: '0.001',
46
+ severity: 'medium',
47
+ },
48
+ {
49
+ name: 'Integer overflow quantity',
50
+ description: 'Submit extremely large quantity to cause integer overflow',
51
+ paramTargets: ['quantity', 'qty', 'count'],
52
+ value: '999999999',
53
+ severity: 'high',
54
+ },
55
+ {
56
+ name: 'Price tampering - penny',
57
+ description: 'Change price to 1 cent to test server-side validation',
58
+ paramTargets: ['price', 'amount', 'unit_price', 'cost'],
59
+ value: '0.01',
60
+ severity: 'critical',
61
+ },
62
+ {
63
+ name: 'Currency confusion',
64
+ description: 'Submit unexpected currency code to exploit conversion',
65
+ paramTargets: ['currency', 'currency_code', 'cur'],
66
+ value: 'XXX',
67
+ severity: 'medium',
68
+ },
69
+ {
70
+ name: 'Coupon code - empty',
71
+ description: 'Submit empty coupon to test for default discount',
72
+ paramTargets: ['coupon', 'coupon_code', 'promo', 'promo_code', 'discount_code'],
73
+ value: '',
74
+ severity: 'low',
75
+ },
76
+ {
77
+ name: 'Coupon code - universal',
78
+ description: 'Try common test/debug coupon codes',
79
+ paramTargets: ['coupon', 'coupon_code', 'promo', 'promo_code', 'discount_code'],
80
+ values: ['TEST', 'DEBUG', 'ADMIN', '100OFF', 'FREE', 'DISCOUNT', 'OVERRIDE'],
81
+ severity: 'high',
82
+ },
83
+ {
84
+ name: 'Negative discount',
85
+ description: 'Submit negative discount percentage to increase price awareness',
86
+ paramTargets: ['discount', 'discount_percent', 'discount_amount'],
87
+ value: '-50',
88
+ severity: 'medium',
89
+ },
90
+ {
91
+ name: 'Tax bypass',
92
+ description: 'Set tax to zero or remove tax parameter',
93
+ paramTargets: ['tax', 'tax_amount', 'tax_rate', 'vat'],
94
+ value: '0',
95
+ severity: 'medium',
96
+ },
97
+ {
98
+ name: 'Shipping manipulation',
99
+ description: 'Set shipping cost to zero or negative',
100
+ paramTargets: ['shipping', 'shipping_cost', 'delivery_fee', 'shipping_amount'],
101
+ value: '0',
102
+ severity: 'medium',
103
+ },
104
+ ];
105
+ }
106
+
107
+ /**
108
+ * Test pricing surfaces for manipulation vulnerabilities.
109
+ */
110
+ async exploit(businessContext) {
111
+ const findings = [];
112
+ const pricingSurfaces = businessContext.pricingSurfaces || [];
113
+
114
+ if (pricingSurfaces.length === 0) {
115
+ this.logger?.info?.('Pricing Exploiter: no pricing surfaces found — skipping');
116
+ return findings;
117
+ }
118
+
119
+ this.logger?.info?.(`Pricing Exploiter: testing ${pricingSurfaces.length} pricing surfaces`);
120
+
121
+ for (const surface of pricingSurfaces) {
122
+ // Test forms with pricing fields
123
+ if (surface.type === 'form' && surface.fields) {
124
+ const formFindings = await this._testForm(surface);
125
+ findings.push(...formFindings);
126
+ }
127
+
128
+ // Test API endpoints
129
+ if (surface.type === 'api') {
130
+ const apiFindings = await this._testAPI(surface);
131
+ findings.push(...apiFindings);
132
+ }
133
+ }
134
+
135
+ this.logger?.info?.(`Pricing Exploiter: found ${findings.length} issues`);
136
+ return findings;
137
+ }
138
+
139
+ async _testForm(surface) {
140
+ const findings = [];
141
+ const fields = (surface.fields || []).map(f => ({
142
+ name: (f.name || f.id || '').toLowerCase(),
143
+ original: f,
144
+ }));
145
+
146
+ for (const probe of this.PROBES) {
147
+ const matchingFields = fields.filter(f =>
148
+ probe.paramTargets.some(target => f.name.includes(target))
149
+ );
150
+
151
+ if (matchingFields.length === 0) continue;
152
+
153
+ for (const field of matchingFields) {
154
+ const values = probe.values || [probe.value];
155
+ for (const value of values) {
156
+ const result = await this._submitProbe(surface, field, value, probe);
157
+ if (result) {
158
+ findings.push(result);
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ return findings;
165
+ }
166
+
167
+ async _testAPI(surface) {
168
+ const findings = [];
169
+ const url = surface.url;
170
+
171
+ for (const probe of this.PROBES) {
172
+ const values = probe.values || [probe.value];
173
+ for (const value of values) {
174
+ for (const param of probe.paramTargets) {
175
+ try {
176
+ const body = {};
177
+ body[param] = value;
178
+
179
+ const controller = new AbortController();
180
+ const timeout = setTimeout(() => controller.abort(), 8000);
181
+
182
+ const response = await fetch(url, {
183
+ method: surface.method || 'POST',
184
+ headers: { 'Content-Type': 'application/json' },
185
+ body: JSON.stringify(body),
186
+ signal: controller.signal,
187
+ });
188
+ clearTimeout(timeout);
189
+
190
+ // If the server accepts the manipulated value (2xx), flag it
191
+ if (response.ok) {
192
+ const text = await response.text();
193
+ const accepted = this._checkAccepted(text, probe, value);
194
+ if (accepted) {
195
+ findings.push(createFinding({
196
+ module: 'logic',
197
+ title: `Pricing Manipulation: ${probe.name}`,
198
+ severity: probe.severity,
199
+ affected_surface: url,
200
+ description: `${probe.description}. The API at ${url} accepted ${param}=${value} without server-side validation. ${accepted.reason}`,
201
+ reproduction: [
202
+ `1. Send ${surface.method || 'POST'} to ${url}`,
203
+ `2. Set "${param}" to "${value}"`,
204
+ `3. Server responds with 200 OK — value accepted`,
205
+ ],
206
+ evidence: `Parameter: ${param}=${value}\nResponse status: ${response.status}\nResponse: ${text.substring(0, 300)}`,
207
+ remediation: 'Implement server-side validation for all pricing parameters. Never trust client-supplied prices, quantities, or totals. Recalculate all amounts server-side from the product catalog. Validate quantities are positive integers within allowed ranges.',
208
+ }));
209
+ }
210
+ }
211
+ } catch {
212
+ continue;
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ return findings;
219
+ }
220
+
221
+ async _submitProbe(surface, field, value, probe) {
222
+ try {
223
+ const url = surface.url || surface.pageUrl;
224
+ const body = {};
225
+ body[field.name] = value;
226
+
227
+ const controller = new AbortController();
228
+ const timeout = setTimeout(() => controller.abort(), 8000);
229
+
230
+ const response = await fetch(url, {
231
+ method: 'POST',
232
+ headers: { 'Content-Type': 'application/json' },
233
+ body: JSON.stringify(body),
234
+ signal: controller.signal,
235
+ });
236
+ clearTimeout(timeout);
237
+
238
+ if (response.ok) {
239
+ const text = await response.text();
240
+ const accepted = this._checkAccepted(text, probe, value);
241
+ if (accepted) {
242
+ return createFinding({
243
+ module: 'logic',
244
+ title: `Pricing Manipulation: ${probe.name}`,
245
+ severity: probe.severity,
246
+ affected_surface: url,
247
+ description: `${probe.description}. The form accepted "${field.name}=${value}" without validation. ${accepted.reason}`,
248
+ reproduction: [
249
+ `1. Navigate to ${surface.pageUrl || url}`,
250
+ `2. Set "${field.name}" to "${value}"`,
251
+ `3. Submit the form`,
252
+ ],
253
+ evidence: `Field: ${field.name}=${value}\nStatus: ${response.status}`,
254
+ remediation: 'Implement server-side validation for all pricing parameters. Never trust client-supplied prices or quantities.',
255
+ });
256
+ }
257
+ }
258
+ } catch {
259
+ // Network error — skip
260
+ }
261
+ return null;
262
+ }
263
+
264
+ _checkAccepted(responseText, probe, value) {
265
+ const lower = responseText.toLowerCase();
266
+
267
+ // If the response contains success indicators
268
+ const successIndicators = [
269
+ /success/i, /accepted/i, /confirmed/i, /processed/i,
270
+ /order.*created/i, /added.*cart/i, /applied/i,
271
+ ];
272
+
273
+ // If the response contains the manipulated value echoed back
274
+ const echoed = lower.includes(value.toLowerCase());
275
+
276
+ // If the response does NOT contain error indicators
277
+ const errorIndicators = [
278
+ /invalid/i, /error/i, /not allowed/i, /rejected/i,
279
+ /must be positive/i, /minimum/i, /maximum/i, /out of range/i,
280
+ ];
281
+ const hasError = errorIndicators.some(p => p.test(responseText));
282
+
283
+ if (hasError) return null;
284
+
285
+ const hasSuccess = successIndicators.some(p => p.test(responseText));
286
+
287
+ if (hasSuccess || echoed) {
288
+ return {
289
+ reason: hasSuccess
290
+ ? 'Server returned success response for manipulated value'
291
+ : 'Server echoed the manipulated value without rejection',
292
+ };
293
+ }
294
+
295
+ return null;
296
+ }
297
+ }
298
+
299
+ export default PricingExploiter;
@@ -0,0 +1,222 @@
1
+ import { createFinding } from '../../utils/finding.js';
2
+
3
+ /**
4
+ * RaceConditionDetector — Tests for race conditions in critical paths.
5
+ *
6
+ * Fires concurrent requests at state-changing endpoints to detect:
7
+ * - Double spend (concurrent payment/transfer)
8
+ * - Duplicate submission (same action processed twice)
9
+ * - TOCTOU (time-of-check vs time-of-use)
10
+ * - Limit bypass (concurrent requests that individually pass limits but collectively exceed)
11
+ * - Inventory oversell (concurrent purchases of limited-stock item)
12
+ */
13
+ export class RaceConditionDetector {
14
+ constructor(logger) {
15
+ this.logger = logger;
16
+ this.CONCURRENCY = 10; // Number of simultaneous requests
17
+ }
18
+
19
+ /**
20
+ * Test for race conditions on critical endpoints.
21
+ */
22
+ async detect(businessContext, surfaceInventory) {
23
+ const findings = [];
24
+
25
+ this.logger?.info?.('Race Condition Detector: starting tests');
26
+
27
+ // Gather critical endpoints from all business domains
28
+ const criticalEndpoints = this._gatherCriticalEndpoints(businessContext);
29
+
30
+ if (criticalEndpoints.length === 0) {
31
+ this.logger?.info?.('Race Condition Detector: no critical endpoints found — skipping');
32
+ return findings;
33
+ }
34
+
35
+ this.logger?.info?.(`Race Condition Detector: testing ${criticalEndpoints.length} endpoints`);
36
+
37
+ for (const endpoint of criticalEndpoints) {
38
+ const result = await this._testEndpoint(endpoint);
39
+ if (result) {
40
+ findings.push(result);
41
+ }
42
+ }
43
+
44
+ this.logger?.info?.(`Race Condition Detector: found ${findings.length} issues`);
45
+ return findings;
46
+ }
47
+
48
+ /**
49
+ * Gather state-changing endpoints that are race-condition sensitive.
50
+ */
51
+ _gatherCriticalEndpoints(businessContext) {
52
+ const endpoints = [];
53
+
54
+ // Payment endpoints
55
+ for (const surface of (businessContext.domains.payments || [])) {
56
+ if (surface.type === 'api' || surface.type === 'form') {
57
+ endpoints.push({
58
+ url: surface.url,
59
+ method: surface.method || 'POST',
60
+ category: 'payment',
61
+ body: { amount: 1, action: 'process' },
62
+ });
63
+ }
64
+ }
65
+
66
+ // Inventory endpoints
67
+ for (const surface of (businessContext.domains.inventory || [])) {
68
+ if (surface.type === 'api') {
69
+ endpoints.push({
70
+ url: surface.url,
71
+ method: surface.method || 'POST',
72
+ category: 'inventory',
73
+ body: { quantity: 1, action: 'purchase' },
74
+ });
75
+ }
76
+ }
77
+
78
+ // Referral/reward endpoints
79
+ for (const surface of (businessContext.domains.referrals || [])) {
80
+ if (surface.type === 'api') {
81
+ endpoints.push({
82
+ url: surface.url,
83
+ method: surface.method || 'POST',
84
+ category: 'reward',
85
+ body: { action: 'claim' },
86
+ });
87
+ }
88
+ }
89
+
90
+ // Subscription endpoints
91
+ for (const surface of (businessContext.domains.subscriptions || [])) {
92
+ if (surface.type === 'api') {
93
+ endpoints.push({
94
+ url: surface.url,
95
+ method: surface.method || 'POST',
96
+ category: 'subscription',
97
+ body: { action: 'change' },
98
+ });
99
+ }
100
+ }
101
+
102
+ return endpoints;
103
+ }
104
+
105
+ /**
106
+ * Fire concurrent requests at an endpoint and analyze results.
107
+ */
108
+ async _testEndpoint(endpoint) {
109
+ try {
110
+ // Fire N concurrent identical requests
111
+ const requests = Array.from({ length: this.CONCURRENCY }, () =>
112
+ this._fireRequest(endpoint)
113
+ );
114
+
115
+ const results = await Promise.allSettled(requests);
116
+
117
+ // Analyze results
118
+ const successes = results.filter(r =>
119
+ r.status === 'fulfilled' && r.value?.ok
120
+ );
121
+ const failures = results.filter(r =>
122
+ r.status === 'fulfilled' && r.value && !r.value.ok
123
+ );
124
+ const errors = results.filter(r => r.status === 'rejected');
125
+
126
+ // If all requests succeeded, the endpoint may lack concurrency control
127
+ if (successes.length >= 2) {
128
+ // Check if responses are identical (indicating no state change detection)
129
+ const responseBodies = successes
130
+ .map(r => r.value?.body)
131
+ .filter(Boolean);
132
+
133
+ const allIdentical = responseBodies.length >= 2 &&
134
+ responseBodies.every(b => b === responseBodies[0]);
135
+
136
+ const severity = this._getSeverity(endpoint.category, successes.length);
137
+
138
+ return createFinding({
139
+ module: 'logic',
140
+ title: `Race Condition: ${this._getCategoryTitle(endpoint.category)}`,
141
+ severity,
142
+ affected_surface: endpoint.url,
143
+ description: `${this.CONCURRENCY} concurrent requests were fired at ${endpoint.url}. ${successes.length}/${this.CONCURRENCY} succeeded, suggesting the endpoint lacks concurrency control. This could lead to ${this._getImpact(endpoint.category)}.`,
144
+ reproduction: [
145
+ `1. Prepare ${this.CONCURRENCY} identical ${endpoint.method} requests to ${endpoint.url}`,
146
+ `2. Fire all requests simultaneously using Promise.all`,
147
+ `3. ${successes.length} requests returned success (2xx)`,
148
+ allIdentical
149
+ ? `4. All success responses were identical — no state change detected`
150
+ : `4. Responses varied — partial state corruption possible`,
151
+ ],
152
+ evidence: `Concurrent requests: ${this.CONCURRENCY}\nSuccesses: ${successes.length}\nFailures: ${failures.length}\nErrors: ${errors.length}\nIdentical responses: ${allIdentical}`,
153
+ remediation: this._getRemediation(endpoint.category),
154
+ });
155
+ }
156
+ } catch (err) {
157
+ this.logger?.debug?.(`Race test for ${endpoint.url} failed: ${err.message}`);
158
+ }
159
+
160
+ return null;
161
+ }
162
+
163
+ async _fireRequest(endpoint) {
164
+ try {
165
+ const controller = new AbortController();
166
+ const timeout = setTimeout(() => controller.abort(), 10000);
167
+
168
+ const response = await fetch(endpoint.url, {
169
+ method: endpoint.method,
170
+ headers: { 'Content-Type': 'application/json' },
171
+ body: JSON.stringify(endpoint.body),
172
+ signal: controller.signal,
173
+ });
174
+ clearTimeout(timeout);
175
+
176
+ const body = await response.text();
177
+ return { ok: response.ok, status: response.status, body };
178
+ } catch {
179
+ return null;
180
+ }
181
+ }
182
+
183
+ _getCategoryTitle(category) {
184
+ const titles = {
185
+ payment: 'Double Spend Risk',
186
+ inventory: 'Oversell Risk',
187
+ reward: 'Reward Farming',
188
+ subscription: 'State Corruption',
189
+ };
190
+ return titles[category] || 'Concurrent Modification';
191
+ }
192
+
193
+ _getImpact(category) {
194
+ const impacts = {
195
+ payment: 'double charges, double payouts, or multiple payment processing for a single transaction',
196
+ inventory: 'selling more items than available stock, inventory going negative',
197
+ reward: 'claiming the same reward multiple times, point/credit inflation',
198
+ subscription: 'corrupted subscription state, free access to paid tiers',
199
+ };
200
+ return impacts[category] || 'inconsistent state from concurrent modifications';
201
+ }
202
+
203
+ _getSeverity(category, successCount) {
204
+ if (category === 'payment' && successCount >= 2) return 'critical';
205
+ if (category === 'inventory' && successCount >= 3) return 'high';
206
+ if (category === 'reward' && successCount >= 3) return 'high';
207
+ return 'medium';
208
+ }
209
+
210
+ _getRemediation(category) {
211
+ const base = 'Implement concurrency control: ';
212
+ const remediations = {
213
+ payment: base + 'Use database-level locking (SELECT FOR UPDATE), idempotency keys, and optimistic concurrency control for all payment operations. Process payments through a queue to serialize concurrent requests.',
214
+ inventory: base + 'Use atomic database operations (UPDATE ... WHERE stock > 0) for inventory decrements. Implement pessimistic locking on stock-sensitive operations.',
215
+ reward: base + 'Use unique constraints on reward claims (user_id + reward_id). Implement rate limiting and claim deduplication. Process reward claims through a serialized queue.',
216
+ subscription: base + 'Use optimistic locking with version numbers on subscription records. Reject concurrent modifications with a 409 Conflict response.',
217
+ };
218
+ return remediations[category] || base + 'Use database-level locking and idempotency keys for state-changing operations.';
219
+ }
220
+ }
221
+
222
+ export default RaceConditionDetector;