openpen 0.2.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 (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/dist/checks/auth-bypass.d.ts +12 -0
  4. package/dist/checks/auth-bypass.js +93 -0
  5. package/dist/checks/bac.d.ts +12 -0
  6. package/dist/checks/bac.js +107 -0
  7. package/dist/checks/base.d.ts +22 -0
  8. package/dist/checks/base.js +13 -0
  9. package/dist/checks/index.d.ts +7 -0
  10. package/dist/checks/index.js +40 -0
  11. package/dist/checks/llm-leak.d.ts +23 -0
  12. package/dist/checks/llm-leak.js +251 -0
  13. package/dist/checks/mass-assignment.d.ts +12 -0
  14. package/dist/checks/mass-assignment.js +169 -0
  15. package/dist/checks/prompt-injection.d.ts +23 -0
  16. package/dist/checks/prompt-injection.js +262 -0
  17. package/dist/checks/security-headers.d.ts +12 -0
  18. package/dist/checks/security-headers.js +133 -0
  19. package/dist/checks/sensitive-data.d.ts +12 -0
  20. package/dist/checks/sensitive-data.js +122 -0
  21. package/dist/checks/sqli.d.ts +12 -0
  22. package/dist/checks/sqli.js +178 -0
  23. package/dist/checks/ssrf.d.ts +12 -0
  24. package/dist/checks/ssrf.js +126 -0
  25. package/dist/checks/xss.d.ts +12 -0
  26. package/dist/checks/xss.js +79 -0
  27. package/dist/cli.d.ts +5 -0
  28. package/dist/cli.js +300 -0
  29. package/dist/fuzzer/engine.d.ts +27 -0
  30. package/dist/fuzzer/engine.js +126 -0
  31. package/dist/fuzzer/mutator.d.ts +8 -0
  32. package/dist/fuzzer/mutator.js +54 -0
  33. package/dist/fuzzer/payloads.d.ts +13 -0
  34. package/dist/fuzzer/payloads.js +167 -0
  35. package/dist/reporter/index.d.ts +5 -0
  36. package/dist/reporter/index.js +5 -0
  37. package/dist/reporter/json.d.ts +5 -0
  38. package/dist/reporter/json.js +14 -0
  39. package/dist/reporter/terminal.d.ts +5 -0
  40. package/dist/reporter/terminal.js +59 -0
  41. package/dist/spec/openapi.d.ts +5 -0
  42. package/dist/spec/openapi.js +119 -0
  43. package/dist/spec/parser.d.ts +11 -0
  44. package/dist/spec/parser.js +45 -0
  45. package/dist/types.d.ts +145 -0
  46. package/dist/types.js +4 -0
  47. package/dist/utils/http.d.ts +37 -0
  48. package/dist/utils/http.js +92 -0
  49. package/dist/utils/logger.d.ts +8 -0
  50. package/dist/utils/logger.js +20 -0
  51. package/dist/ws/checks.d.ts +18 -0
  52. package/dist/ws/checks.js +558 -0
  53. package/dist/ws/engine.d.ts +47 -0
  54. package/dist/ws/engine.js +139 -0
  55. package/package.json +41 -0
package/dist/cli.js ADDED
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * openpen - Open source API fuzzing and penetration testing CLI
4
+ */
5
+ import { Command } from 'commander';
6
+ import { setVerbose, info, error } from './utils/logger.js';
7
+ import { loadTarget } from './spec/parser.js';
8
+ import { FuzzEngine } from './fuzzer/engine.js';
9
+ import { getAllChecks, getChecksByIds, listChecks } from './checks/index.js';
10
+ import { getAllWsChecks, getWsChecksByIds, listWsChecks } from './ws/checks.js';
11
+ import { reportTerminal, reportJson } from './reporter/index.js';
12
+ const program = new Command();
13
+ program
14
+ .name('openpen')
15
+ .description('Open source CLI for API fuzzing and penetration testing')
16
+ .version('0.1.0');
17
+ // scan command
18
+ program
19
+ .command('scan')
20
+ .description('Run OWASP security checks against an API')
21
+ .argument('<target>', 'Base URL of the API')
22
+ .option('--spec <path>', 'Path or URL to OpenAPI/Swagger spec')
23
+ .option('--checks <list>', 'Comma-separated check IDs to run')
24
+ .option('--exclude-checks <list>', 'Checks to skip')
25
+ .option('--auth-header <header>', 'Auth header (e.g. "Authorization: Bearer tok")', collect, [])
26
+ .option('--auth-token <token>', 'Bearer token shorthand')
27
+ .option('--method <methods>', 'HTTP methods to test (comma-separated)')
28
+ .option('--paths <paths>', 'Comma-separated paths to test (manual mode)')
29
+ .option('--depth <level>', 'Payload volume: shallow, normal, deep', 'normal')
30
+ .option('--output <file>', 'Write report to file')
31
+ .option('--format <fmt>', 'Output format: terminal, json', 'terminal')
32
+ .option('--timeout <ms>', 'Per-request timeout in ms', '10000')
33
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
34
+ .option('--rate-limit <n>', 'Max requests per second', '50')
35
+ .option('--verbose', 'Show individual request details')
36
+ .option('--no-color', 'Disable colored output')
37
+ .action(async (targetUrl, opts) => {
38
+ setVerbose(opts.verbose || false);
39
+ const auth = parseAuth(opts.authHeader, opts.authToken);
40
+ const target = await loadTarget(targetUrl, {
41
+ specPath: opts.spec,
42
+ methods: opts.method?.split(','),
43
+ paths: opts.paths?.split(','),
44
+ auth,
45
+ });
46
+ if (target.endpoints.length === 0) {
47
+ error('No endpoints found. Provide --spec or --paths.');
48
+ process.exit(1);
49
+ }
50
+ info(`\n OPENPEN v0.1.0 -- API Security Scanner\n`);
51
+ info(` Target: ${targetUrl}`);
52
+ info(` Endpoints: ${target.endpoints.length}`);
53
+ info(` Depth: ${opts.depth}`);
54
+ const config = {
55
+ depth: opts.depth,
56
+ checks: opts.checks ? opts.checks.split(',') : [],
57
+ excludeChecks: opts.excludeChecks ? opts.excludeChecks.split(',') : [],
58
+ timeout: parseInt(opts.timeout),
59
+ concurrency: parseInt(opts.concurrency),
60
+ rateLimit: parseInt(opts.rateLimit),
61
+ verbose: opts.verbose || false,
62
+ noColor: !opts.color,
63
+ };
64
+ const checks = config.checks.length > 0
65
+ ? getChecksByIds(config.checks)
66
+ : getAllChecks().filter(c => !config.excludeChecks.includes(c.id));
67
+ info(` Checks: ${checks.length} modules\n`);
68
+ const startedAt = new Date();
69
+ let totalRequests = 0;
70
+ const findings = [];
71
+ for (const check of checks) {
72
+ info(` Running: ${check.name}...`);
73
+ const result = await check.run(target, config);
74
+ findings.push(...result.findings);
75
+ totalRequests += result.requestCount;
76
+ }
77
+ const completedAt = new Date();
78
+ const report = {
79
+ target: targetUrl,
80
+ startedAt: startedAt.toISOString(),
81
+ completedAt: completedAt.toISOString(),
82
+ duration: completedAt.getTime() - startedAt.getTime(),
83
+ totalRequests,
84
+ findings,
85
+ summary: {
86
+ critical: findings.filter(f => f.severity === 'critical').length,
87
+ high: findings.filter(f => f.severity === 'high').length,
88
+ medium: findings.filter(f => f.severity === 'medium').length,
89
+ low: findings.filter(f => f.severity === 'low').length,
90
+ info: findings.filter(f => f.severity === 'info').length,
91
+ },
92
+ checksRun: checks.map(c => c.id),
93
+ };
94
+ if (opts.format === 'json') {
95
+ reportJson(report, opts.output);
96
+ }
97
+ else {
98
+ reportTerminal(report, !opts.color);
99
+ }
100
+ });
101
+ // fuzz command
102
+ program
103
+ .command('fuzz')
104
+ .description('Fuzz API endpoints with mutated payloads')
105
+ .argument('<target>', 'URL to fuzz')
106
+ .option('--method <method>', 'HTTP method', 'GET')
107
+ .option('--body <json>', 'Request body (use FUZZ as placeholder)')
108
+ .option('--headers <json>', 'Additional headers as JSON')
109
+ .option('--fuzz-params <list>', 'Parameters to fuzz (comma-separated)')
110
+ .option('--wordlist <name>', 'Wordlist: sqli, xss, path-traversal, ssrf, all', 'all')
111
+ .option('--detect <methods>', 'Anomaly detection: status,length,time,content', 'status,length,time,content')
112
+ .option('--baseline', 'Send clean request first for comparison')
113
+ .option('--auth-header <header>', 'Auth header', collect, [])
114
+ .option('--auth-token <token>', 'Bearer token shorthand')
115
+ .option('--timeout <ms>', 'Per-request timeout', '10000')
116
+ .option('--concurrency <n>', 'Max concurrent requests', '10')
117
+ .option('--rate-limit <n>', 'Max requests per second', '50')
118
+ .option('--output <file>', 'Write results to file')
119
+ .option('--verbose', 'Show each request/response')
120
+ .action(async (targetUrl, opts) => {
121
+ setVerbose(opts.verbose || false);
122
+ const auth = parseAuth(opts.authHeader, opts.authToken);
123
+ const engine = new FuzzEngine({
124
+ timeout: parseInt(opts.timeout),
125
+ concurrency: parseInt(opts.concurrency),
126
+ rateLimit: parseInt(opts.rateLimit),
127
+ verbose: opts.verbose || false,
128
+ });
129
+ info(`\n OPENPEN v0.1.0 -- Fuzzer\n`);
130
+ info(` Target: ${targetUrl}`);
131
+ info(` Method: ${opts.method}`);
132
+ info(` Wordlist: ${opts.wordlist}\n`);
133
+ const results = await engine.fuzz({
134
+ url: targetUrl,
135
+ method: opts.method,
136
+ body: opts.body,
137
+ headers: {
138
+ ...(opts.headers ? JSON.parse(opts.headers) : {}),
139
+ ...(auth?.headers || {}),
140
+ },
141
+ fuzzParams: opts.fuzzParams?.split(',') || [],
142
+ wordlist: opts.wordlist,
143
+ detect: opts.detect.split(','),
144
+ baseline: opts.baseline || false,
145
+ });
146
+ const anomalies = results.filter(r => r.anomalies.length > 0);
147
+ info(`\n Results: ${anomalies.length} anomalies from ${results.length} requests\n`);
148
+ for (const r of anomalies) {
149
+ info(` [${r.anomalies.join(',')}] ${r.payload} -> ${r.response.statusCode} (${r.response.responseTime}ms)`);
150
+ }
151
+ if (opts.output) {
152
+ const { writeFileSync } = await import('fs');
153
+ writeFileSync(opts.output, JSON.stringify(results, null, 2));
154
+ info(`\n Report written to ${opts.output}`);
155
+ }
156
+ });
157
+ // info command
158
+ program
159
+ .command('info')
160
+ .description('Reconnaissance - fingerprint an API')
161
+ .argument('<target>', 'Base URL of the API')
162
+ .option('--spec <path>', 'Path or URL to OpenAPI/Swagger spec')
163
+ .option('--auth-header <header>', 'Auth header', collect, [])
164
+ .option('--auth-token <token>', 'Bearer token shorthand')
165
+ .action(async (targetUrl, opts) => {
166
+ const auth = parseAuth(opts.authHeader, opts.authToken);
167
+ const target = await loadTarget(targetUrl, {
168
+ specPath: opts.spec,
169
+ auth,
170
+ });
171
+ info(`\n OPENPEN v0.1.0 -- API Recon\n`);
172
+ info(` Target: ${targetUrl}`);
173
+ info(` Endpoints: ${target.endpoints.length}\n`);
174
+ for (const ep of target.endpoints) {
175
+ const params = ep.parameters.map(p => `${p.name}(${p.in})`).join(', ');
176
+ info(` ${ep.method.padEnd(7)} ${ep.path}${params ? ' [' + params + ']' : ''}`);
177
+ }
178
+ // Fingerprint
179
+ try {
180
+ const res = await fetch(targetUrl, { signal: AbortSignal.timeout(5000) });
181
+ info(`\n Server Headers:`);
182
+ for (const [k, v] of res.headers) {
183
+ if (['server', 'x-powered-by', 'x-frame-options', 'x-content-type-options',
184
+ 'strict-transport-security', 'content-security-policy',
185
+ 'access-control-allow-origin'].includes(k.toLowerCase())) {
186
+ info(` ${k}: ${v}`);
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ info(`\n Could not reach ${targetUrl} for fingerprinting`);
192
+ }
193
+ });
194
+ // ws-test command
195
+ program
196
+ .command('ws-test')
197
+ .description('Run security checks against a WebSocket endpoint')
198
+ .argument('<target>', 'WebSocket URL (ws:// or wss://)')
199
+ .option('--checks <list>', 'Comma-separated check IDs to run')
200
+ .option('--exclude-checks <list>', 'Checks to skip')
201
+ .option('--timeout <ms>', 'Connection timeout in ms', '5000')
202
+ .option('--output <file>', 'Write report to file')
203
+ .option('--format <fmt>', 'Output format: terminal, json', 'terminal')
204
+ .option('--verbose', 'Show detailed test output')
205
+ .action(async (targetUrl, opts) => {
206
+ setVerbose(opts.verbose || false);
207
+ // Validate URL scheme
208
+ if (!targetUrl.startsWith('ws://') && !targetUrl.startsWith('wss://')) {
209
+ // Auto-upgrade http(s) to ws(s)
210
+ targetUrl = targetUrl.replace(/^https:/, 'wss:').replace(/^http:/, 'ws:');
211
+ if (!targetUrl.startsWith('ws://') && !targetUrl.startsWith('wss://')) {
212
+ targetUrl = `wss://${targetUrl}`;
213
+ }
214
+ }
215
+ const timeout = parseInt(opts.timeout);
216
+ const allChecks = opts.checks
217
+ ? getWsChecksByIds(opts.checks.split(','))
218
+ : getAllWsChecks().filter(c => !(opts.excludeChecks || '').split(',').includes(c.info.id));
219
+ info(`\n OPENPEN v0.1.0 -- WebSocket Security Tester\n`);
220
+ info(` Target: ${targetUrl}`);
221
+ info(` Checks: ${allChecks.length} modules\n`);
222
+ const startedAt = new Date();
223
+ const results = [];
224
+ for (const check of allChecks) {
225
+ info(` Running: ${check.info.name}...`);
226
+ const result = await check.run(targetUrl, timeout);
227
+ results.push(result);
228
+ const icon = result.status === 'pass' ? 'OK' :
229
+ result.status === 'fail' ? 'FAIL' :
230
+ result.status === 'warn' ? 'WARN' : 'ERR';
231
+ const sev = result.status === 'pass' ? '' : ` [${result.severity.toUpperCase()}]`;
232
+ info(` ${icon}${sev} ${result.description}`);
233
+ }
234
+ const completedAt = new Date();
235
+ const report = {
236
+ target: targetUrl,
237
+ startedAt: startedAt.toISOString(),
238
+ completedAt: completedAt.toISOString(),
239
+ duration: completedAt.getTime() - startedAt.getTime(),
240
+ results,
241
+ summary: {
242
+ pass: results.filter(r => r.status === 'pass').length,
243
+ fail: results.filter(r => r.status === 'fail').length,
244
+ warn: results.filter(r => r.status === 'warn').length,
245
+ error: results.filter(r => r.status === 'error').length,
246
+ },
247
+ };
248
+ info(`\n Summary: ${report.summary.pass} pass, ${report.summary.fail} fail, ${report.summary.warn} warn, ${report.summary.error} error`);
249
+ info(` Duration: ${report.duration}ms\n`);
250
+ if (opts.format === 'json' || opts.output) {
251
+ const json = JSON.stringify(report, null, 2);
252
+ if (opts.output) {
253
+ const { writeFileSync } = await import('fs');
254
+ writeFileSync(opts.output, json);
255
+ info(` Report written to ${opts.output}\n`);
256
+ }
257
+ else {
258
+ console.log(json);
259
+ }
260
+ }
261
+ });
262
+ // list-checks command
263
+ program
264
+ .command('list-checks')
265
+ .description('List available security check modules')
266
+ .action(() => {
267
+ info(`\n OPENPEN v0.1.0 -- Available Checks\n`);
268
+ info(` HTTP API Checks:`);
269
+ const checks = listChecks();
270
+ for (const c of checks) {
271
+ info(` ${c.id.padEnd(20)} ${c.owaspCategory.padEnd(25)} ${c.name}`);
272
+ }
273
+ info(`\n WebSocket Checks:`);
274
+ const wsChecks = listWsChecks();
275
+ for (const c of wsChecks) {
276
+ info(` ${c.id.padEnd(20)} ${''.padEnd(25)} ${c.name}`);
277
+ }
278
+ info('');
279
+ });
280
+ // Helpers
281
+ function collect(val, prev) {
282
+ prev.push(val);
283
+ return prev;
284
+ }
285
+ function parseAuth(headers, token) {
286
+ const result = {};
287
+ for (const h of headers) {
288
+ const idx = h.indexOf(':');
289
+ if (idx > 0) {
290
+ result[h.slice(0, idx).trim()] = h.slice(idx + 1).trim();
291
+ }
292
+ }
293
+ if (token) {
294
+ result['Authorization'] = `Bearer ${token}`;
295
+ }
296
+ if (Object.keys(result).length === 0)
297
+ return undefined;
298
+ return { headers: result };
299
+ }
300
+ program.parse();
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Core fuzzing orchestrator
3
+ */
4
+ import type { FuzzResult } from '../types.js';
5
+ export interface FuzzEngineConfig {
6
+ timeout: number;
7
+ concurrency: number;
8
+ rateLimit: number;
9
+ verbose: boolean;
10
+ }
11
+ export interface FuzzRequest {
12
+ url: string;
13
+ method: string;
14
+ body?: string;
15
+ headers: Record<string, string>;
16
+ fuzzParams: string[];
17
+ wordlist: string;
18
+ detect: string[];
19
+ baseline: boolean;
20
+ }
21
+ export declare class FuzzEngine {
22
+ private semaphore;
23
+ private rateLimiter;
24
+ private config;
25
+ constructor(config: FuzzEngineConfig);
26
+ fuzz(req: FuzzRequest): Promise<FuzzResult[]>;
27
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Core fuzzing orchestrator
3
+ */
4
+ import { sendRequest, Semaphore, RateLimiter } from '../utils/http.js';
5
+ import { getPayloads } from './payloads.js';
6
+ import { info, verbose } from '../utils/logger.js';
7
+ export class FuzzEngine {
8
+ semaphore;
9
+ rateLimiter;
10
+ config;
11
+ constructor(config) {
12
+ this.config = config;
13
+ this.semaphore = new Semaphore(config.concurrency);
14
+ this.rateLimiter = new RateLimiter(config.rateLimit);
15
+ }
16
+ async fuzz(req) {
17
+ const payloads = getPayloads(req.wordlist);
18
+ info(` Payloads: ${payloads.length}`);
19
+ // Get baseline if requested
20
+ let baselineResponse;
21
+ if (req.baseline) {
22
+ verbose(' Getting baseline response...');
23
+ const cleanUrl = req.url.replace(/FUZZ/g, 'test');
24
+ const cleanBody = req.body?.replace(/FUZZ/g, 'test');
25
+ try {
26
+ const res = await sendRequest({
27
+ url: cleanUrl,
28
+ method: req.method,
29
+ headers: req.headers,
30
+ body: cleanBody,
31
+ timeout: this.config.timeout,
32
+ });
33
+ baselineResponse = res.response;
34
+ verbose(` Baseline: ${baselineResponse.statusCode} (${baselineResponse.responseTime}ms, ${baselineResponse.bodySnippet.length} bytes)`);
35
+ }
36
+ catch {
37
+ verbose(' Baseline request failed, continuing without');
38
+ }
39
+ }
40
+ const results = [];
41
+ const detectors = new Set(req.detect);
42
+ const tasks = payloads.map(payload => async () => {
43
+ await this.semaphore.acquire();
44
+ try {
45
+ await this.rateLimiter.wait();
46
+ const fuzzedUrl = req.url.replace(/FUZZ/g, encodeURIComponent(payload));
47
+ const fuzzedBody = req.body?.replace(/FUZZ/g, payload);
48
+ const res = await sendRequest({
49
+ url: fuzzedUrl,
50
+ method: req.method,
51
+ headers: req.headers,
52
+ body: fuzzedBody,
53
+ timeout: this.config.timeout,
54
+ });
55
+ const anomalies = detectAnomalies(res.response, baselineResponse, payload, detectors);
56
+ results.push({
57
+ payload,
58
+ request: res.request,
59
+ response: res.response,
60
+ anomalies,
61
+ baseline: baselineResponse,
62
+ });
63
+ }
64
+ catch (err) {
65
+ verbose(` Error fuzzing with payload "${payload.slice(0, 30)}": ${err}`);
66
+ }
67
+ finally {
68
+ this.semaphore.release();
69
+ }
70
+ });
71
+ // Execute all fuzz tasks
72
+ await Promise.all(tasks.map(t => t()));
73
+ return results;
74
+ }
75
+ }
76
+ function detectAnomalies(response, baseline, payload, detectors) {
77
+ const anomalies = [];
78
+ // Status code change
79
+ if (detectors.has('status') && baseline) {
80
+ if (response.statusCode !== baseline.statusCode) {
81
+ // 500 errors are especially interesting
82
+ if (response.statusCode >= 500) {
83
+ anomalies.push('status_change');
84
+ }
85
+ else if (Math.floor(response.statusCode / 100) !== Math.floor(baseline.statusCode / 100)) {
86
+ anomalies.push('status_change');
87
+ }
88
+ }
89
+ }
90
+ // Response length anomaly
91
+ if (detectors.has('length') && baseline) {
92
+ const baseLen = baseline.bodySnippet.length;
93
+ const resLen = response.bodySnippet.length;
94
+ if (baseLen > 0) {
95
+ const ratio = Math.abs(resLen - baseLen) / baseLen;
96
+ if (ratio > 0.5) {
97
+ anomalies.push('length_anomaly');
98
+ }
99
+ }
100
+ }
101
+ // Timing anomaly (possible time-based injection)
102
+ if (detectors.has('time') && baseline) {
103
+ if (response.responseTime > baseline.responseTime * 3 && response.responseTime > 3000) {
104
+ anomalies.push('time_anomaly');
105
+ }
106
+ }
107
+ // Error string detection
108
+ if (detectors.has('content')) {
109
+ const body = response.bodySnippet.toLowerCase();
110
+ const errorPatterns = [
111
+ 'sql syntax', 'mysql', 'postgresql', 'sqlite', 'ora-',
112
+ 'syntax error', 'uncaught exception', 'stack trace',
113
+ 'internal server error', 'fatal error',
114
+ 'root:', '/etc/passwd', '/bin/sh',
115
+ 'access denied', 'permission denied',
116
+ ];
117
+ if (errorPatterns.some(p => body.includes(p))) {
118
+ anomalies.push('error_string');
119
+ }
120
+ // Check for payload reflection (potential XSS)
121
+ if (response.bodySnippet.includes(payload)) {
122
+ anomalies.push('reflection');
123
+ }
124
+ }
125
+ return anomalies;
126
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Payload mutation strategies - encoding, case variation, null bytes
3
+ */
4
+ export type MutationStrategy = 'url-encode' | 'double-encode' | 'unicode' | 'case-swap' | 'null-byte' | 'concat';
5
+ /**
6
+ * Apply a set of mutations to a payload, returning all variants
7
+ */
8
+ export declare function mutatePayload(payload: string, strategies?: MutationStrategy[]): string[];
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Payload mutation strategies - encoding, case variation, null bytes
3
+ */
4
+ /**
5
+ * Apply a set of mutations to a payload, returning all variants
6
+ */
7
+ export function mutatePayload(payload, strategies) {
8
+ const strats = strategies || ['url-encode', 'double-encode', 'case-swap', 'null-byte'];
9
+ const results = [payload]; // always include original
10
+ for (const s of strats) {
11
+ const mutated = applyMutation(payload, s);
12
+ if (mutated !== payload) {
13
+ results.push(mutated);
14
+ }
15
+ }
16
+ return results;
17
+ }
18
+ function applyMutation(payload, strategy) {
19
+ switch (strategy) {
20
+ case 'url-encode':
21
+ return urlEncode(payload);
22
+ case 'double-encode':
23
+ return urlEncode(urlEncode(payload));
24
+ case 'unicode':
25
+ return unicodeEscape(payload);
26
+ case 'case-swap':
27
+ return caseSwap(payload);
28
+ case 'null-byte':
29
+ return payload + '%00';
30
+ case 'concat':
31
+ return concatSplit(payload);
32
+ }
33
+ }
34
+ function urlEncode(s) {
35
+ return encodeURIComponent(s);
36
+ }
37
+ function unicodeEscape(s) {
38
+ return s.replace(/[<>'"/\\]/g, ch => {
39
+ const code = ch.charCodeAt(0);
40
+ return `\\u${code.toString(16).padStart(4, '0')}`;
41
+ });
42
+ }
43
+ function caseSwap(s) {
44
+ return s.replace(/[a-zA-Z]/g, ch => {
45
+ return ch === ch.toLowerCase() ? ch.toUpperCase() : ch.toLowerCase();
46
+ });
47
+ }
48
+ function concatSplit(s) {
49
+ // Split strings at midpoint with concat operator
50
+ if (s.length < 4)
51
+ return s;
52
+ const mid = Math.floor(s.length / 2);
53
+ return s.slice(0, mid) + "'+'" + s.slice(mid);
54
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Built-in payload dictionaries for fuzzing
3
+ */
4
+ export declare const SQLI_PAYLOADS: string[];
5
+ export declare const XSS_PAYLOADS: string[];
6
+ export declare const PATH_TRAVERSAL_PAYLOADS: string[];
7
+ export declare const SSRF_PAYLOADS: string[];
8
+ export declare const NOSQL_PAYLOADS: string[];
9
+ export declare const COMMAND_INJECTION_PAYLOADS: string[];
10
+ export declare const PROMPT_INJECTION_PAYLOADS: string[];
11
+ export declare const LLM_LEAK_PAYLOADS: string[];
12
+ export type WordlistName = 'sqli' | 'xss' | 'path-traversal' | 'ssrf' | 'nosql' | 'command-injection' | 'prompt-injection' | 'llm-leak' | 'all';
13
+ export declare function getPayloads(name: WordlistName): string[];