@securitychecks/cli 0.1.1-rc.1

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/dist/lib.d.ts ADDED
@@ -0,0 +1,908 @@
1
+ import { AuditResult, Finding, CollectorArtifact, Severity, CheckResult, Artifact } from '@securitychecks/collector';
2
+
3
+ /**
4
+ * Audit API - Programmatic interface for running the staff check
5
+ *
6
+ * This module provides the core audit functionality that can be used by:
7
+ * - CLI commands
8
+ * - MCP server
9
+ * - Programmatic integration
10
+ *
11
+ * SECURITY NOTE: All audit functions require cloud API authentication.
12
+ * Detection logic runs server-side to protect IP.
13
+ * See: docs/POST_MORTEM_001_ENGINE_EXPOSURE.md
14
+ */
15
+
16
+ interface AuditOptions {
17
+ /** Target path to audit (default: current directory) */
18
+ targetPath?: string;
19
+ /** Only run specific invariant checks by ID */
20
+ only?: string[];
21
+ /** Skip specific invariant checks by ID */
22
+ skip?: string[];
23
+ }
24
+ /**
25
+ * Run the full staff check audit
26
+ *
27
+ * This is the main programmatic API for running the staff check.
28
+ * It performs two steps:
29
+ * 1. Collect artifacts from the target codebase (facts)
30
+ * 2. Send to cloud API for evaluation (findings)
31
+ *
32
+ * SECURITY: Detection logic runs server-side to protect IP.
33
+ * An API key is required. Get one at https://securitychecks.ai/dashboard/settings/api-keys
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * import { audit } from '@securitychecks/cli';
38
+ *
39
+ * // Requires SECURITYCHECKS_API_KEY environment variable
40
+ * const result = await audit({
41
+ * targetPath: '/path/to/codebase',
42
+ * only: ['WEBHOOK.IDEMPOTENT'],
43
+ * });
44
+ *
45
+ * console.log(result.summary);
46
+ * ```
47
+ */
48
+ declare function audit(options?: AuditOptions): Promise<AuditResult>;
49
+
50
+ /**
51
+ * Schema Version Compatibility
52
+ *
53
+ * Ensures CLI can consume artifacts from compatible collector versions.
54
+ *
55
+ * The schema version follows semver:
56
+ * - MAJOR: Breaking changes (fields removed, types changed)
57
+ * - MINOR: Additive changes (new optional fields)
58
+ * - PATCH: Bug fixes, clarifications
59
+ *
60
+ * CLI declares a supported range, collector emits current version.
61
+ */
62
+ /**
63
+ * The schema version range this CLI version supports.
64
+ *
65
+ * Format: "^MAJOR.MINOR.x" - compatible with any version that has:
66
+ * - Same MAJOR version (no breaking changes)
67
+ * - Same or higher MINOR version (may have new optional fields)
68
+ *
69
+ * When updating:
70
+ * - Bump MINOR when CLI starts using new optional fields
71
+ * - Bump MAJOR when CLI requires breaking schema changes
72
+ */
73
+ declare const SUPPORTED_SCHEMA_RANGE: {
74
+ minMajor: number;
75
+ minMinor: number;
76
+ maxMajor: number;
77
+ };
78
+ interface SchemaValidationResult {
79
+ valid: boolean;
80
+ artifactVersion: string;
81
+ currentVersion: string;
82
+ error?: string;
83
+ remediation?: string;
84
+ }
85
+ /**
86
+ * Validate that an artifact's schema version is compatible with this CLI.
87
+ *
88
+ * Compatibility rules:
89
+ * - Artifact MAJOR must equal CLI's supported MAJOR (breaking changes)
90
+ * - Artifact MINOR must be >= CLI's minMinor (additive features)
91
+ * - Pre-1.0.0 artifacts (missing schemaVersion) are assumed "1.0.0"
92
+ */
93
+ declare function validateSchemaVersion(artifactSchemaVersion?: string): SchemaValidationResult;
94
+ /**
95
+ * Get the current collector schema version
96
+ */
97
+ declare function getCurrentSchemaVersion(): string;
98
+
99
+ /**
100
+ * Deterministic Error Codes for SecurityChecks CLI
101
+ *
102
+ * Format: SC_<CATEGORY>_<NUMBER>
103
+ *
104
+ * Categories:
105
+ * - CONFIG: Configuration errors
106
+ * - PARSE: Parsing/syntax errors
107
+ * - CHECK: Checker execution errors
108
+ * - IO: File system / network errors
109
+ * - CLI: Command line argument errors
110
+ */
111
+ declare const ErrorCodes: {
112
+ readonly CONFIG_NOT_FOUND: "SC_CONFIG_001";
113
+ readonly CONFIG_INVALID: "SC_CONFIG_002";
114
+ readonly CONFIG_SCHEMA_ERROR: "SC_CONFIG_003";
115
+ readonly PARSE_TYPESCRIPT_ERROR: "SC_PARSE_101";
116
+ readonly PARSE_FILE_NOT_FOUND: "SC_PARSE_102";
117
+ readonly PARSE_UNSUPPORTED_SYNTAX: "SC_PARSE_103";
118
+ readonly CHECK_EXECUTION_ERROR: "SC_CHECK_201";
119
+ readonly CHECK_TIMEOUT: "SC_CHECK_202";
120
+ readonly CHECK_INVARIANT_NOT_FOUND: "SC_CHECK_203";
121
+ readonly IO_READ_ERROR: "SC_IO_301";
122
+ readonly IO_WRITE_ERROR: "SC_IO_302";
123
+ readonly IO_PERMISSION_DENIED: "SC_IO_303";
124
+ readonly IO_PATH_NOT_FOUND: "SC_IO_304";
125
+ readonly CLI_INVALID_ARGUMENT: "SC_CLI_401";
126
+ readonly CLI_MISSING_ARGUMENT: "SC_CLI_402";
127
+ readonly CLI_UNKNOWN_COMMAND: "SC_CLI_403";
128
+ readonly ARTIFACT_NOT_FOUND: "SC_ARTIFACT_501";
129
+ readonly ARTIFACT_INVALID: "SC_ARTIFACT_502";
130
+ readonly ARTIFACT_VERSION_MISMATCH: "SC_ARTIFACT_503";
131
+ readonly CLOUD_AUTH_FAILED: "SC_CLOUD_601";
132
+ readonly CLOUD_PERMISSION_DENIED: "SC_CLOUD_602";
133
+ readonly CLOUD_NOT_FOUND: "SC_CLOUD_603";
134
+ readonly CLOUD_RATE_LIMITED: "SC_CLOUD_604";
135
+ readonly CLOUD_API_ERROR: "SC_CLOUD_605";
136
+ readonly CLOUD_NETWORK_ERROR: "SC_CLOUD_606";
137
+ readonly CLOUD_INVALID_API_KEY: "SC_CLOUD_607";
138
+ readonly AUTH_REQUIRED: "SC_CLOUD_608";
139
+ readonly OFFLINE_NOT_SUPPORTED: "SC_CLOUD_609";
140
+ };
141
+ type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
142
+ /**
143
+ * User-friendly error messages for each error code
144
+ */
145
+ declare const ErrorMessages: Record<ErrorCode, string>;
146
+ /**
147
+ * Remediation guidance for each error code
148
+ * Helps users understand what to do when they encounter an error.
149
+ */
150
+ declare const ErrorRemediation: Record<ErrorCode, string>;
151
+ /**
152
+ * Structured CLI Error with deterministic error code
153
+ */
154
+ declare class CLIError extends Error {
155
+ readonly code: ErrorCode;
156
+ readonly details?: unknown;
157
+ readonly cause?: Error;
158
+ constructor(code: ErrorCode, message?: string, options?: {
159
+ details?: unknown;
160
+ cause?: Error;
161
+ });
162
+ /**
163
+ * Get remediation guidance for this error
164
+ */
165
+ getRemediation(): string;
166
+ /**
167
+ * Format error for user display
168
+ */
169
+ toUserString(verbose?: boolean): string;
170
+ /**
171
+ * Format error with remediation for user display
172
+ */
173
+ toUserStringWithRemediation(): string;
174
+ /**
175
+ * Format error for JSON output
176
+ */
177
+ toJSON(): Record<string, unknown>;
178
+ }
179
+ /**
180
+ * Check if an error is a CLIError
181
+ */
182
+ declare function isCLIError(error: unknown): error is CLIError;
183
+ /**
184
+ * Wrap an unknown error in a CLIError
185
+ */
186
+ declare function wrapError(error: unknown, code: ErrorCode, message?: string): CLIError;
187
+
188
+ /**
189
+ * CI Environment Detection
190
+ *
191
+ * Detects CI/CD environment and extracts relevant context
192
+ * (branch, commit SHA, PR number) for proper scan association.
193
+ */
194
+ interface CIContext {
195
+ /** CI provider name */
196
+ provider: 'github-actions' | 'gitlab-ci' | 'circleci' | 'jenkins' | 'unknown';
197
+ /** Git branch name */
198
+ branch?: string;
199
+ /** Git commit SHA */
200
+ commitSha?: string;
201
+ /** Pull/Merge request number */
202
+ prNumber?: number;
203
+ /** Repository name (owner/repo) */
204
+ repository?: string;
205
+ /** Whether this is a PR/MR event */
206
+ isPullRequest: boolean;
207
+ }
208
+
209
+ /**
210
+ * Cloud Evaluation Client
211
+ *
212
+ * Sends artifacts to the cloud API for async server-side evaluation.
213
+ * Artifacts are stored in R2 and processed by Fly.io workers.
214
+ *
215
+ * Architecture:
216
+ * 1. CLI collects artifact locally (code never leaves)
217
+ * 2. Artifact sent to /api/v1/evaluate, stored in R2
218
+ * 3. Server queues QStash job for Fly.io worker
219
+ * 4. CLI polls /api/v1/scans/{id} until complete
220
+ * 5. Findings returned to CLI for display
221
+ */
222
+
223
+ interface CloudEvaluateOptions {
224
+ /** API key for authentication */
225
+ apiKey: string;
226
+ /** Cloud API base URL */
227
+ baseUrl: string;
228
+ /** Specific invariants to run (default: all) */
229
+ invariants?: string[];
230
+ /** Invariants to skip */
231
+ skip?: string[];
232
+ /** Minimum severity to return */
233
+ severity?: 'P0' | 'P1' | 'P2';
234
+ /** Project slug for scan association */
235
+ projectSlug?: string;
236
+ /** Timeout in ms (default: 300000 = 5 min) */
237
+ timeout?: number;
238
+ /** Poll interval in ms (default: 2000 = 2s) */
239
+ pollInterval?: number;
240
+ /** Progress callback */
241
+ onProgress?: EvaluationProgressCallback;
242
+ /** CI context (auto-detected if not provided) */
243
+ ciContext?: CIContext | null;
244
+ }
245
+ interface CloudEvaluateResult {
246
+ findings: Finding[];
247
+ stats: {
248
+ invariantsRun: number;
249
+ patternsRun: number;
250
+ findingsCount: number;
251
+ executionMs: number;
252
+ };
253
+ usage: {
254
+ scansUsed: number;
255
+ scansRemaining: number;
256
+ };
257
+ }
258
+ /** Scan status from API */
259
+ type ScanStatus = 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' | 'CANCELLED';
260
+ /** Progress callback for evaluation */
261
+ type EvaluationProgressCallback = (info: {
262
+ status: ScanStatus;
263
+ message?: string;
264
+ }) => void;
265
+ /**
266
+ * Check if cloud evaluation is available
267
+ */
268
+ declare function isCloudEvalAvailable(apiKey?: string): boolean;
269
+ /**
270
+ * Evaluate artifact via cloud API (async with polling)
271
+ *
272
+ * @param artifact The artifact to evaluate
273
+ * @param options Evaluation options
274
+ * @throws Error if evaluation fails or times out
275
+ */
276
+ declare function evaluateCloud(artifact: CollectorArtifact, options: CloudEvaluateOptions): Promise<CloudEvaluateResult>;
277
+ /**
278
+ * Check cloud API health
279
+ */
280
+ declare function checkCloudHealth(baseUrl: string): Promise<boolean>;
281
+ /**
282
+ * Get available invariants from cloud API
283
+ */
284
+ declare function getCloudInvariants(baseUrl: string, apiKey: string): Promise<Array<{
285
+ id: string;
286
+ name: string;
287
+ description: string;
288
+ severity: string;
289
+ }>>;
290
+
291
+ /**
292
+ * Stable Finding ID Generation
293
+ *
294
+ * Generates deterministic, stable IDs for findings that survive:
295
+ * - Re-runs
296
+ * - Ordering changes
297
+ * - Remediation/message tweaks
298
+ * - Minor code refactors
299
+ *
300
+ * IDs are NOT stable across:
301
+ * - Moving code to different files
302
+ * - Renaming functions (these are legitimately different anchors)
303
+ *
304
+ * Format: `${invariantId}:${hash}` (e.g., WEBHOOK.IDEMPOTENT:9c31f0a2b4d1)
305
+ */
306
+
307
+ /**
308
+ * Extract the identity payload for a finding.
309
+ * This is what gets hashed to produce the findingId.
310
+ */
311
+ declare function extractIdentityPayload(finding: Finding): Record<string, string>;
312
+ /**
313
+ * Generate a stable findingId for a finding.
314
+ *
315
+ * @example
316
+ * const id = generateFindingId(finding);
317
+ * // "WEBHOOK.IDEMPOTENT:9c31f0a2b4d1"
318
+ */
319
+ declare function generateFindingId(finding: Finding): string;
320
+ /**
321
+ * Add findingId to a finding (mutates the finding).
322
+ * Returns the same finding for chaining.
323
+ */
324
+ declare function attachFindingId<T extends Finding>(finding: T): T & {
325
+ findingId: string;
326
+ };
327
+ /**
328
+ * Add findingIds to all findings in a list.
329
+ */
330
+ declare function attachFindingIds<T extends Finding>(findings: T[]): (T & {
331
+ findingId: string;
332
+ })[];
333
+
334
+ /**
335
+ * Baseline and Waiver Schema Versions
336
+ *
337
+ * These versions are CLI-level (separate from collector artifact schema).
338
+ * Bump these when the storage format changes.
339
+ */
340
+ declare const BASELINE_SCHEMA_VERSION = "1.0.0";
341
+ declare const WAIVER_SCHEMA_VERSION = "1.1.0";
342
+ declare const WAIVER_REASON_KEYS: readonly ["false_positive", "acceptable_risk", "will_fix_later", "not_applicable", "other"];
343
+ type WaiverReasonKey = typeof WAIVER_REASON_KEYS[number];
344
+ /**
345
+ * A baseline entry represents a known finding that should not fail CI.
346
+ * Baselines are used to adopt scheck incrementally on existing codebases.
347
+ */
348
+ interface BaselineEntry {
349
+ /** Stable finding ID (invariantId:hash) */
350
+ findingId: string;
351
+ /** The invariant this finding belongs to */
352
+ invariantId: string;
353
+ /** File where the finding was detected */
354
+ file: string;
355
+ /** Symbol (function/class name) if available */
356
+ symbol?: string;
357
+ /** When this was first added to baseline */
358
+ createdAt: string;
359
+ /** When this was last seen in a run */
360
+ lastSeenAt: string;
361
+ /** Optional notes explaining why this is baselined */
362
+ notes?: string;
363
+ }
364
+ /**
365
+ * The baseline file format.
366
+ */
367
+ interface BaselineFile {
368
+ /** Schema version for baseline file format */
369
+ schemaVersion: string;
370
+ /** CLI version that generated this file */
371
+ toolVersion: string;
372
+ /** Collector schema version used when generating findings */
373
+ collectorSchemaVersion?: string;
374
+ /** Tool identifier (e.g., "@securitychecks/cli@0.1.0") */
375
+ generatedBy: string;
376
+ /** When the baseline was last updated (UTC ISO date) */
377
+ updatedAt: string;
378
+ /** Baseline entries keyed by findingId for O(1) lookup */
379
+ entries: Record<string, BaselineEntry>;
380
+ }
381
+ /**
382
+ * A waiver temporarily suppresses a finding.
383
+ * Unlike baselines, waivers expire and require explicit justification.
384
+ */
385
+ interface WaiverEntry {
386
+ /** Stable finding ID (invariantId:hash) */
387
+ findingId: string;
388
+ /** The invariant this waiver applies to */
389
+ invariantId: string;
390
+ /** File where the finding was detected */
391
+ file: string;
392
+ /** Symbol (function/class name) if available */
393
+ symbol?: string;
394
+ /** Structured waiver reason (optional; aligns with web UI) */
395
+ reasonKey?: WaiverReasonKey;
396
+ /** Why this is being waived (required) */
397
+ reason: string;
398
+ /** Who created the waiver */
399
+ owner: string;
400
+ /** When the waiver expires (ISO date) */
401
+ expiresAt: string;
402
+ /** When the waiver was created */
403
+ createdAt: string;
404
+ }
405
+ /**
406
+ * The waiver file format.
407
+ */
408
+ interface WaiverFile {
409
+ /** Schema version for waiver file format */
410
+ schemaVersion: string;
411
+ /** CLI version that generated this file */
412
+ toolVersion: string;
413
+ /** Tool identifier (e.g., "@securitychecks/cli@0.1.0") */
414
+ generatedBy: string;
415
+ /** When the waiver file was last updated (UTC ISO date) */
416
+ updatedAt: string;
417
+ /** Waiver entries keyed by findingId */
418
+ entries: Record<string, WaiverEntry>;
419
+ }
420
+
421
+ /**
422
+ * Baseline and Waiver Storage
423
+ *
424
+ * Handles loading, saving, and managing baseline/waiver files.
425
+ * Files are stored in `.scheck/` directory.
426
+ */
427
+
428
+ /**
429
+ * Load the baseline file, creating an empty one if it doesn't exist.
430
+ */
431
+ declare function loadBaseline(rootPath: string): Promise<BaselineFile>;
432
+ /**
433
+ * Save the baseline file with deterministic ordering.
434
+ */
435
+ declare function saveBaseline(rootPath: string, baseline: BaselineFile, collectorSchemaVersion?: string): Promise<void>;
436
+ /**
437
+ * Add findings to the baseline.
438
+ * Returns the number of new entries added.
439
+ */
440
+ declare function addToBaseline(baseline: BaselineFile, findings: Finding[], notes?: string): number;
441
+ /**
442
+ * Check if a finding is in the baseline.
443
+ */
444
+ declare function isInBaseline(baseline: BaselineFile, finding: Finding): boolean;
445
+ /**
446
+ * Remove stale entries that haven't been seen in a certain number of days.
447
+ * Returns the number of entries removed.
448
+ */
449
+ declare function pruneBaseline(baseline: BaselineFile, staleDays?: number): number;
450
+ /**
451
+ * Load the waiver file, creating an empty one if it doesn't exist.
452
+ */
453
+ declare function loadWaivers(rootPath: string): Promise<WaiverFile>;
454
+ /**
455
+ * Save the waiver file with deterministic ordering.
456
+ */
457
+ declare function saveWaivers(rootPath: string, waivers: WaiverFile): Promise<void>;
458
+ /**
459
+ * Add a waiver for a finding.
460
+ */
461
+ declare function addWaiver(waivers: WaiverFile, finding: Finding, options: {
462
+ reason: string;
463
+ reasonKey?: WaiverEntry['reasonKey'];
464
+ owner: string;
465
+ expiresInDays: number;
466
+ }): WaiverEntry;
467
+ /**
468
+ * Check if a finding has a valid (non-expired) waiver.
469
+ * Returns the waiver if valid, undefined if expired or not found.
470
+ */
471
+ declare function getValidWaiver(waivers: WaiverFile, finding: Finding): WaiverEntry | undefined;
472
+ /**
473
+ * Remove expired waivers from the file.
474
+ * Returns the number of waivers removed.
475
+ */
476
+ declare function pruneExpiredWaivers(waivers: WaiverFile): number;
477
+ /**
478
+ * Get all waivers that are about to expire (within N days).
479
+ */
480
+ declare function getExpiringWaivers(waivers: WaiverFile, withinDays?: number): WaiverEntry[];
481
+
482
+ /**
483
+ * Baseline and Waiver Matching
484
+ *
485
+ * Applies baselines and waivers to findings, categorizing them for CI decisions.
486
+ */
487
+
488
+ /**
489
+ * A finding with its baseline/waiver status resolved.
490
+ */
491
+ interface CategorizedFinding extends Finding {
492
+ findingId: string;
493
+ /** Whether this finding is in the baseline */
494
+ isBaselined: boolean;
495
+ /** Active waiver if present */
496
+ waiver?: WaiverEntry;
497
+ /** Whether this finding should fail CI */
498
+ shouldFail: boolean;
499
+ }
500
+ /**
501
+ * Result of categorizing findings.
502
+ */
503
+ interface CategorizationResult {
504
+ /** All findings with status */
505
+ all: CategorizedFinding[];
506
+ /** New findings not in baseline (may fail CI) */
507
+ new: CategorizedFinding[];
508
+ /** Findings in baseline (won't fail CI) */
509
+ baselined: CategorizedFinding[];
510
+ /** Findings with active waivers (won't fail CI) */
511
+ waived: CategorizedFinding[];
512
+ /** Summary counts */
513
+ counts: {
514
+ total: number;
515
+ new: number;
516
+ baselined: number;
517
+ waived: number;
518
+ willFail: number;
519
+ };
520
+ }
521
+ /**
522
+ * Categorize findings against baseline and waivers.
523
+ *
524
+ * @param findings - Raw findings from checkers
525
+ * @param baseline - Loaded baseline file
526
+ * @param waivers - Loaded waiver file
527
+ * @param failSeverities - Which severities should fail CI (default: P0, P1)
528
+ */
529
+ declare function categorizeFindings(findings: Finding[], baseline: BaselineFile, waivers: WaiverFile, failSeverities?: Severity[]): CategorizationResult;
530
+ /**
531
+ * Determine CI exit status based on categorized findings.
532
+ *
533
+ * @returns 0 for success, 1 for failure
534
+ */
535
+ declare function getCIExitCode(result: CategorizationResult): number;
536
+ /**
537
+ * Get a summary message for CI output.
538
+ */
539
+ declare function getCISummary(result: CategorizationResult): string;
540
+ /**
541
+ * Detect and handle findingId collisions.
542
+ * Returns findings with unique IDs (appending :a, :b, etc. if needed).
543
+ *
544
+ * This is a defensive measure - collisions should be extremely rare with
545
+ * 12 hex chars of SHA-256, but we define the behavior explicitly.
546
+ */
547
+ declare function resolveCollisions(findings: Finding[]): (Finding & {
548
+ findingId: string;
549
+ })[];
550
+ /**
551
+ * Check if there are any findingId collisions in a set of findings.
552
+ * Returns true if collisions exist.
553
+ */
554
+ declare function hasCollisions(findings: Finding[]): boolean;
555
+
556
+ /**
557
+ * Finding Correlation Engine
558
+ *
559
+ * Correlates findings across invariants to detect compounding risks.
560
+ * Multiple findings on the same code path often compound each other,
561
+ * creating worse outcomes than the sum of their parts.
562
+ *
563
+ * Key concepts:
564
+ * - Correlated findings: Multiple findings sharing a code path
565
+ * - Compounding risk: Combined severity is higher than individual
566
+ * - Attack path: Narrative of how findings chain together
567
+ *
568
+ * Example:
569
+ * Route: POST /api/webhooks/stripe
570
+ * ├── WEBHOOK.IDEMPOTENT: ✗ No idempotency check
571
+ * ├── TRANSACTION.POST_COMMIT: ✗ Email inside transaction
572
+ * └── Combined: If replayed, sends duplicate emails AND inconsistent DB
573
+ */
574
+
575
+ interface CorrelatedFinding {
576
+ /** The primary finding (highest severity) */
577
+ primary: Finding;
578
+ /** Related findings on the same code path */
579
+ related: Finding[];
580
+ /** Shared context between findings */
581
+ sharedContext: SharedContext;
582
+ /** How the findings compound each other */
583
+ compoundingEffect: CompoundingEffect;
584
+ /** Adjusted severity based on correlation */
585
+ adjustedSeverity: Severity;
586
+ /** Attack path narrative */
587
+ attackPath?: AttackPath;
588
+ }
589
+ interface SharedContext {
590
+ /** Common file */
591
+ file?: string;
592
+ /** Common function */
593
+ functionName?: string;
594
+ /** Common route (if applicable) */
595
+ route?: string;
596
+ /** Shared call chain */
597
+ callChain?: string[];
598
+ /** Number of findings in this correlation */
599
+ findingCount: number;
600
+ }
601
+ interface CompoundingEffect {
602
+ /** Description of how findings compound */
603
+ description: string;
604
+ /** Risk multiplier (1.0 = no change, 2.0 = double risk) */
605
+ riskMultiplier: number;
606
+ /** Signals explaining the compounding */
607
+ signals: string[];
608
+ }
609
+ interface AttackPath {
610
+ /** Title of the attack path */
611
+ title: string;
612
+ /** Step-by-step narrative */
613
+ steps: AttackStep[];
614
+ /** Overall exploitability */
615
+ exploitability: 'easy' | 'medium' | 'hard';
616
+ /** Impact level */
617
+ impact: 'low' | 'medium' | 'high' | 'critical';
618
+ /** Time window (if applicable) */
619
+ timeWindow?: string;
620
+ }
621
+ interface AttackStep {
622
+ /** Step number */
623
+ step: number;
624
+ /** Description of this step */
625
+ description: string;
626
+ /** Which finding enables this step */
627
+ invariantId: string;
628
+ /** File/line reference */
629
+ location?: {
630
+ file: string;
631
+ line: number;
632
+ };
633
+ }
634
+ interface CorrelationResult {
635
+ /** All correlated finding groups */
636
+ correlations: CorrelatedFinding[];
637
+ /** Statistics */
638
+ stats: {
639
+ totalFindings: number;
640
+ correlatedFindings: number;
641
+ correlationGroups: number;
642
+ severityEscalations: number;
643
+ };
644
+ }
645
+ /**
646
+ * Correlate findings to detect compounding risks
647
+ */
648
+ declare function correlateFindings(results: CheckResult[], artifact: Artifact): CorrelationResult;
649
+ /**
650
+ * Format a correlated finding for display
651
+ */
652
+ declare function formatCorrelatedFinding(correlation: CorrelatedFinding): string;
653
+ /**
654
+ * Format correlation statistics
655
+ */
656
+ declare function formatCorrelationStats(result: CorrelationResult): string;
657
+
658
+ /**
659
+ * Correlation Telemetry
660
+ *
661
+ * Reports correlation data to the SecurityChecks SaaS.
662
+ * This builds the data moat around which invariant combinations
663
+ * actually compound risk in production codebases.
664
+ */
665
+
666
+ interface CorrelationTelemetryConfig {
667
+ enabled: boolean;
668
+ endpoint?: string;
669
+ apiKey?: string;
670
+ timeout?: number;
671
+ }
672
+ /**
673
+ * Report correlation results to the SaaS
674
+ */
675
+ declare function reportCorrelations(result: CorrelationResult, config: CorrelationTelemetryConfig, framework?: string): Promise<{
676
+ success: boolean;
677
+ stored?: number;
678
+ errors?: number;
679
+ }>;
680
+ /**
681
+ * Report feedback on a correlation (user marking as accurate/inaccurate)
682
+ */
683
+ declare function reportCorrelationFeedback(requestId: string, wasAccurate: boolean, reason?: string, config?: CorrelationTelemetryConfig): Promise<boolean>;
684
+
685
+ /**
686
+ * Anonymous Telemetry
687
+ *
688
+ * Reports aggregate scan statistics to the SecurityChecks SaaS.
689
+ * NO source code, NO file paths, NO PII - just patterns and numbers.
690
+ *
691
+ * This data powers:
692
+ * - Framework-specific calibration
693
+ * - Pattern effectiveness tracking
694
+ * - Invariant impact analysis
695
+ */
696
+
697
+ interface TelemetryConfig {
698
+ enabled: boolean;
699
+ endpoint?: string;
700
+ apiKey?: string;
701
+ timeout?: number;
702
+ }
703
+ interface ScanTelemetry {
704
+ scanId: string;
705
+ codebase: {
706
+ filesScanned: number;
707
+ servicesCount: number;
708
+ linesOfCode?: number;
709
+ };
710
+ frameworks: string[];
711
+ findings: {
712
+ byInvariant: Record<string, number>;
713
+ byPriority: {
714
+ P0: number;
715
+ P1: number;
716
+ P2: number;
717
+ };
718
+ total: number;
719
+ };
720
+ correlation?: {
721
+ groups: number;
722
+ escalations: number;
723
+ correlatedFindings: number;
724
+ };
725
+ calibration?: {
726
+ calibrated: number;
727
+ suppressed: number;
728
+ };
729
+ patterns?: {
730
+ applied: number;
731
+ findings: number;
732
+ };
733
+ meta: {
734
+ duration?: number;
735
+ clientVersion: string;
736
+ mode?: 'ci' | 'manual' | 'watch';
737
+ ciProvider?: string;
738
+ };
739
+ baseline?: {
740
+ size: number;
741
+ waivers: number;
742
+ newFindings: number;
743
+ };
744
+ feedback?: {
745
+ waivedCount: number;
746
+ waiverReasons: Record<string, number>;
747
+ baselinedCount: number;
748
+ };
749
+ }
750
+ /**
751
+ * Build telemetry data from scan results
752
+ */
753
+ declare function buildTelemetry(result: AuditResult, options: {
754
+ filesScanned: number;
755
+ frameworks: string[];
756
+ correlation?: CorrelationResult;
757
+ categorization?: CategorizationResult;
758
+ calibratedCount?: number;
759
+ suppressedCount?: number;
760
+ patternsApplied?: number;
761
+ patternFindings?: number;
762
+ mode?: 'ci' | 'manual' | 'watch';
763
+ baselineSize?: number;
764
+ waiversCount?: number;
765
+ }): ScanTelemetry;
766
+ /**
767
+ * Report telemetry to the SaaS
768
+ */
769
+ declare function reportTelemetry(telemetry: ScanTelemetry, config: TelemetryConfig): Promise<boolean>;
770
+ /**
771
+ * Check if telemetry is opt-out
772
+ */
773
+ declare function isTelemetryDisabled(): boolean;
774
+
775
+ /**
776
+ * Calibration API Client
777
+ *
778
+ * "The SaaS advises. The local tool decides."
779
+ *
780
+ * This module handles communication with the SecurityChecks Calibration API.
781
+ * The API provides confidence tuning based on aggregate data from many codebases.
782
+ *
783
+ * Key principles:
784
+ * - No source code is ever sent (only patterns and metadata)
785
+ * - API suggestions are advisory only
786
+ * - Local tool retains veto power via minConfidence threshold
787
+ * - Fails safely to local-only mode on network errors
788
+ */
789
+
790
+ interface AggregateCalibrationConfig {
791
+ enabled: boolean;
792
+ endpoint?: string;
793
+ apiKey?: string;
794
+ timeout?: number;
795
+ cacheEnabled?: boolean;
796
+ }
797
+ interface FrameworkBaseline {
798
+ framework: string;
799
+ avgFindings: number;
800
+ avgP0: number;
801
+ avgP1: number;
802
+ avgP2: number;
803
+ scansAnalyzed: number;
804
+ confidence: 'high' | 'medium' | 'low';
805
+ }
806
+ interface InvariantStats {
807
+ invariantId: string;
808
+ avgPerScan: number;
809
+ hitRate: number;
810
+ p0Rate: number;
811
+ p1Rate: number;
812
+ p2Rate: number;
813
+ }
814
+ interface PatternStats {
815
+ patternId: string;
816
+ framework: string | null;
817
+ accuracy: number | null;
818
+ matchesPerScan: number;
819
+ confidence: 'high' | 'medium' | 'low';
820
+ }
821
+ interface CorrelationStats {
822
+ ruleId: string;
823
+ accuracy: number | null;
824
+ escalationRate: number | null;
825
+ isVerified: boolean;
826
+ }
827
+ interface AggregateCalibrationData {
828
+ version: string;
829
+ generatedAt: string;
830
+ frameworks: FrameworkBaseline[];
831
+ invariants: InvariantStats[];
832
+ patterns: PatternStats[];
833
+ correlations: CorrelationStats[];
834
+ meta: {
835
+ totalScansAnalyzed: number;
836
+ lastUpdated: string | null;
837
+ };
838
+ }
839
+ interface AggregateCalibrationResult {
840
+ data: AggregateCalibrationData | null;
841
+ fromCache: boolean;
842
+ error?: string;
843
+ }
844
+ /**
845
+ * Fetch aggregate calibration data for the specified frameworks
846
+ */
847
+ declare function fetchAggregateCalibration(frameworks: string[], config: AggregateCalibrationConfig): Promise<AggregateCalibrationResult>;
848
+ /**
849
+ * Clear the aggregate calibration cache
850
+ */
851
+ declare function clearAggregateCache(): void;
852
+ /**
853
+ * Get the framework baseline for comparison
854
+ */
855
+ declare function getFrameworkBaseline(calibration: AggregateCalibrationData, framework: string): FrameworkBaseline | undefined;
856
+ /**
857
+ * Check if a pattern should be skipped due to low accuracy
858
+ */
859
+ declare function shouldSkipPattern(calibration: AggregateCalibrationData, patternId: string, framework?: string, accuracyThreshold?: number): boolean;
860
+ /**
861
+ * Get patterns that should be skipped for a framework
862
+ */
863
+ declare function getSkippedPatterns(calibration: AggregateCalibrationData, framework?: string, accuracyThreshold?: number): string[];
864
+ /**
865
+ * Get correlation rules with high confidence
866
+ */
867
+ declare function getVerifiedCorrelations(calibration: AggregateCalibrationData): CorrelationStats[];
868
+ /**
869
+ * Calculate relative finding severity based on framework baseline
870
+ */
871
+ declare function calculateRelativeSeverity(findingCount: number, baseline: FrameworkBaseline, type?: 'total' | 'P0' | 'P1' | 'P2'): 'below_average' | 'average' | 'above_average' | 'critical';
872
+ /**
873
+ * Generate calibration summary for output
874
+ */
875
+ declare function formatAggregateCalibrationSummary(calibration: AggregateCalibrationData, frameworks: string[], findings: {
876
+ P0: number;
877
+ P1: number;
878
+ P2: number;
879
+ total: number;
880
+ }): string;
881
+ /**
882
+ * Check if aggregate calibration is disabled via environment
883
+ */
884
+ declare function isAggregateCalibrationDisabled(): boolean;
885
+
886
+ /**
887
+ * Staff Engineer Templates
888
+ *
889
+ * Shared, stable UX helpers used by:
890
+ * - CLI output (human-readable prompts)
891
+ * - MCP adapters (Claude Code, etc.)
892
+ *
893
+ * Keep these centralized to avoid drift across integration surfaces.
894
+ */
895
+ type TestFramework = 'jest' | 'vitest' | 'playwright' | (string & {});
896
+ type InvariantLike = {
897
+ id: string;
898
+ name: string;
899
+ requiredProof?: string;
900
+ };
901
+ /**
902
+ * Returns the "A staff engineer would ask..." question for each invariant.
903
+ * These are the probing questions that senior engineers ask in code review.
904
+ */
905
+ declare function getStaffQuestion(invariantId: string): string | null;
906
+ declare function generateTestSkeleton(invariant: InvariantLike | null | undefined, framework: TestFramework, context?: string): string;
907
+
908
+ export { type AggregateCalibrationConfig, type AggregateCalibrationData, type AggregateCalibrationResult, type AttackPath, type AttackStep, type AuditOptions, BASELINE_SCHEMA_VERSION, type BaselineEntry, type BaselineFile, CLIError, type CategorizationResult, type CategorizedFinding, type CloudEvaluateOptions, type CloudEvaluateResult, type CompoundingEffect, type CorrelatedFinding, type CorrelationResult, type CorrelationStats, type CorrelationTelemetryConfig, type ErrorCode, ErrorCodes, ErrorMessages, ErrorRemediation, type EvaluationProgressCallback, type FrameworkBaseline, type InvariantLike, type InvariantStats, type PatternStats, SUPPORTED_SCHEMA_RANGE, type ScanTelemetry, type SchemaValidationResult, type SharedContext, type TelemetryConfig, type TestFramework, WAIVER_SCHEMA_VERSION, type WaiverEntry, type WaiverFile, addToBaseline, addWaiver, attachFindingId, attachFindingIds, audit, buildTelemetry, calculateRelativeSeverity, categorizeFindings, checkCloudHealth, clearAggregateCache, correlateFindings, evaluateCloud, extractIdentityPayload, fetchAggregateCalibration, formatAggregateCalibrationSummary, formatCorrelatedFinding, formatCorrelationStats, generateFindingId, generateTestSkeleton, getCIExitCode, getCISummary, getCloudInvariants, getCurrentSchemaVersion, getExpiringWaivers, getFrameworkBaseline, getSkippedPatterns, getStaffQuestion, getValidWaiver, getVerifiedCorrelations, hasCollisions, isAggregateCalibrationDisabled, isCLIError, isCloudEvalAvailable, isInBaseline, isTelemetryDisabled, loadBaseline, loadWaivers, pruneBaseline, pruneExpiredWaivers, reportCorrelationFeedback, reportCorrelations, reportTelemetry, resolveCollisions, saveBaseline, saveWaivers, shouldSkipPattern, validateSchemaVersion, wrapError };