@vibecheckai/cli 3.5.0 → 3.5.2
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/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Configuration for Vibecheck Scans
|
|
3
|
+
*
|
|
4
|
+
* Provides default exclusions and limits for all scan operations.
|
|
5
|
+
* Cross-platform compatible (Windows, macOS, Linux).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
// DEFAULT EXCLUSIONS
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
|
+
|
|
14
|
+
const DEFAULT_EXCLUSIONS = [
|
|
15
|
+
// Dependencies
|
|
16
|
+
'node_modules',
|
|
17
|
+
'vendor',
|
|
18
|
+
'.venv',
|
|
19
|
+
'venv',
|
|
20
|
+
'__pycache__',
|
|
21
|
+
'.pip',
|
|
22
|
+
|
|
23
|
+
// Build outputs
|
|
24
|
+
'dist',
|
|
25
|
+
'build',
|
|
26
|
+
'.next',
|
|
27
|
+
'out',
|
|
28
|
+
'.turbo',
|
|
29
|
+
'coverage',
|
|
30
|
+
|
|
31
|
+
// Version control
|
|
32
|
+
'.git',
|
|
33
|
+
'.svn',
|
|
34
|
+
'.hg',
|
|
35
|
+
|
|
36
|
+
// IDE
|
|
37
|
+
'.idea',
|
|
38
|
+
'.vscode',
|
|
39
|
+
'.cursor',
|
|
40
|
+
|
|
41
|
+
// Vibecheck artifacts
|
|
42
|
+
'.vibecheck',
|
|
43
|
+
'.guardrail',
|
|
44
|
+
|
|
45
|
+
// Logs and temp
|
|
46
|
+
'logs',
|
|
47
|
+
'*.log',
|
|
48
|
+
'tmp',
|
|
49
|
+
'temp',
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
53
|
+
// FILE SIZE LIMITS
|
|
54
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
const MAX_FILE_SIZE_BYTES = 1024 * 1024; // 1MB
|
|
57
|
+
const MAX_FILES_TO_SCAN = 10000;
|
|
58
|
+
|
|
59
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
60
|
+
// HELPER FUNCTIONS
|
|
61
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if a file path should be excluded based on default patterns.
|
|
65
|
+
* Handles Windows path separators correctly.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} filePath - File path to check (can be absolute or relative)
|
|
68
|
+
* @param {string[]} [additionalExclusions] - Additional exclusion patterns
|
|
69
|
+
* @returns {boolean} True if file should be excluded
|
|
70
|
+
*/
|
|
71
|
+
function shouldExclude(filePath, additionalExclusions = []) {
|
|
72
|
+
const allExclusions = [...DEFAULT_EXCLUSIONS, ...additionalExclusions];
|
|
73
|
+
|
|
74
|
+
// Normalize path separators (Windows uses \, Unix uses /)
|
|
75
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
76
|
+
|
|
77
|
+
// Split path into segments
|
|
78
|
+
const segments = normalized.split('/');
|
|
79
|
+
|
|
80
|
+
for (const pattern of allExclusions) {
|
|
81
|
+
// Handle glob patterns (e.g., *.log)
|
|
82
|
+
if (pattern.includes('*')) {
|
|
83
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*').replace(/\./g, '\\.'));
|
|
84
|
+
if (regex.test(normalized) || segments.some(seg => regex.test(seg))) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
// Exact match or directory match
|
|
89
|
+
if (segments.includes(pattern) ||
|
|
90
|
+
normalized.includes(`/${pattern}/`) ||
|
|
91
|
+
normalized.startsWith(`${pattern}/`) ||
|
|
92
|
+
normalized.endsWith(`/${pattern}`)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Check if a file exceeds size limits
|
|
103
|
+
*
|
|
104
|
+
* @param {number} fileSizeBytes - File size in bytes
|
|
105
|
+
* @returns {boolean} True if file exceeds limit
|
|
106
|
+
*/
|
|
107
|
+
function exceedsSizeLimit(fileSizeBytes) {
|
|
108
|
+
return fileSizeBytes > MAX_FILE_SIZE_BYTES;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get default exclusion patterns
|
|
113
|
+
*
|
|
114
|
+
* @returns {string[]} Array of exclusion patterns
|
|
115
|
+
*/
|
|
116
|
+
function getDefaultExclusions() {
|
|
117
|
+
return [...DEFAULT_EXCLUSIONS];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
module.exports = {
|
|
121
|
+
DEFAULT_EXCLUSIONS,
|
|
122
|
+
MAX_FILE_SIZE_BYTES,
|
|
123
|
+
MAX_FILES_TO_SCAN,
|
|
124
|
+
shouldExclude,
|
|
125
|
+
exceedsSizeLimit,
|
|
126
|
+
getDefaultExclusions,
|
|
127
|
+
};
|
|
@@ -257,10 +257,12 @@ function createDiagnostics(projectPath) {
|
|
|
257
257
|
|
|
258
258
|
try {
|
|
259
259
|
const { execSync } = require('child_process');
|
|
260
|
-
|
|
260
|
+
// Windows-compatible: use -n 1 instead of piping to head
|
|
261
|
+
const result = execSync('git log --all --full-history -n 1 -- .env', {
|
|
261
262
|
cwd: projectPath,
|
|
262
263
|
encoding: 'utf8',
|
|
263
264
|
timeout: 10000,
|
|
265
|
+
stdio: ['pipe', 'pipe', 'pipe'], // Suppress stderr on all platforms
|
|
264
266
|
}).trim();
|
|
265
267
|
|
|
266
268
|
if (result) {
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// bin/runners/lib/engine/ast-cache.js
|
|
2
|
+
// Shared AST cache with content-hash keying
|
|
3
|
+
|
|
4
|
+
const parser = require("@babel/parser");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Compute SHA256 hash
|
|
9
|
+
* @param {string} content
|
|
10
|
+
* @returns {string}
|
|
11
|
+
*/
|
|
12
|
+
function sha256(content) {
|
|
13
|
+
return crypto.createHash("sha256").update(content).digest("hex");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Default Babel parser options for maximum compatibility
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
20
|
+
sourceType: "unambiguous",
|
|
21
|
+
errorRecovery: true,
|
|
22
|
+
allowImportExportEverywhere: true,
|
|
23
|
+
plugins: [
|
|
24
|
+
"typescript",
|
|
25
|
+
"jsx",
|
|
26
|
+
"dynamicImport",
|
|
27
|
+
"importMeta",
|
|
28
|
+
"topLevelAwait",
|
|
29
|
+
"classProperties",
|
|
30
|
+
"classPrivateProperties",
|
|
31
|
+
"classPrivateMethods",
|
|
32
|
+
"optionalChaining",
|
|
33
|
+
"nullishCoalescingOperator",
|
|
34
|
+
"decorators-legacy",
|
|
35
|
+
],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* ASTCache - Content-hash keyed AST cache
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* const cache = new ASTCache();
|
|
43
|
+
* const ast = cache.parse(content, filePath);
|
|
44
|
+
*
|
|
45
|
+
* // Later, same content will return cached AST
|
|
46
|
+
* const ast2 = cache.parse(content, filePath); // cache hit
|
|
47
|
+
*/
|
|
48
|
+
class ASTCache {
|
|
49
|
+
/**
|
|
50
|
+
* @param {Object} [options]
|
|
51
|
+
* @param {number} [options.maxEntries] - Max cache entries (default: 5000)
|
|
52
|
+
* @param {Object} [options.parserOptions] - Babel parser options override
|
|
53
|
+
*/
|
|
54
|
+
constructor(options = {}) {
|
|
55
|
+
this.maxEntries = options.maxEntries || 5000;
|
|
56
|
+
this.parserOptions = { ...DEFAULT_PARSER_OPTIONS, ...options.parserOptions };
|
|
57
|
+
|
|
58
|
+
/** @type {Map<string, { ast: any, accessCount: number }>} hash -> { ast, accessCount } */
|
|
59
|
+
this._cache = new Map();
|
|
60
|
+
|
|
61
|
+
this.stats = {
|
|
62
|
+
hits: 0,
|
|
63
|
+
misses: 0,
|
|
64
|
+
parseErrors: 0,
|
|
65
|
+
evictions: 0,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse code and cache the AST
|
|
71
|
+
* @param {string} content - Source code
|
|
72
|
+
* @param {string} [filePath] - Optional file path for error messages
|
|
73
|
+
* @returns {{ ast: any, error: Error|null }}
|
|
74
|
+
*/
|
|
75
|
+
parse(content, filePath) {
|
|
76
|
+
const hash = sha256(content);
|
|
77
|
+
|
|
78
|
+
// Check cache
|
|
79
|
+
if (this._cache.has(hash)) {
|
|
80
|
+
const entry = this._cache.get(hash);
|
|
81
|
+
entry.accessCount++;
|
|
82
|
+
this.stats.hits++;
|
|
83
|
+
return { ast: entry.ast, error: null };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this.stats.misses++;
|
|
87
|
+
|
|
88
|
+
// Parse
|
|
89
|
+
let ast = null;
|
|
90
|
+
let error = null;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
ast = parser.parse(content, {
|
|
94
|
+
...this.parserOptions,
|
|
95
|
+
sourceFilename: filePath || undefined,
|
|
96
|
+
});
|
|
97
|
+
} catch (e) {
|
|
98
|
+
error = e;
|
|
99
|
+
this.stats.parseErrors++;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Cache if successful
|
|
103
|
+
if (ast) {
|
|
104
|
+
this._cacheEntry(hash, ast);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { ast, error };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get cached AST by content hash
|
|
112
|
+
* @param {string} hash - Content hash
|
|
113
|
+
* @returns {any|null}
|
|
114
|
+
*/
|
|
115
|
+
getByHash(hash) {
|
|
116
|
+
const entry = this._cache.get(hash);
|
|
117
|
+
if (entry) {
|
|
118
|
+
entry.accessCount++;
|
|
119
|
+
this.stats.hits++;
|
|
120
|
+
return entry.ast;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if AST is cached
|
|
127
|
+
* @param {string} content
|
|
128
|
+
* @returns {boolean}
|
|
129
|
+
*/
|
|
130
|
+
has(content) {
|
|
131
|
+
return this._cache.has(sha256(content));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Cache an entry with LRU eviction
|
|
136
|
+
* @param {string} hash
|
|
137
|
+
* @param {any} ast
|
|
138
|
+
*/
|
|
139
|
+
_cacheEntry(hash, ast) {
|
|
140
|
+
// Evict if at capacity
|
|
141
|
+
if (this._cache.size >= this.maxEntries) {
|
|
142
|
+
this._evictLRU();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this._cache.set(hash, { ast, accessCount: 1 });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Evict least recently used entries
|
|
150
|
+
*/
|
|
151
|
+
_evictLRU() {
|
|
152
|
+
// Find entry with lowest access count
|
|
153
|
+
let minKey = null;
|
|
154
|
+
let minCount = Infinity;
|
|
155
|
+
|
|
156
|
+
for (const [key, entry] of this._cache) {
|
|
157
|
+
if (entry.accessCount < minCount) {
|
|
158
|
+
minCount = entry.accessCount;
|
|
159
|
+
minKey = key;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (minKey) {
|
|
164
|
+
this._cache.delete(minKey);
|
|
165
|
+
this.stats.evictions++;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Clear the cache
|
|
171
|
+
*/
|
|
172
|
+
clear() {
|
|
173
|
+
this._cache.clear();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get cache size
|
|
178
|
+
* @returns {number}
|
|
179
|
+
*/
|
|
180
|
+
get size() {
|
|
181
|
+
return this._cache.size;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get cache stats summary
|
|
186
|
+
* @returns {Object}
|
|
187
|
+
*/
|
|
188
|
+
getSummary() {
|
|
189
|
+
const total = this.stats.hits + this.stats.misses;
|
|
190
|
+
const hitRate = total > 0 ? (this.stats.hits / total * 100).toFixed(1) : 0;
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
...this.stats,
|
|
194
|
+
size: this._cache.size,
|
|
195
|
+
hitRate: `${hitRate}%`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Global shared AST cache instance
|
|
202
|
+
* Use this for cross-adapter/analyzer caching
|
|
203
|
+
*/
|
|
204
|
+
const globalASTCache = new ASTCache();
|
|
205
|
+
|
|
206
|
+
module.exports = {
|
|
207
|
+
ASTCache,
|
|
208
|
+
globalASTCache,
|
|
209
|
+
DEFAULT_PARSER_OPTIONS,
|
|
210
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// bin/runners/lib/engine/auth-extractor.js
|
|
2
|
+
// Optimized auth/middleware extraction using RepoIndex
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
function sha256(text) {
|
|
9
|
+
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function evidenceFromContent(content, fileRel, lineNo, reason) {
|
|
13
|
+
if (!content) return null;
|
|
14
|
+
const lines = content.split(/\r?\n/);
|
|
15
|
+
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
16
|
+
const snippet = lines[idx] || "";
|
|
17
|
+
return {
|
|
18
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
19
|
+
file: fileRel,
|
|
20
|
+
lines: `${lineNo}-${lineNo}`,
|
|
21
|
+
snippetHash: sha256(snippet),
|
|
22
|
+
reason
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function findLineMatches(code, regex) {
|
|
27
|
+
const out = [];
|
|
28
|
+
const lines = code.split(/\r?\n/);
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
if (regex.test(lines[i])) out.push(i + 1);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function guessAuthSignalsFromCode(code) {
|
|
36
|
+
const signals = [];
|
|
37
|
+
|
|
38
|
+
const patterns = [
|
|
39
|
+
{ key: "next_middleware", rx: /\bNextResponse\.(redirect|rewrite)\b/ },
|
|
40
|
+
{ key: "next_auth", rx: /\bgetServerSession\b|\bNextAuth\b|\bauth\(\)\b/ },
|
|
41
|
+
{ key: "clerk", rx: /\bclerkMiddleware\b|\bauthMiddleware\b|@clerk\/nextjs/ },
|
|
42
|
+
{ key: "supabase", rx: /\bcreateRouteHandlerClient\b|\bcreateServerClient\b|@supabase/ },
|
|
43
|
+
{ key: "jwt_verify", rx: /\b(jwtVerify|verifyJWT|verifyToken|authorization|bearer)\b/i },
|
|
44
|
+
{ key: "session", rx: /\b(session|cookie|setCookie|getCookie)\b/i },
|
|
45
|
+
{ key: "rbac", rx: /\b(role|roles|permissions|rbac|isAdmin|adminOnly)\b/i },
|
|
46
|
+
{ key: "fastify_hook", rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/ },
|
|
47
|
+
{ key: "fastify_jwt", rx: /@fastify\/jwt|fastify-jwt|fastify\.jwt/i },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const p of patterns) {
|
|
51
|
+
if (p.rx.test(code)) signals.push(p.key);
|
|
52
|
+
}
|
|
53
|
+
return Array.from(new Set(signals));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve Next.js middleware using RepoIndex
|
|
58
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
59
|
+
* @returns {Array}
|
|
60
|
+
*/
|
|
61
|
+
function extractNextMiddleware(index) {
|
|
62
|
+
// Find middleware.ts/js files
|
|
63
|
+
const middlewareFiles = index.files.filter(f =>
|
|
64
|
+
/^(src\/)?middleware\.(ts|tsx|js|jsx)$/.test(f.rel)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const middlewares = [];
|
|
68
|
+
|
|
69
|
+
for (const file of middlewareFiles) {
|
|
70
|
+
const content = index.getContent(file.abs);
|
|
71
|
+
if (!content) continue;
|
|
72
|
+
|
|
73
|
+
const matcherLines = findLineMatches(content, /\bmatcher\b/);
|
|
74
|
+
const redirectLines = findLineMatches(content, /\bNextResponse\.(redirect|rewrite)\b/);
|
|
75
|
+
|
|
76
|
+
const evidence = [];
|
|
77
|
+
for (const ln of matcherLines.slice(0, 5)) {
|
|
78
|
+
evidence.push(evidenceFromContent(content, file.rel, ln, "Next middleware matcher config"));
|
|
79
|
+
}
|
|
80
|
+
for (const ln of redirectLines.slice(0, 5)) {
|
|
81
|
+
evidence.push(evidenceFromContent(content, file.rel, ln, "Next middleware redirect/rewrite"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const matcher = [];
|
|
85
|
+
const matcherBlock = content.match(/matcher\s*:\s*(\[[\s\S]*?\])/);
|
|
86
|
+
if (matcherBlock && matcherBlock[1]) {
|
|
87
|
+
const raw = matcherBlock[1];
|
|
88
|
+
const strings = Array.from(raw.matchAll(/['"`]([^'"`]+)['"`]/g)).map(m => m[1]);
|
|
89
|
+
matcher.push(...strings);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
middlewares.push({
|
|
93
|
+
file: file.rel,
|
|
94
|
+
matcher,
|
|
95
|
+
signals: guessAuthSignalsFromCode(content),
|
|
96
|
+
evidence
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return middlewares;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Resolve Fastify auth signals using RepoIndex
|
|
105
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
106
|
+
* @param {Array} truthpackRoutes - Server routes from truthpack
|
|
107
|
+
* @returns {Object}
|
|
108
|
+
*/
|
|
109
|
+
function extractFastifyAuthSignals(index, truthpackRoutes) {
|
|
110
|
+
const handlerFiles = new Set((truthpackRoutes || []).map(r => r.handler).filter(Boolean));
|
|
111
|
+
const signals = [];
|
|
112
|
+
const evidence = [];
|
|
113
|
+
|
|
114
|
+
for (const fileRel of handlerFiles) {
|
|
115
|
+
// Try to get content from index first (fast path)
|
|
116
|
+
const fileAbs = path.join(index.repoRoot, fileRel);
|
|
117
|
+
let content = index.getContent(fileAbs);
|
|
118
|
+
|
|
119
|
+
// Fall back to direct read if not in index
|
|
120
|
+
if (!content) {
|
|
121
|
+
try {
|
|
122
|
+
content = fs.readFileSync(fileAbs, "utf8");
|
|
123
|
+
} catch {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const sigs = guessAuthSignalsFromCode(content);
|
|
129
|
+
if (!sigs.length) continue;
|
|
130
|
+
|
|
131
|
+
for (const s of sigs) signals.push({ type: s, file: fileRel });
|
|
132
|
+
|
|
133
|
+
const authLinePatterns = [
|
|
134
|
+
{ rx: /\.addHook\(\s*['"](onRequest|preHandler|preValidation)['"]/, reason: "Fastify hook likely used for auth" },
|
|
135
|
+
{ rx: /\b(jwtVerify|authorization|bearer)\b/i, reason: "JWT/Authorization verification signal" },
|
|
136
|
+
{ rx: /@fastify\/jwt|fastify\.jwt/i, reason: "Fastify JWT plugin signal" },
|
|
137
|
+
{ rx: /\b(isAdmin|adminOnly|permissions|rbac)\b/i, reason: "RBAC/permissions signal" },
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const lines = content.split(/\r?\n/);
|
|
141
|
+
for (let i = 0; i < lines.length; i++) {
|
|
142
|
+
const line = lines[i];
|
|
143
|
+
for (const p of authLinePatterns) {
|
|
144
|
+
if (p.rx.test(line)) {
|
|
145
|
+
evidence.push(evidenceFromContent(content, fileRel, i + 1, p.reason));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (evidence.length > 30) break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const uniqueTypes = Array.from(new Set(signals.map(s => s.type)));
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
signalTypes: uniqueTypes,
|
|
156
|
+
signals,
|
|
157
|
+
evidence
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function matcherCoversPath(matcherList, p) {
|
|
162
|
+
if (!Array.isArray(matcherList) || !matcherList.length) return false;
|
|
163
|
+
const pathStr = p.startsWith("/") ? p : `/${p}`;
|
|
164
|
+
|
|
165
|
+
return matcherList.some(m => {
|
|
166
|
+
if (!m) return false;
|
|
167
|
+
|
|
168
|
+
if (m.includes(":path*")) {
|
|
169
|
+
const prefix = m.split(":path*")[0].replace(/\/$/, "");
|
|
170
|
+
return pathStr.startsWith(prefix || "/");
|
|
171
|
+
}
|
|
172
|
+
if (m.includes("(.*)")) {
|
|
173
|
+
const prefix = m.split("(.*)")[0].replace(/\/$/, "");
|
|
174
|
+
return pathStr.startsWith(prefix || "/");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (m === pathStr) return true;
|
|
178
|
+
if (pathStr.startsWith(m.endsWith("/") ? m : m + "/")) return true;
|
|
179
|
+
|
|
180
|
+
return false;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build auth truth using RepoIndex (optimized)
|
|
186
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
187
|
+
* @param {Array} serverRoutes
|
|
188
|
+
* @returns {Object}
|
|
189
|
+
*/
|
|
190
|
+
function buildAuthTruthV2(index, serverRoutes) {
|
|
191
|
+
const middlewares = extractNextMiddleware(index);
|
|
192
|
+
const matchers = middlewares.flatMap(mw => mw.matcher || []);
|
|
193
|
+
const fastify = extractFastifyAuthSignals(index, serverRoutes);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
nextMiddleware: middlewares,
|
|
197
|
+
nextMatcherPatterns: matchers,
|
|
198
|
+
fastify,
|
|
199
|
+
helpers: {
|
|
200
|
+
matcherCoversPath: "runtime-only"
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
buildAuthTruthV2,
|
|
207
|
+
extractNextMiddleware,
|
|
208
|
+
extractFastifyAuthSignals,
|
|
209
|
+
matcherCoversPath,
|
|
210
|
+
guessAuthSignalsFromCode,
|
|
211
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// bin/runners/lib/engine/billing-extractor.js
|
|
2
|
+
// Optimized billing/Stripe extraction using RepoIndex
|
|
3
|
+
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const crypto = require("crypto");
|
|
6
|
+
|
|
7
|
+
function sha256(text) {
|
|
8
|
+
return "sha256:" + crypto.createHash("sha256").update(text).digest("hex");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function evidenceFromContent(content, fileRel, lineNo, reason) {
|
|
12
|
+
if (!content) return null;
|
|
13
|
+
const lines = content.split(/\r?\n/);
|
|
14
|
+
const idx = Math.max(0, Math.min(lines.length - 1, lineNo - 1));
|
|
15
|
+
const snippet = lines[idx] || "";
|
|
16
|
+
return {
|
|
17
|
+
id: `ev_${crypto.randomBytes(4).toString("hex")}`,
|
|
18
|
+
file: fileRel,
|
|
19
|
+
lines: `${lineNo}-${lineNo}`,
|
|
20
|
+
snippetHash: sha256(snippet),
|
|
21
|
+
reason
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findLineMatches(code, regex) {
|
|
26
|
+
const out = [];
|
|
27
|
+
const lines = code.split(/\r?\n/);
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
if (regex.test(lines[i])) out.push(i + 1);
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function classifyStripeSignals(code) {
|
|
35
|
+
return {
|
|
36
|
+
usesStripeSdk: /\bstripe\b/i.test(code) && /from\s+['"]stripe['"]|require\(['"]stripe['"]\)/.test(code),
|
|
37
|
+
webhookConstructEvent: /\bconstructEvent(Async)?\b/.test(code) || /\bstripe\.webhooks\.constructEvent\b/.test(code),
|
|
38
|
+
readsStripeSignatureHeader: /stripe-signature/i.test(code) || /\bStripe-Signature\b/.test(code),
|
|
39
|
+
rawBodySignal:
|
|
40
|
+
/\bbodyParser\s*:\s*false\b/.test(code) ||
|
|
41
|
+
/\breq\.(text|arrayBuffer)\(\)/.test(code) ||
|
|
42
|
+
/\brawBody\b/.test(code) || /\brequest\.raw\b/.test(code) || /\bcontentTypeParser\b/i.test(code),
|
|
43
|
+
idempotencySignal:
|
|
44
|
+
/\bevent\.id\b/.test(code) && /\b(prisma|db|redis|cache|processed|dedupe|idempotent)\b/i.test(code) ||
|
|
45
|
+
/\bidempotenc(y|e)\b/i.test(code)
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Build billing truth using RepoIndex (optimized)
|
|
51
|
+
* @param {import('./repo-index').RepoIndex} index
|
|
52
|
+
* @param {Object} stats
|
|
53
|
+
* @returns {Object}
|
|
54
|
+
*/
|
|
55
|
+
function buildBillingTruthV2(index, stats) {
|
|
56
|
+
// Use token prefilter - only scan files that mention stripe
|
|
57
|
+
const candidateAbs = index.getByAnyToken(["stripe", "Stripe"]);
|
|
58
|
+
|
|
59
|
+
// Filter to JS/TS files
|
|
60
|
+
const jsExtensions = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
61
|
+
const files = candidateAbs.filter(abs => {
|
|
62
|
+
const ext = path.extname(abs).toLowerCase();
|
|
63
|
+
return jsExtensions.has(ext);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const webhookCandidates = [];
|
|
67
|
+
const stripeFiles = [];
|
|
68
|
+
|
|
69
|
+
for (const fileAbs of files) {
|
|
70
|
+
const fileRel = index.relPath(fileAbs);
|
|
71
|
+
const content = index.getContent(fileAbs);
|
|
72
|
+
if (!content) continue;
|
|
73
|
+
|
|
74
|
+
const signals = classifyStripeSignals(content);
|
|
75
|
+
|
|
76
|
+
if (signals.usesStripeSdk) stripeFiles.push(fileRel);
|
|
77
|
+
|
|
78
|
+
if (signals.webhookConstructEvent || signals.readsStripeSignatureHeader) {
|
|
79
|
+
const ev = [];
|
|
80
|
+
const lines1 = findLineMatches(content, /\bconstructEvent(Async)?\b|stripe\.webhooks\.constructEvent/);
|
|
81
|
+
const lines2 = findLineMatches(content, /stripe-signature|Stripe-Signature/i);
|
|
82
|
+
const lines3 = findLineMatches(content, /bodyParser\s*:\s*false|req\.(text|arrayBuffer)\(|rawBody|contentTypeParser/i);
|
|
83
|
+
const lines4 = findLineMatches(content, /event\.id|idempotenc(y|e)|dedupe|processed/i);
|
|
84
|
+
|
|
85
|
+
for (const ln of lines1.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Stripe webhook signature constructEvent signal"));
|
|
86
|
+
for (const ln of lines2.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Stripe-Signature header usage signal"));
|
|
87
|
+
for (const ln of lines3.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Raw body handling signal (required for Stripe verification)"));
|
|
88
|
+
for (const ln of lines4.slice(0, 3)) ev.push(evidenceFromContent(content, fileRel, ln, "Idempotency/dedupe signal (event replay protection)"));
|
|
89
|
+
|
|
90
|
+
webhookCandidates.push({
|
|
91
|
+
file: fileRel,
|
|
92
|
+
signals,
|
|
93
|
+
evidence: ev
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const hasStripe = stripeFiles.length > 0;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
hasStripe,
|
|
102
|
+
stripeFiles: stripeFiles.slice(0, 200),
|
|
103
|
+
webhookCandidates,
|
|
104
|
+
summary: {
|
|
105
|
+
webhookHandlersFound: webhookCandidates.length,
|
|
106
|
+
verifiedWebhookHandlers: webhookCandidates.filter(w => w.signals.webhookConstructEvent && w.signals.rawBodySignal).length,
|
|
107
|
+
idempotentWebhookHandlers: webhookCandidates.filter(w => w.signals.idempotencySignal).length
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = { buildBillingTruthV2 };
|