@vulcn/engine 0.5.0 → 0.8.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 +50 -0
- package/dist/index.cjs +425 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +273 -2
- package/dist/index.d.ts +273 -2
- package/dist/index.js +416 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -124,12 +124,22 @@ interface PluginHooks {
|
|
|
124
124
|
onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;
|
|
125
125
|
/** Called when recording ends, can transform session */
|
|
126
126
|
onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;
|
|
127
|
+
/** Called once when a scan starts (before any session is executed) */
|
|
128
|
+
onScanStart?: (ctx: ScanContext) => Promise<void>;
|
|
129
|
+
/** Called once when a scan ends (after all sessions have executed) */
|
|
130
|
+
onScanEnd?: (result: RunResult, ctx: ScanContext) => Promise<RunResult>;
|
|
127
131
|
/** Called when run starts */
|
|
128
132
|
onRunStart?: (ctx: RunContext$1) => Promise<void>;
|
|
129
133
|
/** Called before each payload is injected, can transform payload */
|
|
130
134
|
onBeforePayload?: (payload: string, step: Step, ctx: RunContext$1) => Promise<string>;
|
|
131
135
|
/** Called after payload injection, for detection */
|
|
132
136
|
onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Called before the browser/driver is closed.
|
|
139
|
+
* Plugins should await any pending async work here (e.g., flush
|
|
140
|
+
* in-flight response handlers that need browser access).
|
|
141
|
+
*/
|
|
142
|
+
onBeforeClose?: (ctx: PluginContext) => Promise<void>;
|
|
133
143
|
/** Called when run ends, can transform results */
|
|
134
144
|
onRunEnd?: (result: RunResult, ctx: RunContext$1) => Promise<RunResult>;
|
|
135
145
|
/** Called when JavaScript alert/confirm/prompt appears */
|
|
@@ -169,8 +179,14 @@ interface PluginContext {
|
|
|
169
179
|
engine: EngineInfo;
|
|
170
180
|
/** Shared payload registry - loaders add payloads here */
|
|
171
181
|
payloads: RuntimePayload[];
|
|
172
|
-
/** Shared findings collection -
|
|
182
|
+
/** Shared findings collection (read-only view, use addFinding to add) */
|
|
173
183
|
findings: Finding[];
|
|
184
|
+
/**
|
|
185
|
+
* Add a finding through the proper callback chain.
|
|
186
|
+
* Plugins should use this instead of pushing to findings[] directly,
|
|
187
|
+
* so consumers (CLI, worker) get notified via onFinding callbacks.
|
|
188
|
+
*/
|
|
189
|
+
addFinding: (finding: Finding) => void;
|
|
174
190
|
/** Scoped logger */
|
|
175
191
|
logger: PluginLogger;
|
|
176
192
|
/** Fetch API for network requests */
|
|
@@ -194,6 +210,17 @@ interface RunContext$1 extends PluginContext {
|
|
|
194
210
|
/** Whether running headless */
|
|
195
211
|
headless: boolean;
|
|
196
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Context for scan-level hooks (wraps all sessions)
|
|
215
|
+
*/
|
|
216
|
+
interface ScanContext extends PluginContext {
|
|
217
|
+
/** All sessions in this scan */
|
|
218
|
+
sessions: Session[];
|
|
219
|
+
/** Whether running headless */
|
|
220
|
+
headless: boolean;
|
|
221
|
+
/** Total sessions count */
|
|
222
|
+
sessionCount: number;
|
|
223
|
+
}
|
|
197
224
|
/**
|
|
198
225
|
* Context for detection hooks
|
|
199
226
|
*/
|
|
@@ -438,6 +465,8 @@ interface CrawlOptions {
|
|
|
438
465
|
pageTimeout?: number;
|
|
439
466
|
/** Only crawl pages under the same origin (default: true) */
|
|
440
467
|
sameOrigin?: boolean;
|
|
468
|
+
/** Playwright storage state JSON for authenticated crawling */
|
|
469
|
+
storageState?: string;
|
|
441
470
|
/** Callback when a page is crawled */
|
|
442
471
|
onPageCrawled?: (url: string, formsFound: number) => void;
|
|
443
472
|
}
|
|
@@ -451,6 +480,18 @@ interface RunOptions {
|
|
|
451
480
|
onFinding?: (finding: Finding) => void;
|
|
452
481
|
/** Callback for step completion */
|
|
453
482
|
onStepComplete?: (stepId: string, payloadCount: number) => void;
|
|
483
|
+
/**
|
|
484
|
+
* Called by the driver runner after the page/environment is ready.
|
|
485
|
+
* The driver-manager uses this to fire plugin onRunStart hooks
|
|
486
|
+
* with the real page object (instead of null).
|
|
487
|
+
*/
|
|
488
|
+
onPageReady?: (page: unknown) => Promise<void>;
|
|
489
|
+
/**
|
|
490
|
+
* Called by the driver runner before closing the browser/environment.
|
|
491
|
+
* The driver-manager uses this to fire plugin onBeforeClose hooks
|
|
492
|
+
* so plugins can flush pending async work.
|
|
493
|
+
*/
|
|
494
|
+
onBeforeClose?: (page: unknown) => Promise<void>;
|
|
454
495
|
/** Driver-specific options */
|
|
455
496
|
[key: string]: unknown;
|
|
456
497
|
}
|
|
@@ -627,8 +668,27 @@ declare class DriverManager {
|
|
|
627
668
|
/**
|
|
628
669
|
* Execute a session
|
|
629
670
|
* Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.
|
|
671
|
+
* Plugin onRunStart is deferred until the driver signals the page is ready
|
|
672
|
+
* via the onPageReady callback, ensuring plugins get a real page object.
|
|
630
673
|
*/
|
|
631
674
|
execute(session: Session, pluginManager: PluginManager, options?: RunOptions): Promise<RunResult>;
|
|
675
|
+
/**
|
|
676
|
+
* Execute multiple sessions with a shared browser (scan-level orchestration).
|
|
677
|
+
*
|
|
678
|
+
* This is the preferred entry point for running a full scan. It:
|
|
679
|
+
* 1. Launches ONE browser for the entire scan
|
|
680
|
+
* 2. Passes the browser to each session's runner via options.browser
|
|
681
|
+
* 3. Each session creates its own context (lightweight, isolated cookies)
|
|
682
|
+
* 4. Aggregates results across all sessions
|
|
683
|
+
* 5. Closes the browser once at the end
|
|
684
|
+
*
|
|
685
|
+
* This is 5-10x faster than calling execute() per session because
|
|
686
|
+
* launching a browser takes 2-3 seconds.
|
|
687
|
+
*/
|
|
688
|
+
executeScan(sessions: Session[], pluginManager: PluginManager, options?: RunOptions): Promise<{
|
|
689
|
+
results: RunResult[];
|
|
690
|
+
aggregate: RunResult;
|
|
691
|
+
}>;
|
|
632
692
|
/**
|
|
633
693
|
* Validate driver structure
|
|
634
694
|
*/
|
|
@@ -643,4 +703,215 @@ declare class DriverManager {
|
|
|
643
703
|
*/
|
|
644
704
|
declare const driverManager: DriverManager;
|
|
645
705
|
|
|
646
|
-
|
|
706
|
+
/**
|
|
707
|
+
* Vulcn Auth Module
|
|
708
|
+
*
|
|
709
|
+
* Handles credential encryption/decryption and auth state management.
|
|
710
|
+
*
|
|
711
|
+
* Security:
|
|
712
|
+
* - AES-256-GCM encryption for credentials at rest
|
|
713
|
+
* - PBKDF2 key derivation from passphrase
|
|
714
|
+
* - Reads passphrase from VULCN_KEY env var (CI/CD) or interactive prompt
|
|
715
|
+
* - Auth state (cookies, localStorage) encrypted separately
|
|
716
|
+
*/
|
|
717
|
+
/** Form-based login credentials */
|
|
718
|
+
interface FormCredentials {
|
|
719
|
+
type: "form";
|
|
720
|
+
username: string;
|
|
721
|
+
password: string;
|
|
722
|
+
/** Custom login URL (if different from target) */
|
|
723
|
+
loginUrl?: string;
|
|
724
|
+
/** Custom CSS selector for username field */
|
|
725
|
+
userSelector?: string;
|
|
726
|
+
/** Custom CSS selector for password field */
|
|
727
|
+
passSelector?: string;
|
|
728
|
+
}
|
|
729
|
+
/** Header-based authentication (API keys, Bearer tokens) */
|
|
730
|
+
interface HeaderCredentials {
|
|
731
|
+
type: "header";
|
|
732
|
+
headers: Record<string, string>;
|
|
733
|
+
}
|
|
734
|
+
/** All credential types */
|
|
735
|
+
type Credentials = FormCredentials | HeaderCredentials;
|
|
736
|
+
/** Auth configuration for a scan */
|
|
737
|
+
interface AuthConfig {
|
|
738
|
+
/** Auth strategy */
|
|
739
|
+
strategy: "storage-state" | "header";
|
|
740
|
+
/** Login page URL */
|
|
741
|
+
loginUrl?: string;
|
|
742
|
+
/** Text that appears when logged in (e.g., "Logout") */
|
|
743
|
+
loggedInIndicator?: string;
|
|
744
|
+
/** Text that appears when logged out (e.g., "Sign In") */
|
|
745
|
+
loggedOutIndicator?: string;
|
|
746
|
+
/** Session expiry detection rules */
|
|
747
|
+
sessionExpiry?: {
|
|
748
|
+
/** HTTP status codes that indicate session expired */
|
|
749
|
+
statusCodes?: number[];
|
|
750
|
+
/** URL pattern that indicates redirect to login */
|
|
751
|
+
redirectPattern?: string;
|
|
752
|
+
/** Page content that indicates session expired */
|
|
753
|
+
pageContent?: string;
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Encrypt data with AES-256-GCM.
|
|
758
|
+
*
|
|
759
|
+
* @param data - Plaintext data to encrypt
|
|
760
|
+
* @param passphrase - Passphrase for key derivation
|
|
761
|
+
* @returns JSON string of EncryptedData
|
|
762
|
+
*/
|
|
763
|
+
declare function encrypt(data: string, passphrase: string): string;
|
|
764
|
+
/**
|
|
765
|
+
* Decrypt data encrypted with encrypt().
|
|
766
|
+
*
|
|
767
|
+
* @param encrypted - JSON string from encrypt()
|
|
768
|
+
* @param passphrase - Passphrase used during encryption
|
|
769
|
+
* @returns Decrypted plaintext
|
|
770
|
+
* @throws Error if passphrase is wrong or data is tampered
|
|
771
|
+
*/
|
|
772
|
+
declare function decrypt(encrypted: string, passphrase: string): string;
|
|
773
|
+
/**
|
|
774
|
+
* Encrypt credentials to a storable string.
|
|
775
|
+
*/
|
|
776
|
+
declare function encryptCredentials(credentials: Credentials, passphrase: string): string;
|
|
777
|
+
/**
|
|
778
|
+
* Decrypt credentials from a stored string.
|
|
779
|
+
*/
|
|
780
|
+
declare function decryptCredentials(encrypted: string, passphrase: string): Credentials;
|
|
781
|
+
/**
|
|
782
|
+
* Encrypt browser storage state (cookies, localStorage, etc.).
|
|
783
|
+
* The state is the JSON output from Playwright's context.storageState().
|
|
784
|
+
*/
|
|
785
|
+
declare function encryptStorageState(storageState: string, passphrase: string): string;
|
|
786
|
+
/**
|
|
787
|
+
* Decrypt browser storage state.
|
|
788
|
+
*/
|
|
789
|
+
declare function decryptStorageState(encrypted: string, passphrase: string): string;
|
|
790
|
+
/**
|
|
791
|
+
* Get passphrase from environment or throw.
|
|
792
|
+
*
|
|
793
|
+
* In CI/CD, set VULCN_KEY env var.
|
|
794
|
+
* In interactive mode, the CLI should prompt and pass the value here.
|
|
795
|
+
*/
|
|
796
|
+
declare function getPassphrase(interactive?: string): string;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Vulcn Session Format v2
|
|
800
|
+
*
|
|
801
|
+
* Directory-based session format: `.vulcn/` or `<name>.vulcn/`
|
|
802
|
+
*
|
|
803
|
+
* Structure:
|
|
804
|
+
* manifest.yml - scan config, session list, auth config
|
|
805
|
+
* auth/config.yml - login strategy, indicators
|
|
806
|
+
* auth/state.enc - encrypted storageState (cookies/localStorage)
|
|
807
|
+
* sessions/*.yml - individual session files (one per form)
|
|
808
|
+
* requests/*.json - captured HTTP metadata (for Tier 1 fast scan)
|
|
809
|
+
*/
|
|
810
|
+
|
|
811
|
+
/** Manifest file schema (manifest.yml) */
|
|
812
|
+
interface ScanManifest {
|
|
813
|
+
/** Format version */
|
|
814
|
+
version: "2";
|
|
815
|
+
/** Human-readable scan name */
|
|
816
|
+
name: string;
|
|
817
|
+
/** Target URL */
|
|
818
|
+
target: string;
|
|
819
|
+
/** When the scan was recorded */
|
|
820
|
+
recordedAt: string;
|
|
821
|
+
/** Driver name */
|
|
822
|
+
driver: string;
|
|
823
|
+
/** Driver configuration */
|
|
824
|
+
driverConfig: Record<string, unknown>;
|
|
825
|
+
/** Auth configuration (optional) */
|
|
826
|
+
auth?: {
|
|
827
|
+
strategy: string;
|
|
828
|
+
configFile?: string;
|
|
829
|
+
stateFile?: string;
|
|
830
|
+
loggedInIndicator?: string;
|
|
831
|
+
loggedOutIndicator?: string;
|
|
832
|
+
reAuthOn?: Array<Record<string, unknown>>;
|
|
833
|
+
};
|
|
834
|
+
/** Session file references */
|
|
835
|
+
sessions: SessionRef[];
|
|
836
|
+
/** Scan configuration */
|
|
837
|
+
scan?: {
|
|
838
|
+
tier?: "auto" | "http-only" | "browser-only";
|
|
839
|
+
parallel?: number;
|
|
840
|
+
timeout?: number;
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/** Reference to a session file within the manifest */
|
|
844
|
+
interface SessionRef {
|
|
845
|
+
/** Relative path to session file */
|
|
846
|
+
file: string;
|
|
847
|
+
/** Whether this session has injectable inputs */
|
|
848
|
+
injectable?: boolean;
|
|
849
|
+
}
|
|
850
|
+
/** HTTP request metadata for Tier 1 fast scanning */
|
|
851
|
+
interface CapturedRequest {
|
|
852
|
+
/** Request method */
|
|
853
|
+
method: string;
|
|
854
|
+
/** Full URL */
|
|
855
|
+
url: string;
|
|
856
|
+
/** Request headers */
|
|
857
|
+
headers: Record<string, string>;
|
|
858
|
+
/** Form data (for POST) */
|
|
859
|
+
body?: string;
|
|
860
|
+
/** Content type */
|
|
861
|
+
contentType?: string;
|
|
862
|
+
/** Which form field is injectable */
|
|
863
|
+
injectableField?: string;
|
|
864
|
+
/** Session name this request belongs to */
|
|
865
|
+
sessionName: string;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Load a v2 session directory into Session[] ready for execution.
|
|
869
|
+
*
|
|
870
|
+
* @param dirPath - Path to the .vulcn/ directory
|
|
871
|
+
* @returns Array of sessions with manifest metadata attached
|
|
872
|
+
*/
|
|
873
|
+
declare function loadSessionDir(dirPath: string): Promise<{
|
|
874
|
+
manifest: ScanManifest;
|
|
875
|
+
sessions: Session[];
|
|
876
|
+
authConfig?: AuthConfig;
|
|
877
|
+
}>;
|
|
878
|
+
/**
|
|
879
|
+
* Check if a path is a v2 session directory.
|
|
880
|
+
*/
|
|
881
|
+
declare function isSessionDir(path: string): boolean;
|
|
882
|
+
/**
|
|
883
|
+
* Check if a path looks like a v2 session directory (by extension).
|
|
884
|
+
*/
|
|
885
|
+
declare function looksLikeSessionDir(path: string): boolean;
|
|
886
|
+
/**
|
|
887
|
+
* Save sessions to a v2 session directory.
|
|
888
|
+
*
|
|
889
|
+
* Creates the directory structure:
|
|
890
|
+
* <dirPath>/
|
|
891
|
+
* ├── manifest.yml
|
|
892
|
+
* ├── sessions/
|
|
893
|
+
* │ ├── <session-name>.yml
|
|
894
|
+
* │ └── ...
|
|
895
|
+
* └── requests/ (if HTTP metadata provided)
|
|
896
|
+
* └── ...
|
|
897
|
+
*/
|
|
898
|
+
declare function saveSessionDir(dirPath: string, options: {
|
|
899
|
+
name: string;
|
|
900
|
+
target: string;
|
|
901
|
+
driver: string;
|
|
902
|
+
driverConfig: Record<string, unknown>;
|
|
903
|
+
sessions: Session[];
|
|
904
|
+
authConfig?: AuthConfig;
|
|
905
|
+
encryptedState?: string;
|
|
906
|
+
requests?: CapturedRequest[];
|
|
907
|
+
}): Promise<void>;
|
|
908
|
+
/**
|
|
909
|
+
* Read encrypted auth state from a session directory.
|
|
910
|
+
*/
|
|
911
|
+
declare function readAuthState(dirPath: string): Promise<string | null>;
|
|
912
|
+
/**
|
|
913
|
+
* Read captured HTTP requests from a session directory.
|
|
914
|
+
*/
|
|
915
|
+
declare function readCapturedRequests(dirPath: string): Promise<CapturedRequest[]>;
|
|
916
|
+
|
|
917
|
+
export { type AuthConfig, type CapturedRequest, type CrawlOptions, type Credentials, type CustomPayload, type CustomPayloadFile, DRIVER_API_VERSION, type DetectContext, type DriverLogger, DriverManager, type DriverSource, type EngineInfo, type Finding, type FormCredentials, type HeaderCredentials, type LoadedDriver, type LoadedPlugin as LoadedPluginInfo, PLUGIN_API_VERSION, type PayloadCategory, type PayloadSource, type PluginConfig, type PluginContext, type PluginHooks, type PluginLogger, PluginManager, type RunContext$1 as PluginRunContext, type PluginSource, type RecordContext, type RecordOptions, type RecorderDriver, type RecordingHandle, type RunContext, type RunOptions, type RunResult, type RunnerDriver, type RuntimePayload, type ScanContext, type ScanManifest, type Session, type SessionRef, type Step, type VulcnConfig, type VulcnDriver, type VulcnPlugin, decrypt, decryptCredentials, decryptStorageState, driverManager, encrypt, encryptCredentials, encryptStorageState, getPassphrase, isSessionDir, loadSessionDir, looksLikeSessionDir, pluginManager, readAuthState, readCapturedRequests, saveSessionDir };
|
package/dist/index.d.ts
CHANGED
|
@@ -124,12 +124,22 @@ interface PluginHooks {
|
|
|
124
124
|
onRecordStep?: (step: Step, ctx: RecordContext) => Promise<Step>;
|
|
125
125
|
/** Called when recording ends, can transform session */
|
|
126
126
|
onRecordEnd?: (session: Session, ctx: RecordContext) => Promise<Session>;
|
|
127
|
+
/** Called once when a scan starts (before any session is executed) */
|
|
128
|
+
onScanStart?: (ctx: ScanContext) => Promise<void>;
|
|
129
|
+
/** Called once when a scan ends (after all sessions have executed) */
|
|
130
|
+
onScanEnd?: (result: RunResult, ctx: ScanContext) => Promise<RunResult>;
|
|
127
131
|
/** Called when run starts */
|
|
128
132
|
onRunStart?: (ctx: RunContext$1) => Promise<void>;
|
|
129
133
|
/** Called before each payload is injected, can transform payload */
|
|
130
134
|
onBeforePayload?: (payload: string, step: Step, ctx: RunContext$1) => Promise<string>;
|
|
131
135
|
/** Called after payload injection, for detection */
|
|
132
136
|
onAfterPayload?: (ctx: DetectContext) => Promise<Finding[]>;
|
|
137
|
+
/**
|
|
138
|
+
* Called before the browser/driver is closed.
|
|
139
|
+
* Plugins should await any pending async work here (e.g., flush
|
|
140
|
+
* in-flight response handlers that need browser access).
|
|
141
|
+
*/
|
|
142
|
+
onBeforeClose?: (ctx: PluginContext) => Promise<void>;
|
|
133
143
|
/** Called when run ends, can transform results */
|
|
134
144
|
onRunEnd?: (result: RunResult, ctx: RunContext$1) => Promise<RunResult>;
|
|
135
145
|
/** Called when JavaScript alert/confirm/prompt appears */
|
|
@@ -169,8 +179,14 @@ interface PluginContext {
|
|
|
169
179
|
engine: EngineInfo;
|
|
170
180
|
/** Shared payload registry - loaders add payloads here */
|
|
171
181
|
payloads: RuntimePayload[];
|
|
172
|
-
/** Shared findings collection -
|
|
182
|
+
/** Shared findings collection (read-only view, use addFinding to add) */
|
|
173
183
|
findings: Finding[];
|
|
184
|
+
/**
|
|
185
|
+
* Add a finding through the proper callback chain.
|
|
186
|
+
* Plugins should use this instead of pushing to findings[] directly,
|
|
187
|
+
* so consumers (CLI, worker) get notified via onFinding callbacks.
|
|
188
|
+
*/
|
|
189
|
+
addFinding: (finding: Finding) => void;
|
|
174
190
|
/** Scoped logger */
|
|
175
191
|
logger: PluginLogger;
|
|
176
192
|
/** Fetch API for network requests */
|
|
@@ -194,6 +210,17 @@ interface RunContext$1 extends PluginContext {
|
|
|
194
210
|
/** Whether running headless */
|
|
195
211
|
headless: boolean;
|
|
196
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Context for scan-level hooks (wraps all sessions)
|
|
215
|
+
*/
|
|
216
|
+
interface ScanContext extends PluginContext {
|
|
217
|
+
/** All sessions in this scan */
|
|
218
|
+
sessions: Session[];
|
|
219
|
+
/** Whether running headless */
|
|
220
|
+
headless: boolean;
|
|
221
|
+
/** Total sessions count */
|
|
222
|
+
sessionCount: number;
|
|
223
|
+
}
|
|
197
224
|
/**
|
|
198
225
|
* Context for detection hooks
|
|
199
226
|
*/
|
|
@@ -438,6 +465,8 @@ interface CrawlOptions {
|
|
|
438
465
|
pageTimeout?: number;
|
|
439
466
|
/** Only crawl pages under the same origin (default: true) */
|
|
440
467
|
sameOrigin?: boolean;
|
|
468
|
+
/** Playwright storage state JSON for authenticated crawling */
|
|
469
|
+
storageState?: string;
|
|
441
470
|
/** Callback when a page is crawled */
|
|
442
471
|
onPageCrawled?: (url: string, formsFound: number) => void;
|
|
443
472
|
}
|
|
@@ -451,6 +480,18 @@ interface RunOptions {
|
|
|
451
480
|
onFinding?: (finding: Finding) => void;
|
|
452
481
|
/** Callback for step completion */
|
|
453
482
|
onStepComplete?: (stepId: string, payloadCount: number) => void;
|
|
483
|
+
/**
|
|
484
|
+
* Called by the driver runner after the page/environment is ready.
|
|
485
|
+
* The driver-manager uses this to fire plugin onRunStart hooks
|
|
486
|
+
* with the real page object (instead of null).
|
|
487
|
+
*/
|
|
488
|
+
onPageReady?: (page: unknown) => Promise<void>;
|
|
489
|
+
/**
|
|
490
|
+
* Called by the driver runner before closing the browser/environment.
|
|
491
|
+
* The driver-manager uses this to fire plugin onBeforeClose hooks
|
|
492
|
+
* so plugins can flush pending async work.
|
|
493
|
+
*/
|
|
494
|
+
onBeforeClose?: (page: unknown) => Promise<void>;
|
|
454
495
|
/** Driver-specific options */
|
|
455
496
|
[key: string]: unknown;
|
|
456
497
|
}
|
|
@@ -627,8 +668,27 @@ declare class DriverManager {
|
|
|
627
668
|
/**
|
|
628
669
|
* Execute a session
|
|
629
670
|
* Invokes plugin hooks (onRunStart, onRunEnd) around the driver runner.
|
|
671
|
+
* Plugin onRunStart is deferred until the driver signals the page is ready
|
|
672
|
+
* via the onPageReady callback, ensuring plugins get a real page object.
|
|
630
673
|
*/
|
|
631
674
|
execute(session: Session, pluginManager: PluginManager, options?: RunOptions): Promise<RunResult>;
|
|
675
|
+
/**
|
|
676
|
+
* Execute multiple sessions with a shared browser (scan-level orchestration).
|
|
677
|
+
*
|
|
678
|
+
* This is the preferred entry point for running a full scan. It:
|
|
679
|
+
* 1. Launches ONE browser for the entire scan
|
|
680
|
+
* 2. Passes the browser to each session's runner via options.browser
|
|
681
|
+
* 3. Each session creates its own context (lightweight, isolated cookies)
|
|
682
|
+
* 4. Aggregates results across all sessions
|
|
683
|
+
* 5. Closes the browser once at the end
|
|
684
|
+
*
|
|
685
|
+
* This is 5-10x faster than calling execute() per session because
|
|
686
|
+
* launching a browser takes 2-3 seconds.
|
|
687
|
+
*/
|
|
688
|
+
executeScan(sessions: Session[], pluginManager: PluginManager, options?: RunOptions): Promise<{
|
|
689
|
+
results: RunResult[];
|
|
690
|
+
aggregate: RunResult;
|
|
691
|
+
}>;
|
|
632
692
|
/**
|
|
633
693
|
* Validate driver structure
|
|
634
694
|
*/
|
|
@@ -643,4 +703,215 @@ declare class DriverManager {
|
|
|
643
703
|
*/
|
|
644
704
|
declare const driverManager: DriverManager;
|
|
645
705
|
|
|
646
|
-
|
|
706
|
+
/**
|
|
707
|
+
* Vulcn Auth Module
|
|
708
|
+
*
|
|
709
|
+
* Handles credential encryption/decryption and auth state management.
|
|
710
|
+
*
|
|
711
|
+
* Security:
|
|
712
|
+
* - AES-256-GCM encryption for credentials at rest
|
|
713
|
+
* - PBKDF2 key derivation from passphrase
|
|
714
|
+
* - Reads passphrase from VULCN_KEY env var (CI/CD) or interactive prompt
|
|
715
|
+
* - Auth state (cookies, localStorage) encrypted separately
|
|
716
|
+
*/
|
|
717
|
+
/** Form-based login credentials */
|
|
718
|
+
interface FormCredentials {
|
|
719
|
+
type: "form";
|
|
720
|
+
username: string;
|
|
721
|
+
password: string;
|
|
722
|
+
/** Custom login URL (if different from target) */
|
|
723
|
+
loginUrl?: string;
|
|
724
|
+
/** Custom CSS selector for username field */
|
|
725
|
+
userSelector?: string;
|
|
726
|
+
/** Custom CSS selector for password field */
|
|
727
|
+
passSelector?: string;
|
|
728
|
+
}
|
|
729
|
+
/** Header-based authentication (API keys, Bearer tokens) */
|
|
730
|
+
interface HeaderCredentials {
|
|
731
|
+
type: "header";
|
|
732
|
+
headers: Record<string, string>;
|
|
733
|
+
}
|
|
734
|
+
/** All credential types */
|
|
735
|
+
type Credentials = FormCredentials | HeaderCredentials;
|
|
736
|
+
/** Auth configuration for a scan */
|
|
737
|
+
interface AuthConfig {
|
|
738
|
+
/** Auth strategy */
|
|
739
|
+
strategy: "storage-state" | "header";
|
|
740
|
+
/** Login page URL */
|
|
741
|
+
loginUrl?: string;
|
|
742
|
+
/** Text that appears when logged in (e.g., "Logout") */
|
|
743
|
+
loggedInIndicator?: string;
|
|
744
|
+
/** Text that appears when logged out (e.g., "Sign In") */
|
|
745
|
+
loggedOutIndicator?: string;
|
|
746
|
+
/** Session expiry detection rules */
|
|
747
|
+
sessionExpiry?: {
|
|
748
|
+
/** HTTP status codes that indicate session expired */
|
|
749
|
+
statusCodes?: number[];
|
|
750
|
+
/** URL pattern that indicates redirect to login */
|
|
751
|
+
redirectPattern?: string;
|
|
752
|
+
/** Page content that indicates session expired */
|
|
753
|
+
pageContent?: string;
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Encrypt data with AES-256-GCM.
|
|
758
|
+
*
|
|
759
|
+
* @param data - Plaintext data to encrypt
|
|
760
|
+
* @param passphrase - Passphrase for key derivation
|
|
761
|
+
* @returns JSON string of EncryptedData
|
|
762
|
+
*/
|
|
763
|
+
declare function encrypt(data: string, passphrase: string): string;
|
|
764
|
+
/**
|
|
765
|
+
* Decrypt data encrypted with encrypt().
|
|
766
|
+
*
|
|
767
|
+
* @param encrypted - JSON string from encrypt()
|
|
768
|
+
* @param passphrase - Passphrase used during encryption
|
|
769
|
+
* @returns Decrypted plaintext
|
|
770
|
+
* @throws Error if passphrase is wrong or data is tampered
|
|
771
|
+
*/
|
|
772
|
+
declare function decrypt(encrypted: string, passphrase: string): string;
|
|
773
|
+
/**
|
|
774
|
+
* Encrypt credentials to a storable string.
|
|
775
|
+
*/
|
|
776
|
+
declare function encryptCredentials(credentials: Credentials, passphrase: string): string;
|
|
777
|
+
/**
|
|
778
|
+
* Decrypt credentials from a stored string.
|
|
779
|
+
*/
|
|
780
|
+
declare function decryptCredentials(encrypted: string, passphrase: string): Credentials;
|
|
781
|
+
/**
|
|
782
|
+
* Encrypt browser storage state (cookies, localStorage, etc.).
|
|
783
|
+
* The state is the JSON output from Playwright's context.storageState().
|
|
784
|
+
*/
|
|
785
|
+
declare function encryptStorageState(storageState: string, passphrase: string): string;
|
|
786
|
+
/**
|
|
787
|
+
* Decrypt browser storage state.
|
|
788
|
+
*/
|
|
789
|
+
declare function decryptStorageState(encrypted: string, passphrase: string): string;
|
|
790
|
+
/**
|
|
791
|
+
* Get passphrase from environment or throw.
|
|
792
|
+
*
|
|
793
|
+
* In CI/CD, set VULCN_KEY env var.
|
|
794
|
+
* In interactive mode, the CLI should prompt and pass the value here.
|
|
795
|
+
*/
|
|
796
|
+
declare function getPassphrase(interactive?: string): string;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* Vulcn Session Format v2
|
|
800
|
+
*
|
|
801
|
+
* Directory-based session format: `.vulcn/` or `<name>.vulcn/`
|
|
802
|
+
*
|
|
803
|
+
* Structure:
|
|
804
|
+
* manifest.yml - scan config, session list, auth config
|
|
805
|
+
* auth/config.yml - login strategy, indicators
|
|
806
|
+
* auth/state.enc - encrypted storageState (cookies/localStorage)
|
|
807
|
+
* sessions/*.yml - individual session files (one per form)
|
|
808
|
+
* requests/*.json - captured HTTP metadata (for Tier 1 fast scan)
|
|
809
|
+
*/
|
|
810
|
+
|
|
811
|
+
/** Manifest file schema (manifest.yml) */
|
|
812
|
+
interface ScanManifest {
|
|
813
|
+
/** Format version */
|
|
814
|
+
version: "2";
|
|
815
|
+
/** Human-readable scan name */
|
|
816
|
+
name: string;
|
|
817
|
+
/** Target URL */
|
|
818
|
+
target: string;
|
|
819
|
+
/** When the scan was recorded */
|
|
820
|
+
recordedAt: string;
|
|
821
|
+
/** Driver name */
|
|
822
|
+
driver: string;
|
|
823
|
+
/** Driver configuration */
|
|
824
|
+
driverConfig: Record<string, unknown>;
|
|
825
|
+
/** Auth configuration (optional) */
|
|
826
|
+
auth?: {
|
|
827
|
+
strategy: string;
|
|
828
|
+
configFile?: string;
|
|
829
|
+
stateFile?: string;
|
|
830
|
+
loggedInIndicator?: string;
|
|
831
|
+
loggedOutIndicator?: string;
|
|
832
|
+
reAuthOn?: Array<Record<string, unknown>>;
|
|
833
|
+
};
|
|
834
|
+
/** Session file references */
|
|
835
|
+
sessions: SessionRef[];
|
|
836
|
+
/** Scan configuration */
|
|
837
|
+
scan?: {
|
|
838
|
+
tier?: "auto" | "http-only" | "browser-only";
|
|
839
|
+
parallel?: number;
|
|
840
|
+
timeout?: number;
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/** Reference to a session file within the manifest */
|
|
844
|
+
interface SessionRef {
|
|
845
|
+
/** Relative path to session file */
|
|
846
|
+
file: string;
|
|
847
|
+
/** Whether this session has injectable inputs */
|
|
848
|
+
injectable?: boolean;
|
|
849
|
+
}
|
|
850
|
+
/** HTTP request metadata for Tier 1 fast scanning */
|
|
851
|
+
interface CapturedRequest {
|
|
852
|
+
/** Request method */
|
|
853
|
+
method: string;
|
|
854
|
+
/** Full URL */
|
|
855
|
+
url: string;
|
|
856
|
+
/** Request headers */
|
|
857
|
+
headers: Record<string, string>;
|
|
858
|
+
/** Form data (for POST) */
|
|
859
|
+
body?: string;
|
|
860
|
+
/** Content type */
|
|
861
|
+
contentType?: string;
|
|
862
|
+
/** Which form field is injectable */
|
|
863
|
+
injectableField?: string;
|
|
864
|
+
/** Session name this request belongs to */
|
|
865
|
+
sessionName: string;
|
|
866
|
+
}
|
|
867
|
+
/**
|
|
868
|
+
* Load a v2 session directory into Session[] ready for execution.
|
|
869
|
+
*
|
|
870
|
+
* @param dirPath - Path to the .vulcn/ directory
|
|
871
|
+
* @returns Array of sessions with manifest metadata attached
|
|
872
|
+
*/
|
|
873
|
+
declare function loadSessionDir(dirPath: string): Promise<{
|
|
874
|
+
manifest: ScanManifest;
|
|
875
|
+
sessions: Session[];
|
|
876
|
+
authConfig?: AuthConfig;
|
|
877
|
+
}>;
|
|
878
|
+
/**
|
|
879
|
+
* Check if a path is a v2 session directory.
|
|
880
|
+
*/
|
|
881
|
+
declare function isSessionDir(path: string): boolean;
|
|
882
|
+
/**
|
|
883
|
+
* Check if a path looks like a v2 session directory (by extension).
|
|
884
|
+
*/
|
|
885
|
+
declare function looksLikeSessionDir(path: string): boolean;
|
|
886
|
+
/**
|
|
887
|
+
* Save sessions to a v2 session directory.
|
|
888
|
+
*
|
|
889
|
+
* Creates the directory structure:
|
|
890
|
+
* <dirPath>/
|
|
891
|
+
* ├── manifest.yml
|
|
892
|
+
* ├── sessions/
|
|
893
|
+
* │ ├── <session-name>.yml
|
|
894
|
+
* │ └── ...
|
|
895
|
+
* └── requests/ (if HTTP metadata provided)
|
|
896
|
+
* └── ...
|
|
897
|
+
*/
|
|
898
|
+
declare function saveSessionDir(dirPath: string, options: {
|
|
899
|
+
name: string;
|
|
900
|
+
target: string;
|
|
901
|
+
driver: string;
|
|
902
|
+
driverConfig: Record<string, unknown>;
|
|
903
|
+
sessions: Session[];
|
|
904
|
+
authConfig?: AuthConfig;
|
|
905
|
+
encryptedState?: string;
|
|
906
|
+
requests?: CapturedRequest[];
|
|
907
|
+
}): Promise<void>;
|
|
908
|
+
/**
|
|
909
|
+
* Read encrypted auth state from a session directory.
|
|
910
|
+
*/
|
|
911
|
+
declare function readAuthState(dirPath: string): Promise<string | null>;
|
|
912
|
+
/**
|
|
913
|
+
* Read captured HTTP requests from a session directory.
|
|
914
|
+
*/
|
|
915
|
+
declare function readCapturedRequests(dirPath: string): Promise<CapturedRequest[]>;
|
|
916
|
+
|
|
917
|
+
export { type AuthConfig, type CapturedRequest, type CrawlOptions, type Credentials, type CustomPayload, type CustomPayloadFile, DRIVER_API_VERSION, type DetectContext, type DriverLogger, DriverManager, type DriverSource, type EngineInfo, type Finding, type FormCredentials, type HeaderCredentials, type LoadedDriver, type LoadedPlugin as LoadedPluginInfo, PLUGIN_API_VERSION, type PayloadCategory, type PayloadSource, type PluginConfig, type PluginContext, type PluginHooks, type PluginLogger, PluginManager, type RunContext$1 as PluginRunContext, type PluginSource, type RecordContext, type RecordOptions, type RecorderDriver, type RecordingHandle, type RunContext, type RunOptions, type RunResult, type RunnerDriver, type RuntimePayload, type ScanContext, type ScanManifest, type Session, type SessionRef, type Step, type VulcnConfig, type VulcnDriver, type VulcnPlugin, decrypt, decryptCredentials, decryptStorageState, driverManager, encrypt, encryptCredentials, encryptStorageState, getPassphrase, isSessionDir, loadSessionDir, looksLikeSessionDir, pluginManager, readAuthState, readCapturedRequests, saveSessionDir };
|