@vulcn/engine 0.1.0 → 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.
- package/CHANGELOG.md +1 -1
- package/LICENSE +662 -21
- package/README.md +1 -1
- package/dist/index.cjs +627 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +392 -30
- package/dist/index.d.ts +392 -30
- package/dist/index.js +614 -182
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,25 +17,32 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
23
|
-
BUILTIN_PAYLOADS: () => BUILTIN_PAYLOADS,
|
|
24
33
|
BrowserNotFoundError: () => BrowserNotFoundError,
|
|
34
|
+
PLUGIN_API_VERSION: () => PLUGIN_API_VERSION,
|
|
35
|
+
PluginManager: () => PluginManager,
|
|
25
36
|
Recorder: () => Recorder,
|
|
26
37
|
Runner: () => Runner,
|
|
27
38
|
SessionSchema: () => SessionSchema,
|
|
28
39
|
StepSchema: () => StepSchema,
|
|
29
40
|
checkBrowsers: () => checkBrowsers,
|
|
30
41
|
createSession: () => createSession,
|
|
31
|
-
getPayload: () => getPayload,
|
|
32
|
-
getPayloadNames: () => getPayloadNames,
|
|
33
|
-
getPayloadsByCategory: () => getPayloadsByCategory,
|
|
34
42
|
installBrowsers: () => installBrowsers,
|
|
35
43
|
launchBrowser: () => launchBrowser,
|
|
36
44
|
parseSession: () => parseSession,
|
|
45
|
+
pluginManager: () => pluginManager,
|
|
37
46
|
serializeSession: () => serializeSession
|
|
38
47
|
});
|
|
39
48
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -228,16 +237,347 @@ async function checkBrowsers() {
|
|
|
228
237
|
return results;
|
|
229
238
|
}
|
|
230
239
|
|
|
240
|
+
// plugin-manager.ts
|
|
241
|
+
var import_promises = require("fs/promises");
|
|
242
|
+
var import_node_fs = require("fs");
|
|
243
|
+
var import_node_path = require("path");
|
|
244
|
+
var import_yaml2 = __toESM(require("yaml"), 1);
|
|
245
|
+
var import_zod2 = require("zod");
|
|
246
|
+
|
|
247
|
+
// plugin-types.ts
|
|
248
|
+
var PLUGIN_API_VERSION = 1;
|
|
249
|
+
|
|
250
|
+
// plugin-manager.ts
|
|
251
|
+
var ENGINE_VERSION = "0.2.0";
|
|
252
|
+
var VulcnConfigSchema = import_zod2.z.object({
|
|
253
|
+
version: import_zod2.z.string().default("1"),
|
|
254
|
+
plugins: import_zod2.z.array(
|
|
255
|
+
import_zod2.z.object({
|
|
256
|
+
name: import_zod2.z.string(),
|
|
257
|
+
config: import_zod2.z.record(import_zod2.z.unknown()).optional(),
|
|
258
|
+
enabled: import_zod2.z.boolean().default(true)
|
|
259
|
+
})
|
|
260
|
+
).optional(),
|
|
261
|
+
settings: import_zod2.z.object({
|
|
262
|
+
browser: import_zod2.z.enum(["chromium", "firefox", "webkit"]).optional(),
|
|
263
|
+
headless: import_zod2.z.boolean().optional(),
|
|
264
|
+
timeout: import_zod2.z.number().optional()
|
|
265
|
+
}).optional()
|
|
266
|
+
});
|
|
267
|
+
var PluginManager = class {
|
|
268
|
+
plugins = [];
|
|
269
|
+
config = null;
|
|
270
|
+
initialized = false;
|
|
271
|
+
/**
|
|
272
|
+
* Shared context passed to all plugins
|
|
273
|
+
*/
|
|
274
|
+
sharedPayloads = [];
|
|
275
|
+
sharedFindings = [];
|
|
276
|
+
/**
|
|
277
|
+
* Load configuration from vulcn.config.yml
|
|
278
|
+
*/
|
|
279
|
+
async loadConfig(configPath) {
|
|
280
|
+
const paths = configPath ? [configPath] : [
|
|
281
|
+
"vulcn.config.yml",
|
|
282
|
+
"vulcn.config.yaml",
|
|
283
|
+
"vulcn.config.json",
|
|
284
|
+
".vulcnrc.yml",
|
|
285
|
+
".vulcnrc.yaml",
|
|
286
|
+
".vulcnrc.json"
|
|
287
|
+
];
|
|
288
|
+
for (const path of paths) {
|
|
289
|
+
const resolved = (0, import_node_path.isAbsolute)(path) ? path : (0, import_node_path.resolve)(process.cwd(), path);
|
|
290
|
+
if ((0, import_node_fs.existsSync)(resolved)) {
|
|
291
|
+
const content = await (0, import_promises.readFile)(resolved, "utf-8");
|
|
292
|
+
const parsed = path.endsWith(".json") ? JSON.parse(content) : import_yaml2.default.parse(content);
|
|
293
|
+
this.config = VulcnConfigSchema.parse(parsed);
|
|
294
|
+
return this.config;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
this.config = { version: "1", plugins: [], settings: {} };
|
|
298
|
+
return this.config;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Load all plugins from config
|
|
302
|
+
*/
|
|
303
|
+
async loadPlugins() {
|
|
304
|
+
if (!this.config) {
|
|
305
|
+
await this.loadConfig();
|
|
306
|
+
}
|
|
307
|
+
const pluginConfigs = this.config?.plugins || [];
|
|
308
|
+
for (const pluginConfig of pluginConfigs) {
|
|
309
|
+
if (pluginConfig.enabled === false) continue;
|
|
310
|
+
try {
|
|
311
|
+
const loaded = await this.loadPlugin(pluginConfig);
|
|
312
|
+
this.plugins.push(loaded);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.error(
|
|
315
|
+
`Failed to load plugin ${pluginConfig.name}:`,
|
|
316
|
+
err instanceof Error ? err.message : String(err)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Load a single plugin
|
|
323
|
+
*/
|
|
324
|
+
async loadPlugin(config) {
|
|
325
|
+
const { name, config: pluginConfig = {} } = config;
|
|
326
|
+
let plugin;
|
|
327
|
+
let source;
|
|
328
|
+
if (name.startsWith("./") || name.startsWith("../") || (0, import_node_path.isAbsolute)(name)) {
|
|
329
|
+
const resolved = (0, import_node_path.isAbsolute)(name) ? name : (0, import_node_path.resolve)(process.cwd(), name);
|
|
330
|
+
const module2 = await import(resolved);
|
|
331
|
+
plugin = module2.default || module2;
|
|
332
|
+
source = "local";
|
|
333
|
+
} else if (name.startsWith("@vulcn/")) {
|
|
334
|
+
const module2 = await import(name);
|
|
335
|
+
plugin = module2.default || module2;
|
|
336
|
+
source = "npm";
|
|
337
|
+
} else {
|
|
338
|
+
const module2 = await import(name);
|
|
339
|
+
plugin = module2.default || module2;
|
|
340
|
+
source = "npm";
|
|
341
|
+
}
|
|
342
|
+
this.validatePlugin(plugin);
|
|
343
|
+
let resolvedConfig = pluginConfig;
|
|
344
|
+
if (plugin.configSchema) {
|
|
345
|
+
try {
|
|
346
|
+
resolvedConfig = plugin.configSchema.parse(pluginConfig);
|
|
347
|
+
} catch (err) {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`Invalid config for plugin ${name}: ${err instanceof Error ? err.message : String(err)}`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return {
|
|
354
|
+
plugin,
|
|
355
|
+
config: resolvedConfig,
|
|
356
|
+
source,
|
|
357
|
+
enabled: true
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Validate plugin structure
|
|
362
|
+
*/
|
|
363
|
+
validatePlugin(plugin) {
|
|
364
|
+
if (!plugin || typeof plugin !== "object") {
|
|
365
|
+
throw new Error("Plugin must be an object");
|
|
366
|
+
}
|
|
367
|
+
const p = plugin;
|
|
368
|
+
if (typeof p.name !== "string" || !p.name) {
|
|
369
|
+
throw new Error("Plugin must have a name");
|
|
370
|
+
}
|
|
371
|
+
if (typeof p.version !== "string" || !p.version) {
|
|
372
|
+
throw new Error("Plugin must have a version");
|
|
373
|
+
}
|
|
374
|
+
const apiVersion = p.apiVersion || 1;
|
|
375
|
+
if (apiVersion > PLUGIN_API_VERSION) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
`Plugin requires API version ${apiVersion}, but engine supports ${PLUGIN_API_VERSION}`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Add a plugin programmatically (for testing or dynamic loading)
|
|
383
|
+
*/
|
|
384
|
+
addPlugin(plugin, config = {}) {
|
|
385
|
+
this.validatePlugin(plugin);
|
|
386
|
+
this.plugins.push({
|
|
387
|
+
plugin,
|
|
388
|
+
config,
|
|
389
|
+
source: "custom",
|
|
390
|
+
enabled: true
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Initialize all plugins (call onInit hooks)
|
|
395
|
+
*/
|
|
396
|
+
async initialize() {
|
|
397
|
+
if (this.initialized) return;
|
|
398
|
+
for (const loaded of this.plugins) {
|
|
399
|
+
if (loaded.plugin.payloads) {
|
|
400
|
+
const payloads = typeof loaded.plugin.payloads === "function" ? await loaded.plugin.payloads() : loaded.plugin.payloads;
|
|
401
|
+
this.sharedPayloads.push(...payloads);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
await this.callHook("onInit", (hook, ctx) => hook(ctx));
|
|
405
|
+
this.initialized = true;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Destroy all plugins (call onDestroy hooks)
|
|
409
|
+
*/
|
|
410
|
+
async destroy() {
|
|
411
|
+
await this.callHook("onDestroy", (hook, ctx) => hook(ctx));
|
|
412
|
+
this.plugins = [];
|
|
413
|
+
this.sharedPayloads = [];
|
|
414
|
+
this.sharedFindings = [];
|
|
415
|
+
this.initialized = false;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get all loaded payloads
|
|
419
|
+
*/
|
|
420
|
+
getPayloads() {
|
|
421
|
+
return this.sharedPayloads;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get all collected findings
|
|
425
|
+
*/
|
|
426
|
+
getFindings() {
|
|
427
|
+
return this.sharedFindings;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Add a finding (used by detectors)
|
|
431
|
+
*/
|
|
432
|
+
addFinding(finding) {
|
|
433
|
+
this.sharedFindings.push(finding);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Add payloads (used by loaders)
|
|
437
|
+
*/
|
|
438
|
+
addPayloads(payloads) {
|
|
439
|
+
this.sharedPayloads.push(...payloads);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Clear findings (for new run)
|
|
443
|
+
*/
|
|
444
|
+
clearFindings() {
|
|
445
|
+
this.sharedFindings = [];
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Get loaded plugins
|
|
449
|
+
*/
|
|
450
|
+
getPlugins() {
|
|
451
|
+
return this.plugins;
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Check if a plugin is loaded by name
|
|
455
|
+
*/
|
|
456
|
+
hasPlugin(name) {
|
|
457
|
+
return this.plugins.some((p) => p.plugin.name === name);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Create base context for plugins
|
|
461
|
+
*/
|
|
462
|
+
createContext(pluginConfig) {
|
|
463
|
+
const engineInfo = {
|
|
464
|
+
version: ENGINE_VERSION,
|
|
465
|
+
pluginApiVersion: PLUGIN_API_VERSION
|
|
466
|
+
};
|
|
467
|
+
return {
|
|
468
|
+
config: pluginConfig,
|
|
469
|
+
engine: engineInfo,
|
|
470
|
+
payloads: this.sharedPayloads,
|
|
471
|
+
findings: this.sharedFindings,
|
|
472
|
+
logger: this.createLogger("plugin"),
|
|
473
|
+
fetch: globalThis.fetch
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Create scoped logger for a plugin
|
|
478
|
+
*/
|
|
479
|
+
createLogger(name) {
|
|
480
|
+
const prefix = `[${name}]`;
|
|
481
|
+
return {
|
|
482
|
+
debug: (msg, ...args) => console.debug(prefix, msg, ...args),
|
|
483
|
+
info: (msg, ...args) => console.info(prefix, msg, ...args),
|
|
484
|
+
warn: (msg, ...args) => console.warn(prefix, msg, ...args),
|
|
485
|
+
error: (msg, ...args) => console.error(prefix, msg, ...args)
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Call a hook on all plugins sequentially
|
|
490
|
+
*/
|
|
491
|
+
async callHook(hookName, executor) {
|
|
492
|
+
for (const loaded of this.plugins) {
|
|
493
|
+
const hook = loaded.plugin.hooks?.[hookName];
|
|
494
|
+
if (hook) {
|
|
495
|
+
const ctx = this.createContext(loaded.config);
|
|
496
|
+
ctx.logger = this.createLogger(loaded.plugin.name);
|
|
497
|
+
try {
|
|
498
|
+
await executor(hook, ctx);
|
|
499
|
+
} catch (err) {
|
|
500
|
+
console.error(
|
|
501
|
+
`Error in plugin ${loaded.plugin.name}.${hookName}:`,
|
|
502
|
+
err instanceof Error ? err.message : String(err)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Call a hook and collect results
|
|
510
|
+
*/
|
|
511
|
+
async callHookCollect(hookName, executor) {
|
|
512
|
+
const results = [];
|
|
513
|
+
for (const loaded of this.plugins) {
|
|
514
|
+
const hook = loaded.plugin.hooks?.[hookName];
|
|
515
|
+
if (hook) {
|
|
516
|
+
const ctx = this.createContext(loaded.config);
|
|
517
|
+
ctx.logger = this.createLogger(loaded.plugin.name);
|
|
518
|
+
try {
|
|
519
|
+
const result = await executor(
|
|
520
|
+
hook,
|
|
521
|
+
ctx
|
|
522
|
+
);
|
|
523
|
+
if (result !== null && result !== void 0) {
|
|
524
|
+
if (Array.isArray(result)) {
|
|
525
|
+
results.push(...result);
|
|
526
|
+
} else {
|
|
527
|
+
results.push(result);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
} catch (err) {
|
|
531
|
+
console.error(
|
|
532
|
+
`Error in plugin ${loaded.plugin.name}.${hookName}:`,
|
|
533
|
+
err instanceof Error ? err.message : String(err)
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return results;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Call a hook that transforms a value through the pipeline
|
|
542
|
+
*/
|
|
543
|
+
async callHookPipe(hookName, initial, executor) {
|
|
544
|
+
let value = initial;
|
|
545
|
+
for (const loaded of this.plugins) {
|
|
546
|
+
const hook = loaded.plugin.hooks?.[hookName];
|
|
547
|
+
if (hook) {
|
|
548
|
+
const ctx = this.createContext(loaded.config);
|
|
549
|
+
ctx.logger = this.createLogger(loaded.plugin.name);
|
|
550
|
+
try {
|
|
551
|
+
value = await executor(
|
|
552
|
+
hook,
|
|
553
|
+
value,
|
|
554
|
+
ctx
|
|
555
|
+
);
|
|
556
|
+
} catch (err) {
|
|
557
|
+
console.error(
|
|
558
|
+
`Error in plugin ${loaded.plugin.name}.${hookName}:`,
|
|
559
|
+
err instanceof Error ? err.message : String(err)
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return value;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
var pluginManager = new PluginManager();
|
|
568
|
+
|
|
231
569
|
// recorder.ts
|
|
232
570
|
var Recorder = class _Recorder {
|
|
233
571
|
/**
|
|
234
572
|
* Start a new recording session
|
|
235
573
|
* Opens a browser window for the user to interact with
|
|
236
574
|
*/
|
|
237
|
-
static async start(startUrl, options = {}) {
|
|
575
|
+
static async start(startUrl, options = {}, config = {}) {
|
|
576
|
+
const manager = config.pluginManager ?? pluginManager;
|
|
238
577
|
const browserType = options.browser ?? "chromium";
|
|
239
578
|
const viewport = options.viewport ?? { width: 1280, height: 720 };
|
|
240
579
|
const headless = options.headless ?? false;
|
|
580
|
+
await manager.initialize();
|
|
241
581
|
const { browser } = await launchBrowser({
|
|
242
582
|
browser: browserType,
|
|
243
583
|
headless
|
|
@@ -258,18 +598,61 @@ var Recorder = class _Recorder {
|
|
|
258
598
|
stepCounter++;
|
|
259
599
|
return `step_${String(stepCounter).padStart(3, "0")}`;
|
|
260
600
|
};
|
|
261
|
-
|
|
601
|
+
const baseRecordContext = {
|
|
602
|
+
startUrl,
|
|
603
|
+
browser: browserType,
|
|
604
|
+
page,
|
|
605
|
+
engine: { version: "0.2.0", pluginApiVersion: 1 },
|
|
606
|
+
payloads: manager.getPayloads(),
|
|
607
|
+
findings: manager.getFindings(),
|
|
608
|
+
logger: {
|
|
609
|
+
debug: console.debug.bind(console),
|
|
610
|
+
info: console.info.bind(console),
|
|
611
|
+
warn: console.warn.bind(console),
|
|
612
|
+
error: console.error.bind(console)
|
|
613
|
+
},
|
|
614
|
+
fetch: globalThis.fetch
|
|
615
|
+
};
|
|
616
|
+
await manager.callHook("onRecordStart", async (hook, ctx) => {
|
|
617
|
+
const recordCtx = { ...baseRecordContext, ...ctx };
|
|
618
|
+
await hook(recordCtx);
|
|
619
|
+
});
|
|
620
|
+
const initialStep = {
|
|
262
621
|
id: generateStepId(),
|
|
263
622
|
type: "navigate",
|
|
264
623
|
url: startUrl,
|
|
265
624
|
timestamp: 0
|
|
266
|
-
}
|
|
267
|
-
_Recorder.
|
|
625
|
+
};
|
|
626
|
+
const transformedInitialStep = await _Recorder.transformStep(
|
|
627
|
+
initialStep,
|
|
628
|
+
manager,
|
|
629
|
+
baseRecordContext
|
|
630
|
+
);
|
|
631
|
+
if (transformedInitialStep) {
|
|
632
|
+
steps.push(transformedInitialStep);
|
|
633
|
+
}
|
|
634
|
+
_Recorder.attachListeners(
|
|
635
|
+
page,
|
|
636
|
+
steps,
|
|
637
|
+
startTime,
|
|
638
|
+
generateStepId,
|
|
639
|
+
manager,
|
|
640
|
+
baseRecordContext
|
|
641
|
+
);
|
|
268
642
|
return {
|
|
269
643
|
async stop() {
|
|
270
644
|
session.steps = steps;
|
|
645
|
+
let finalSession = session;
|
|
646
|
+
for (const loaded of manager.getPlugins()) {
|
|
647
|
+
const hook = loaded.plugin.hooks?.onRecordEnd;
|
|
648
|
+
if (hook) {
|
|
649
|
+
const ctx = manager.createContext(loaded.config);
|
|
650
|
+
const recordCtx = { ...baseRecordContext, ...ctx };
|
|
651
|
+
finalSession = await hook(finalSession, recordCtx);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
271
654
|
await browser.close();
|
|
272
|
-
return
|
|
655
|
+
return finalSession;
|
|
273
656
|
},
|
|
274
657
|
getSteps() {
|
|
275
658
|
return [...steps];
|
|
@@ -279,8 +662,34 @@ var Recorder = class _Recorder {
|
|
|
279
662
|
}
|
|
280
663
|
};
|
|
281
664
|
}
|
|
282
|
-
|
|
665
|
+
/**
|
|
666
|
+
* Transform a step through plugin hooks
|
|
667
|
+
* Returns null if the step should be filtered out
|
|
668
|
+
*/
|
|
669
|
+
static async transformStep(step, manager, baseContext) {
|
|
670
|
+
let transformedStep = step;
|
|
671
|
+
for (const loaded of manager.getPlugins()) {
|
|
672
|
+
const hook = loaded.plugin.hooks?.onRecordStep;
|
|
673
|
+
if (hook) {
|
|
674
|
+
const ctx = manager.createContext(loaded.config);
|
|
675
|
+
const recordCtx = { ...baseContext, ...ctx };
|
|
676
|
+
transformedStep = await hook(transformedStep, recordCtx);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return transformedStep;
|
|
680
|
+
}
|
|
681
|
+
static attachListeners(page, steps, startTime, generateStepId, manager, baseContext) {
|
|
283
682
|
const getTimestamp = () => Date.now() - startTime;
|
|
683
|
+
const addStep = async (step) => {
|
|
684
|
+
const transformed = await _Recorder.transformStep(
|
|
685
|
+
step,
|
|
686
|
+
manager,
|
|
687
|
+
baseContext
|
|
688
|
+
);
|
|
689
|
+
if (transformed) {
|
|
690
|
+
steps.push(transformed);
|
|
691
|
+
}
|
|
692
|
+
};
|
|
284
693
|
page.on("framenavigated", (frame) => {
|
|
285
694
|
if (frame === page.mainFrame()) {
|
|
286
695
|
const url = frame.url();
|
|
@@ -288,7 +697,7 @@ var Recorder = class _Recorder {
|
|
|
288
697
|
if (steps.length > 0 && lastStep.type === "navigate" && lastStep.url === url) {
|
|
289
698
|
return;
|
|
290
699
|
}
|
|
291
|
-
|
|
700
|
+
addStep({
|
|
292
701
|
id: generateStepId(),
|
|
293
702
|
type: "navigate",
|
|
294
703
|
url,
|
|
@@ -298,12 +707,12 @@ var Recorder = class _Recorder {
|
|
|
298
707
|
});
|
|
299
708
|
page.exposeFunction(
|
|
300
709
|
"__vulcn_record",
|
|
301
|
-
(event) => {
|
|
710
|
+
async (event) => {
|
|
302
711
|
const timestamp = getTimestamp();
|
|
303
712
|
switch (event.type) {
|
|
304
713
|
case "click": {
|
|
305
714
|
const data = event.data;
|
|
306
|
-
|
|
715
|
+
await addStep({
|
|
307
716
|
id: generateStepId(),
|
|
308
717
|
type: "click",
|
|
309
718
|
selector: data.selector,
|
|
@@ -314,7 +723,7 @@ var Recorder = class _Recorder {
|
|
|
314
723
|
}
|
|
315
724
|
case "input": {
|
|
316
725
|
const data = event.data;
|
|
317
|
-
|
|
726
|
+
await addStep({
|
|
318
727
|
id: generateStepId(),
|
|
319
728
|
type: "input",
|
|
320
729
|
selector: data.selector,
|
|
@@ -326,7 +735,7 @@ var Recorder = class _Recorder {
|
|
|
326
735
|
}
|
|
327
736
|
case "keypress": {
|
|
328
737
|
const data = event.data;
|
|
329
|
-
|
|
738
|
+
await addStep({
|
|
330
739
|
id: generateStepId(),
|
|
331
740
|
type: "keypress",
|
|
332
741
|
key: data.key,
|
|
@@ -463,163 +872,156 @@ var Recorder = class _Recorder {
|
|
|
463
872
|
}
|
|
464
873
|
};
|
|
465
874
|
|
|
466
|
-
// payloads.ts
|
|
467
|
-
var BUILTIN_PAYLOADS = {
|
|
468
|
-
"xss-basic": {
|
|
469
|
-
name: "xss-basic",
|
|
470
|
-
category: "xss",
|
|
471
|
-
description: "Basic XSS payloads with script tags and event handlers",
|
|
472
|
-
payloads: [
|
|
473
|
-
'<script>alert("XSS")</script>',
|
|
474
|
-
'<img src=x onerror=alert("XSS")>',
|
|
475
|
-
'"><script>alert("XSS")</script>',
|
|
476
|
-
"javascript:alert('XSS')",
|
|
477
|
-
'<svg onload=alert("XSS")>'
|
|
478
|
-
],
|
|
479
|
-
detectPatterns: [
|
|
480
|
-
/<script[^>]*>alert\(/i,
|
|
481
|
-
/onerror\s*=\s*alert\(/i,
|
|
482
|
-
/onload\s*=\s*alert\(/i,
|
|
483
|
-
/javascript:alert\(/i
|
|
484
|
-
]
|
|
485
|
-
},
|
|
486
|
-
"xss-event": {
|
|
487
|
-
name: "xss-event",
|
|
488
|
-
category: "xss",
|
|
489
|
-
description: "XSS via event handlers",
|
|
490
|
-
payloads: [
|
|
491
|
-
'" onfocus="alert(1)" autofocus="',
|
|
492
|
-
"' onmouseover='alert(1)'",
|
|
493
|
-
'<body onload=alert("XSS")>',
|
|
494
|
-
"<input onfocus=alert(1) autofocus>",
|
|
495
|
-
"<marquee onstart=alert(1)>"
|
|
496
|
-
],
|
|
497
|
-
detectPatterns: [
|
|
498
|
-
/onfocus\s*=\s*["']?alert/i,
|
|
499
|
-
/onmouseover\s*=\s*["']?alert/i,
|
|
500
|
-
/onload\s*=\s*["']?alert/i,
|
|
501
|
-
/onstart\s*=\s*["']?alert/i
|
|
502
|
-
]
|
|
503
|
-
},
|
|
504
|
-
"xss-svg": {
|
|
505
|
-
name: "xss-svg",
|
|
506
|
-
category: "xss",
|
|
507
|
-
description: "XSS via SVG elements",
|
|
508
|
-
payloads: [
|
|
509
|
-
'<svg/onload=alert("XSS")>',
|
|
510
|
-
"<svg><script>alert(1)</script></svg>",
|
|
511
|
-
"<svg><animate onbegin=alert(1)>",
|
|
512
|
-
"<svg><set onbegin=alert(1)>"
|
|
513
|
-
],
|
|
514
|
-
detectPatterns: [
|
|
515
|
-
/<svg[^>]*onload\s*=/i,
|
|
516
|
-
/<svg[^>]*>.*<script>/i,
|
|
517
|
-
/onbegin\s*=\s*alert/i
|
|
518
|
-
]
|
|
519
|
-
},
|
|
520
|
-
"sqli-basic": {
|
|
521
|
-
name: "sqli-basic",
|
|
522
|
-
category: "sqli",
|
|
523
|
-
description: "Basic SQL injection payloads",
|
|
524
|
-
payloads: [
|
|
525
|
-
"' OR '1'='1",
|
|
526
|
-
"' OR '1'='1' --",
|
|
527
|
-
"1' OR '1'='1",
|
|
528
|
-
"admin'--",
|
|
529
|
-
"' UNION SELECT NULL--"
|
|
530
|
-
],
|
|
531
|
-
detectPatterns: [
|
|
532
|
-
/sql.*syntax/i,
|
|
533
|
-
/mysql.*error/i,
|
|
534
|
-
/ORA-\d{5}/i,
|
|
535
|
-
/pg_query/i,
|
|
536
|
-
/sqlite.*error/i,
|
|
537
|
-
/unclosed.*quotation/i
|
|
538
|
-
]
|
|
539
|
-
},
|
|
540
|
-
"sqli-error": {
|
|
541
|
-
name: "sqli-error",
|
|
542
|
-
category: "sqli",
|
|
543
|
-
description: "SQL injection payloads to trigger errors",
|
|
544
|
-
payloads: ["'", "''", "`", '"', "')", `'"`, "1' AND '1'='2", "1 AND 1=2"],
|
|
545
|
-
detectPatterns: [
|
|
546
|
-
/sql.*syntax/i,
|
|
547
|
-
/mysql.*error/i,
|
|
548
|
-
/ORA-\d{5}/i,
|
|
549
|
-
/postgresql.*error/i,
|
|
550
|
-
/sqlite.*error/i,
|
|
551
|
-
/quoted.*string.*properly.*terminated/i
|
|
552
|
-
]
|
|
553
|
-
},
|
|
554
|
-
"sqli-blind": {
|
|
555
|
-
name: "sqli-blind",
|
|
556
|
-
category: "sqli",
|
|
557
|
-
description: "Blind SQL injection payloads",
|
|
558
|
-
payloads: [
|
|
559
|
-
"1' AND SLEEP(5)--",
|
|
560
|
-
"1; WAITFOR DELAY '0:0:5'--",
|
|
561
|
-
"1' AND (SELECT COUNT(*) FROM information_schema.tables)>0--"
|
|
562
|
-
],
|
|
563
|
-
detectPatterns: [
|
|
564
|
-
// Blind SQLi is detected by timing, not content
|
|
565
|
-
]
|
|
566
|
-
}
|
|
567
|
-
};
|
|
568
|
-
function getPayload(name) {
|
|
569
|
-
return BUILTIN_PAYLOADS[name];
|
|
570
|
-
}
|
|
571
|
-
function getPayloadNames() {
|
|
572
|
-
return Object.keys(BUILTIN_PAYLOADS);
|
|
573
|
-
}
|
|
574
|
-
function getPayloadsByCategory(category) {
|
|
575
|
-
return Object.values(BUILTIN_PAYLOADS).filter((p) => p.category === category);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
875
|
// runner.ts
|
|
579
876
|
var Runner = class _Runner {
|
|
580
877
|
/**
|
|
581
|
-
* Execute a session with security payloads
|
|
878
|
+
* Execute a session with security payloads from plugins
|
|
879
|
+
*
|
|
880
|
+
* @param session - The recorded session to replay
|
|
881
|
+
* @param options - Runner configuration
|
|
882
|
+
* @param config - Plugin manager configuration
|
|
582
883
|
*/
|
|
583
|
-
static async execute(session,
|
|
884
|
+
static async execute(session, options = {}, config = {}) {
|
|
885
|
+
const manager = config.pluginManager ?? pluginManager;
|
|
584
886
|
const browserType = options.browser ?? session.browser ?? "chromium";
|
|
585
887
|
const headless = options.headless ?? true;
|
|
586
888
|
const startTime = Date.now();
|
|
587
|
-
const findings = [];
|
|
588
889
|
const errors = [];
|
|
589
890
|
let payloadsTested = 0;
|
|
891
|
+
await manager.initialize();
|
|
892
|
+
manager.clearFindings();
|
|
893
|
+
const payloads = manager.getPayloads();
|
|
894
|
+
if (payloads.length === 0) {
|
|
895
|
+
return {
|
|
896
|
+
findings: [],
|
|
897
|
+
stepsExecuted: session.steps.length,
|
|
898
|
+
payloadsTested: 0,
|
|
899
|
+
duration: Date.now() - startTime,
|
|
900
|
+
errors: [
|
|
901
|
+
"No payloads loaded. Add a payload plugin or configure payloads."
|
|
902
|
+
]
|
|
903
|
+
};
|
|
904
|
+
}
|
|
590
905
|
const { browser } = await launchBrowser({
|
|
591
906
|
browser: browserType,
|
|
592
907
|
headless
|
|
593
908
|
});
|
|
594
909
|
const context = await browser.newContext({ viewport: session.viewport });
|
|
595
910
|
const page = await context.newPage();
|
|
911
|
+
const baseRunContext = {
|
|
912
|
+
session,
|
|
913
|
+
page,
|
|
914
|
+
browser: browserType,
|
|
915
|
+
headless,
|
|
916
|
+
engine: { version: "0.2.0", pluginApiVersion: 1 },
|
|
917
|
+
payloads: manager.getPayloads(),
|
|
918
|
+
findings: manager.getFindings(),
|
|
919
|
+
logger: {
|
|
920
|
+
debug: console.debug.bind(console),
|
|
921
|
+
info: console.info.bind(console),
|
|
922
|
+
warn: console.warn.bind(console),
|
|
923
|
+
error: console.error.bind(console)
|
|
924
|
+
},
|
|
925
|
+
fetch: globalThis.fetch
|
|
926
|
+
};
|
|
927
|
+
await manager.callHook("onRunStart", async (hook, ctx) => {
|
|
928
|
+
const runCtx = { ...baseRunContext, ...ctx };
|
|
929
|
+
await hook(runCtx);
|
|
930
|
+
});
|
|
931
|
+
const eventFindings = [];
|
|
932
|
+
let currentDetectContext = null;
|
|
933
|
+
const dialogHandler = async (dialog) => {
|
|
934
|
+
if (currentDetectContext) {
|
|
935
|
+
const findings = await manager.callHookCollect(
|
|
936
|
+
"onDialog",
|
|
937
|
+
async (hook, ctx) => {
|
|
938
|
+
const detectCtx = {
|
|
939
|
+
...currentDetectContext,
|
|
940
|
+
...ctx
|
|
941
|
+
};
|
|
942
|
+
return hook(dialog, detectCtx);
|
|
943
|
+
}
|
|
944
|
+
);
|
|
945
|
+
eventFindings.push(...findings);
|
|
946
|
+
}
|
|
947
|
+
try {
|
|
948
|
+
await dialog.dismiss();
|
|
949
|
+
} catch {
|
|
950
|
+
}
|
|
951
|
+
};
|
|
952
|
+
const consoleHandler = async (msg) => {
|
|
953
|
+
if (currentDetectContext) {
|
|
954
|
+
const findings = await manager.callHookCollect("onConsoleMessage", async (hook, ctx) => {
|
|
955
|
+
const detectCtx = { ...currentDetectContext, ...ctx };
|
|
956
|
+
return hook(msg, detectCtx);
|
|
957
|
+
});
|
|
958
|
+
eventFindings.push(...findings);
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
page.on("dialog", dialogHandler);
|
|
962
|
+
page.on("console", consoleHandler);
|
|
596
963
|
try {
|
|
597
964
|
const injectableSteps = session.steps.filter(
|
|
598
965
|
(step) => step.type === "input" && step.injectable !== false
|
|
599
966
|
);
|
|
600
967
|
const allPayloads = [];
|
|
601
|
-
for (const
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
for (const value of payload.payloads) {
|
|
605
|
-
allPayloads.push({ name, value });
|
|
606
|
-
}
|
|
968
|
+
for (const payloadSet of payloads) {
|
|
969
|
+
for (const value of payloadSet.payloads) {
|
|
970
|
+
allPayloads.push({ payloadSet, value });
|
|
607
971
|
}
|
|
608
972
|
}
|
|
609
973
|
for (const injectableStep of injectableSteps) {
|
|
610
|
-
for (const
|
|
974
|
+
for (const { payloadSet, value: originalValue } of allPayloads) {
|
|
611
975
|
try {
|
|
612
|
-
|
|
976
|
+
let transformedPayload = originalValue;
|
|
977
|
+
for (const loaded of manager.getPlugins()) {
|
|
978
|
+
const hook = loaded.plugin.hooks?.onBeforePayload;
|
|
979
|
+
if (hook) {
|
|
980
|
+
const ctx = manager.createContext(loaded.config);
|
|
981
|
+
const runCtx = { ...baseRunContext, ...ctx };
|
|
982
|
+
transformedPayload = await hook(
|
|
983
|
+
transformedPayload,
|
|
984
|
+
injectableStep,
|
|
985
|
+
runCtx
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
currentDetectContext = {
|
|
990
|
+
...baseRunContext,
|
|
991
|
+
config: {},
|
|
992
|
+
step: injectableStep,
|
|
993
|
+
payloadSet,
|
|
994
|
+
payloadValue: transformedPayload,
|
|
995
|
+
stepId: injectableStep.id
|
|
996
|
+
};
|
|
997
|
+
await _Runner.replayWithPayload(
|
|
613
998
|
page,
|
|
614
999
|
session,
|
|
615
1000
|
injectableStep,
|
|
616
|
-
|
|
617
|
-
payload.value
|
|
1001
|
+
transformedPayload
|
|
618
1002
|
);
|
|
619
|
-
|
|
620
|
-
|
|
1003
|
+
const afterFindings = await manager.callHookCollect("onAfterPayload", async (hook, ctx) => {
|
|
1004
|
+
const detectCtx = {
|
|
1005
|
+
...currentDetectContext,
|
|
1006
|
+
...ctx
|
|
1007
|
+
};
|
|
1008
|
+
return hook(detectCtx);
|
|
1009
|
+
});
|
|
1010
|
+
const reflectionFinding = await _Runner.checkReflection(
|
|
1011
|
+
page,
|
|
1012
|
+
injectableStep,
|
|
1013
|
+
payloadSet,
|
|
1014
|
+
transformedPayload
|
|
1015
|
+
);
|
|
1016
|
+
const allFindings = [...afterFindings, ...eventFindings];
|
|
1017
|
+
if (reflectionFinding) {
|
|
1018
|
+
allFindings.push(reflectionFinding);
|
|
1019
|
+
}
|
|
1020
|
+
for (const finding of allFindings) {
|
|
1021
|
+
manager.addFinding(finding);
|
|
621
1022
|
options.onFinding?.(finding);
|
|
622
1023
|
}
|
|
1024
|
+
eventFindings.length = 0;
|
|
623
1025
|
payloadsTested++;
|
|
624
1026
|
} catch (err) {
|
|
625
1027
|
errors.push(`${injectableStep.id}: ${String(err)}`);
|
|
@@ -627,73 +1029,94 @@ var Runner = class _Runner {
|
|
|
627
1029
|
}
|
|
628
1030
|
}
|
|
629
1031
|
} finally {
|
|
1032
|
+
page.off("dialog", dialogHandler);
|
|
1033
|
+
page.off("console", consoleHandler);
|
|
1034
|
+
currentDetectContext = null;
|
|
630
1035
|
await browser.close();
|
|
631
1036
|
}
|
|
632
|
-
|
|
633
|
-
findings,
|
|
1037
|
+
let result = {
|
|
1038
|
+
findings: manager.getFindings(),
|
|
634
1039
|
stepsExecuted: session.steps.length,
|
|
635
1040
|
payloadsTested,
|
|
636
1041
|
duration: Date.now() - startTime,
|
|
637
1042
|
errors
|
|
638
1043
|
};
|
|
1044
|
+
for (const loaded of manager.getPlugins()) {
|
|
1045
|
+
const hook = loaded.plugin.hooks?.onRunEnd;
|
|
1046
|
+
if (hook) {
|
|
1047
|
+
const ctx = manager.createContext(loaded.config);
|
|
1048
|
+
const runCtx = { ...baseRunContext, ...ctx };
|
|
1049
|
+
result = await hook(result, runCtx);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Execute with explicit payloads (legacy API, for backwards compatibility)
|
|
1056
|
+
*/
|
|
1057
|
+
static async executeWithPayloads(session, payloads, options = {}) {
|
|
1058
|
+
const manager = new PluginManager();
|
|
1059
|
+
manager.addPayloads(payloads);
|
|
1060
|
+
return _Runner.execute(session, options, { pluginManager: manager });
|
|
639
1061
|
}
|
|
640
|
-
|
|
641
|
-
|
|
1062
|
+
/**
|
|
1063
|
+
* Replay session steps with payload injected at target step
|
|
1064
|
+
*/
|
|
1065
|
+
static async replayWithPayload(page, session, targetStep, payloadValue) {
|
|
1066
|
+
await page.goto(session.startUrl, { waitUntil: "domcontentloaded" });
|
|
642
1067
|
for (const step of session.steps) {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
1068
|
+
try {
|
|
1069
|
+
if (step.type === "navigate") {
|
|
1070
|
+
await page.goto(step.url, { waitUntil: "domcontentloaded" });
|
|
1071
|
+
} else if (step.type === "click") {
|
|
1072
|
+
await page.click(step.selector, { timeout: 5e3 });
|
|
1073
|
+
} else if (step.type === "input") {
|
|
1074
|
+
const value = step.id === targetStep.id ? payloadValue : step.value;
|
|
1075
|
+
await page.fill(step.selector, value, { timeout: 5e3 });
|
|
1076
|
+
} else if (step.type === "keypress") {
|
|
1077
|
+
const modifiers = step.modifiers ?? [];
|
|
1078
|
+
for (const mod of modifiers) {
|
|
1079
|
+
await page.keyboard.down(
|
|
1080
|
+
mod
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
await page.keyboard.press(step.key);
|
|
1084
|
+
for (const mod of modifiers.reverse()) {
|
|
1085
|
+
await page.keyboard.up(mod);
|
|
1086
|
+
}
|
|
658
1087
|
}
|
|
1088
|
+
} catch {
|
|
659
1089
|
}
|
|
660
1090
|
if (step.id === targetStep.id) {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
targetStep,
|
|
664
|
-
payloadName,
|
|
665
|
-
payloadValue
|
|
666
|
-
);
|
|
667
|
-
if (finding) {
|
|
668
|
-
return finding;
|
|
669
|
-
}
|
|
1091
|
+
await page.waitForTimeout(100);
|
|
1092
|
+
break;
|
|
670
1093
|
}
|
|
671
1094
|
}
|
|
672
|
-
return void 0;
|
|
673
1095
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1096
|
+
/**
|
|
1097
|
+
* Basic reflection check - fallback when no detection plugin is loaded
|
|
1098
|
+
*/
|
|
1099
|
+
static async checkReflection(page, step, payloadSet, payloadValue) {
|
|
677
1100
|
const content = await page.content();
|
|
678
|
-
for (const pattern of
|
|
1101
|
+
for (const pattern of payloadSet.detectPatterns) {
|
|
679
1102
|
if (pattern.test(content)) {
|
|
680
1103
|
return {
|
|
681
|
-
type:
|
|
682
|
-
severity:
|
|
683
|
-
title: `${
|
|
684
|
-
description: `Payload was reflected in page content`,
|
|
1104
|
+
type: payloadSet.category,
|
|
1105
|
+
severity: _Runner.getSeverity(payloadSet.category),
|
|
1106
|
+
title: `${payloadSet.category.toUpperCase()} vulnerability detected`,
|
|
1107
|
+
description: `Payload pattern was reflected in page content`,
|
|
685
1108
|
stepId: step.id,
|
|
686
1109
|
payload: payloadValue,
|
|
687
1110
|
url: page.url(),
|
|
688
|
-
evidence: content.match(pattern)?.[0]
|
|
1111
|
+
evidence: content.match(pattern)?.[0]?.slice(0, 200)
|
|
689
1112
|
};
|
|
690
1113
|
}
|
|
691
1114
|
}
|
|
692
1115
|
if (content.includes(payloadValue)) {
|
|
693
1116
|
return {
|
|
694
|
-
type:
|
|
1117
|
+
type: payloadSet.category,
|
|
695
1118
|
severity: "medium",
|
|
696
|
-
title: `Potential ${
|
|
1119
|
+
title: `Potential ${payloadSet.category.toUpperCase()} - payload reflection`,
|
|
697
1120
|
description: `Payload was reflected in page without encoding`,
|
|
698
1121
|
stepId: step.id,
|
|
699
1122
|
payload: payloadValue,
|
|
@@ -702,23 +1125,41 @@ var Runner = class _Runner {
|
|
|
702
1125
|
}
|
|
703
1126
|
return void 0;
|
|
704
1127
|
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Determine severity based on vulnerability category
|
|
1130
|
+
*/
|
|
1131
|
+
static getSeverity(category) {
|
|
1132
|
+
switch (category) {
|
|
1133
|
+
case "sqli":
|
|
1134
|
+
case "command-injection":
|
|
1135
|
+
case "xxe":
|
|
1136
|
+
return "critical";
|
|
1137
|
+
case "xss":
|
|
1138
|
+
case "ssrf":
|
|
1139
|
+
case "path-traversal":
|
|
1140
|
+
return "high";
|
|
1141
|
+
case "open-redirect":
|
|
1142
|
+
return "medium";
|
|
1143
|
+
default:
|
|
1144
|
+
return "medium";
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
705
1147
|
};
|
|
706
1148
|
// Annotate the CommonJS export names for ESM import in node:
|
|
707
1149
|
0 && (module.exports = {
|
|
708
|
-
BUILTIN_PAYLOADS,
|
|
709
1150
|
BrowserNotFoundError,
|
|
1151
|
+
PLUGIN_API_VERSION,
|
|
1152
|
+
PluginManager,
|
|
710
1153
|
Recorder,
|
|
711
1154
|
Runner,
|
|
712
1155
|
SessionSchema,
|
|
713
1156
|
StepSchema,
|
|
714
1157
|
checkBrowsers,
|
|
715
1158
|
createSession,
|
|
716
|
-
getPayload,
|
|
717
|
-
getPayloadNames,
|
|
718
|
-
getPayloadsByCategory,
|
|
719
1159
|
installBrowsers,
|
|
720
1160
|
launchBrowser,
|
|
721
1161
|
parseSession,
|
|
1162
|
+
pluginManager,
|
|
722
1163
|
serializeSession
|
|
723
1164
|
});
|
|
724
1165
|
//# sourceMappingURL=index.cjs.map
|