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,291 @@
|
|
|
1
|
+
import { createFinding } from '../../utils/finding.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AbusePatternScanner — Tests for referral, reward, and subscription abuse.
|
|
5
|
+
*
|
|
6
|
+
* Probes:
|
|
7
|
+
* - Self-referral (use own referral code)
|
|
8
|
+
* - Referral loop (circular chains)
|
|
9
|
+
* - Reward farming (rapid repeated claims)
|
|
10
|
+
* - Trial abuse (reset trial limits)
|
|
11
|
+
* - Subscription manipulation (downgrade/cancel/reinstate exploits)
|
|
12
|
+
*/
|
|
13
|
+
export class AbusePatternScanner {
|
|
14
|
+
constructor(logger) {
|
|
15
|
+
this.logger = logger;
|
|
16
|
+
|
|
17
|
+
this.TRIAL_RESET_INDICATORS = [
|
|
18
|
+
/trial.*start/i, /free.*trial/i, /trial.*activated/i,
|
|
19
|
+
/days.*remaining/i, /trial.*extended/i,
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Scan for abuse patterns in business context surfaces.
|
|
25
|
+
*/
|
|
26
|
+
async scan(businessContext, surfaceInventory) {
|
|
27
|
+
const findings = [];
|
|
28
|
+
|
|
29
|
+
this.logger?.info?.('Abuse Pattern Scanner: starting tests');
|
|
30
|
+
|
|
31
|
+
// 1. Self-referral testing
|
|
32
|
+
const referralFindings = await this._testSelfReferral(businessContext);
|
|
33
|
+
findings.push(...referralFindings);
|
|
34
|
+
|
|
35
|
+
// 2. Reward farming
|
|
36
|
+
const rewardFindings = await this._testRewardFarming(businessContext);
|
|
37
|
+
findings.push(...rewardFindings);
|
|
38
|
+
|
|
39
|
+
// 3. Trial abuse
|
|
40
|
+
const trialFindings = await this._testTrialAbuse(businessContext, surfaceInventory);
|
|
41
|
+
findings.push(...trialFindings);
|
|
42
|
+
|
|
43
|
+
// 4. Subscription manipulation
|
|
44
|
+
const subFindings = await this._testSubscriptionAbuse(businessContext);
|
|
45
|
+
findings.push(...subFindings);
|
|
46
|
+
|
|
47
|
+
this.logger?.info?.(`Abuse Pattern Scanner: found ${findings.length} issues`);
|
|
48
|
+
return findings;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Test if a user can use their own referral code.
|
|
53
|
+
*/
|
|
54
|
+
async _testSelfReferral(businessContext) {
|
|
55
|
+
const findings = [];
|
|
56
|
+
const referralSurfaces = businessContext.domains.referrals || [];
|
|
57
|
+
|
|
58
|
+
for (const surface of referralSurfaces) {
|
|
59
|
+
if (surface.type !== 'api' && surface.type !== 'form') continue;
|
|
60
|
+
|
|
61
|
+
const url = surface.url;
|
|
62
|
+
// Try self-referral patterns
|
|
63
|
+
const selfReferralBodies = [
|
|
64
|
+
{ referral_code: 'SELF', user_id: '1', referred_by: '1' },
|
|
65
|
+
{ code: 'TEST123', self: true },
|
|
66
|
+
{ referral: 'self', action: 'apply' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const body of selfReferralBodies) {
|
|
70
|
+
try {
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
73
|
+
|
|
74
|
+
const response = await fetch(url, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json' },
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
signal: controller.signal,
|
|
79
|
+
});
|
|
80
|
+
clearTimeout(timeout);
|
|
81
|
+
|
|
82
|
+
if (response.ok) {
|
|
83
|
+
const text = await response.text();
|
|
84
|
+
if (/success|applied|reward|credit|bonus|points/i.test(text)) {
|
|
85
|
+
findings.push(createFinding({
|
|
86
|
+
module: 'logic',
|
|
87
|
+
title: 'Referral Abuse: Self-Referral Accepted',
|
|
88
|
+
severity: 'high',
|
|
89
|
+
affected_surface: url,
|
|
90
|
+
description: `The referral endpoint at ${url} accepted a self-referral request, allowing a user to earn referral rewards for referring themselves. This can be exploited to farm unlimited credits/points.`,
|
|
91
|
+
reproduction: [
|
|
92
|
+
`1. POST to ${url} with self-referral data`,
|
|
93
|
+
`2. Server returns success response`,
|
|
94
|
+
`3. Repeat to accumulate rewards`,
|
|
95
|
+
],
|
|
96
|
+
evidence: `Response contained reward/success indicators`,
|
|
97
|
+
remediation: 'Block self-referral by comparing referrer and referred user IDs server-side. Implement IP, email, and device fingerprint deduplication for referral programs.',
|
|
98
|
+
}));
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return findings;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Test rapid reward claiming (farming).
|
|
113
|
+
*/
|
|
114
|
+
async _testRewardFarming(businessContext) {
|
|
115
|
+
const findings = [];
|
|
116
|
+
const rewardSurfaces = businessContext.domains.referrals || [];
|
|
117
|
+
|
|
118
|
+
for (const surface of rewardSurfaces) {
|
|
119
|
+
if (surface.type !== 'api') continue;
|
|
120
|
+
|
|
121
|
+
const url = surface.url;
|
|
122
|
+
const body = { action: 'claim', reward_id: '1' };
|
|
123
|
+
|
|
124
|
+
// Fire 5 rapid claims
|
|
125
|
+
try {
|
|
126
|
+
const results = await Promise.allSettled(
|
|
127
|
+
Array.from({ length: 5 }, () =>
|
|
128
|
+
fetch(url, {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: { 'Content-Type': 'application/json' },
|
|
131
|
+
body: JSON.stringify(body),
|
|
132
|
+
signal: AbortSignal.timeout(5000),
|
|
133
|
+
}).then(async r => ({ ok: r.ok, text: await r.text() })).catch(() => null)
|
|
134
|
+
)
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const successes = results.filter(r =>
|
|
138
|
+
r.status === 'fulfilled' && r.value?.ok
|
|
139
|
+
).length;
|
|
140
|
+
|
|
141
|
+
if (successes >= 3) {
|
|
142
|
+
findings.push(createFinding({
|
|
143
|
+
module: 'logic',
|
|
144
|
+
title: 'Reward Farming: No Rate Limiting',
|
|
145
|
+
severity: 'high',
|
|
146
|
+
affected_surface: url,
|
|
147
|
+
description: `The reward endpoint at ${url} accepted ${successes}/5 rapid consecutive claims without rate limiting. An attacker can farm unlimited rewards by scripting rapid requests.`,
|
|
148
|
+
reproduction: [
|
|
149
|
+
`1. Send 5 rapid POST requests to ${url}`,
|
|
150
|
+
`2. ${successes} requests succeeded`,
|
|
151
|
+
`3. No rate limit or cooldown detected`,
|
|
152
|
+
],
|
|
153
|
+
evidence: `Rapid claims accepted: ${successes}/5`,
|
|
154
|
+
remediation: 'Implement per-user rate limiting on reward claims. Add cooldown periods between claims. Use unique claim tokens that can only be used once.',
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return findings;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Test trial period abuse (reset trial via re-registration).
|
|
167
|
+
*/
|
|
168
|
+
async _testTrialAbuse(businessContext, surfaceInventory) {
|
|
169
|
+
const findings = [];
|
|
170
|
+
const subSurfaces = businessContext.domains.subscriptions || [];
|
|
171
|
+
const baseUrl = this._getBaseUrl(surfaceInventory);
|
|
172
|
+
if (!baseUrl) return findings;
|
|
173
|
+
|
|
174
|
+
// Check trial-related endpoints
|
|
175
|
+
const trialPaths = ['/trial', '/free-trial', '/start-trial', '/api/trial', '/api/subscription/trial'];
|
|
176
|
+
|
|
177
|
+
for (const path of trialPaths) {
|
|
178
|
+
try {
|
|
179
|
+
const url = new URL(path, baseUrl).href;
|
|
180
|
+
const controller = new AbortController();
|
|
181
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
182
|
+
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: { 'Content-Type': 'application/json' },
|
|
186
|
+
body: JSON.stringify({ email: 'test@example.com', action: 'start_trial' }),
|
|
187
|
+
signal: controller.signal,
|
|
188
|
+
});
|
|
189
|
+
clearTimeout(timeout);
|
|
190
|
+
|
|
191
|
+
if (response.ok) {
|
|
192
|
+
const text = await response.text();
|
|
193
|
+
if (this.TRIAL_RESET_INDICATORS.some(p => p.test(text))) {
|
|
194
|
+
findings.push(createFinding({
|
|
195
|
+
module: 'logic',
|
|
196
|
+
title: 'Trial Abuse: Trial Restart Possible',
|
|
197
|
+
severity: 'medium',
|
|
198
|
+
affected_surface: url,
|
|
199
|
+
description: `The trial endpoint at ${url} may allow restarting free trials. If the server doesn't track trial history by account/device/IP, users can create new accounts to get unlimited free trials.`,
|
|
200
|
+
reproduction: [
|
|
201
|
+
`1. POST to ${url} with trial start request`,
|
|
202
|
+
`2. Server responds with trial activation`,
|
|
203
|
+
`3. Repeat with different email addresses`,
|
|
204
|
+
],
|
|
205
|
+
evidence: `Response contained trial activation indicators`,
|
|
206
|
+
remediation: 'Track trial usage by multiple signals: email domain, device fingerprint, IP address, payment method. Limit trials to one per payment method or verified identity.',
|
|
207
|
+
}));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return findings;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Test subscription state manipulation.
|
|
220
|
+
*/
|
|
221
|
+
async _testSubscriptionAbuse(businessContext) {
|
|
222
|
+
const findings = [];
|
|
223
|
+
const subSurfaces = businessContext.domains.subscriptions || [];
|
|
224
|
+
|
|
225
|
+
for (const surface of subSurfaces) {
|
|
226
|
+
if (surface.type !== 'api') continue;
|
|
227
|
+
|
|
228
|
+
const url = surface.url;
|
|
229
|
+
|
|
230
|
+
// Test rapid state transitions (downgrade then immediately upgrade)
|
|
231
|
+
const transitions = [
|
|
232
|
+
{ action: 'downgrade', plan: 'free' },
|
|
233
|
+
{ action: 'upgrade', plan: 'premium' },
|
|
234
|
+
{ action: 'cancel' },
|
|
235
|
+
{ action: 'reinstate' },
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
let successCount = 0;
|
|
239
|
+
for (const transition of transitions) {
|
|
240
|
+
try {
|
|
241
|
+
const controller = new AbortController();
|
|
242
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
243
|
+
|
|
244
|
+
const response = await fetch(url, {
|
|
245
|
+
method: 'POST',
|
|
246
|
+
headers: { 'Content-Type': 'application/json' },
|
|
247
|
+
body: JSON.stringify(transition),
|
|
248
|
+
signal: controller.signal,
|
|
249
|
+
});
|
|
250
|
+
clearTimeout(timeout);
|
|
251
|
+
|
|
252
|
+
if (response.ok) successCount++;
|
|
253
|
+
} catch {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (successCount >= 3) {
|
|
259
|
+
findings.push(createFinding({
|
|
260
|
+
module: 'logic',
|
|
261
|
+
title: 'Subscription Abuse: Rapid State Transitions Accepted',
|
|
262
|
+
severity: 'high',
|
|
263
|
+
affected_surface: url,
|
|
264
|
+
description: `The subscription endpoint at ${url} accepted ${successCount}/4 rapid state transitions (downgrade → upgrade → cancel → reinstate) without validation. This could allow users to game billing cycles, retain features after downgrade, or exploit prorated refunds.`,
|
|
265
|
+
reproduction: [
|
|
266
|
+
`1. Downgrade subscription to free`,
|
|
267
|
+
`2. Immediately upgrade to premium`,
|
|
268
|
+
`3. Cancel subscription`,
|
|
269
|
+
`4. Reinstate subscription`,
|
|
270
|
+
`All ${successCount} transitions accepted without cooldown`,
|
|
271
|
+
],
|
|
272
|
+
evidence: `Rapid transitions accepted: ${successCount}/4`,
|
|
273
|
+
remediation: 'Implement state machine validation for subscription transitions. Add cooldown periods between state changes. Validate billing implications server-side before accepting transitions. Log all state changes for audit.',
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return findings;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_getBaseUrl(surfaceInventory) {
|
|
282
|
+
const pages = surfaceInventory.pages || [];
|
|
283
|
+
if (pages.length === 0) return null;
|
|
284
|
+
try {
|
|
285
|
+
const parsed = new URL(pages[0].url || pages[0]);
|
|
286
|
+
return `${parsed.protocol}//${parsed.host}`;
|
|
287
|
+
} catch { return null; }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export default AbusePatternScanner;
|