@vibecheckai/cli 3.1.6 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -32
- package/bin/registry.js +208 -343
- package/bin/runners/context/generators/mcp.js +18 -0
- package/bin/runners/context/index.js +72 -4
- package/bin/runners/context/proof-context.js +293 -1
- package/bin/runners/context/security-scanner.js +311 -73
- package/bin/runners/lib/analyzers.js +607 -20
- package/bin/runners/lib/detectors-v2.js +172 -15
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/html-proof-report.js +913 -0
- package/bin/runners/lib/missions/plan.js +231 -41
- package/bin/runners/lib/missions/templates.js +125 -0
- package/bin/runners/lib/scan-output.js +492 -253
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/runCheckpoint.js +44 -3
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runContext.js +2 -3
- package/bin/runners/runDoctor.js +11 -4
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +37 -20
- package/bin/runners/runPolish.d.ts +4 -0
- package/bin/runners/runPolish.js +608 -29
- package/bin/runners/runProve.js +210 -25
- package/bin/runners/runReality.js +861 -107
- package/bin/runners/runScan.js +238 -4
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runWatch.js +25 -5
- package/bin/vibecheck.js +35 -47
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +152 -15
- package/mcp-server/package.json +1 -1
- package/mcp-server/proof-tools.js +571 -0
- package/mcp-server/tier-auth.js +22 -19
- package/mcp-server/tools-v3.js +744 -0
- package/mcp-server/truth-firewall-tools.js +190 -4
- package/package.json +3 -1
- package/bin/runners/runBadge.js +0 -916
- package/bin/runners/runContracts.js +0 -105
- package/bin/runners/runCtx.js +0 -680
- package/bin/runners/runCtxDiff.js +0 -301
- package/bin/runners/runCtxGuard.js +0 -176
- package/bin/runners/runCtxSync.js +0 -116
- package/bin/runners/runExport.js +0 -93
- package/bin/runners/runGraph.js +0 -454
- package/bin/runners/runInstall.js +0 -273
- package/bin/runners/runLabs.js +0 -341
- package/bin/runners/runLaunch.js +0 -181
- package/bin/runners/runPR.js +0 -255
- package/bin/runners/runPermissions.js +0 -310
- package/bin/runners/runPreflight.js +0 -580
- package/bin/runners/runReplay.js +0 -499
- package/bin/runners/runSecurity.js +0 -92
- package/bin/runners/runShare.js +0 -212
- package/bin/runners/runStatus.js +0 -102
- package/bin/runners/runVerify.js +0 -272
|
@@ -1,580 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preflight Validation Command
|
|
3
|
-
*
|
|
4
|
-
* Validates environment, migrations, storage, and security
|
|
5
|
-
* before deployment. Fails fast on critical issues.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
"use strict";
|
|
9
|
-
|
|
10
|
-
const { execSync } = require("child_process");
|
|
11
|
-
const fs = require("fs");
|
|
12
|
-
const path = require("path");
|
|
13
|
-
const https = require("https");
|
|
14
|
-
const http = require("http");
|
|
15
|
-
|
|
16
|
-
const c = {
|
|
17
|
-
reset: "\x1b[0m",
|
|
18
|
-
bold: "\x1b[1m",
|
|
19
|
-
dim: "\x1b[2m",
|
|
20
|
-
red: "\x1b[31m",
|
|
21
|
-
green: "\x1b[32m",
|
|
22
|
-
yellow: "\x1b[33m",
|
|
23
|
-
blue: "\x1b[34m",
|
|
24
|
-
cyan: "\x1b[36m",
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const checks = {
|
|
28
|
-
env: validateEnvironment,
|
|
29
|
-
secrets: validateSecrets,
|
|
30
|
-
database: validateDatabase,
|
|
31
|
-
storage: validateStorage,
|
|
32
|
-
ssl: validateSSL,
|
|
33
|
-
webhooks: validateWebhooks,
|
|
34
|
-
tiers: validateTierEnforcement,
|
|
35
|
-
services: validateExternalServices,
|
|
36
|
-
security: validateSecurityConfig
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
async function runPreflight(args) {
|
|
40
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
41
|
-
console.log(`
|
|
42
|
-
${c.bold}vibecheck preflight${c.reset} - Deployment validation checks
|
|
43
|
-
|
|
44
|
-
${c.bold}Usage:${c.reset}
|
|
45
|
-
vibecheck preflight [options]
|
|
46
|
-
|
|
47
|
-
${c.bold}Options:${c.reset}
|
|
48
|
-
${c.cyan}--all${c.reset} Run all checks
|
|
49
|
-
${c.cyan}--env${c.reset} Validate environment variables
|
|
50
|
-
${c.cyan}--secrets${c.reset} Validate secrets configuration
|
|
51
|
-
${c.cyan}--database${c.reset} Validate database connectivity
|
|
52
|
-
${c.cyan}--storage${c.reset} Validate storage configuration
|
|
53
|
-
${c.cyan}--ssl${c.reset} Validate SSL certificates
|
|
54
|
-
${c.cyan}--webhooks${c.reset} Validate webhook endpoints
|
|
55
|
-
${c.cyan}--tiers${c.reset} Validate tier enforcement
|
|
56
|
-
${c.cyan}--services${c.reset} Validate external services
|
|
57
|
-
${c.cyan}--security${c.reset} Validate security configuration
|
|
58
|
-
${c.cyan}--help, -h${c.reset} Show this help
|
|
59
|
-
|
|
60
|
-
${c.bold}Examples:${c.reset}
|
|
61
|
-
vibecheck preflight --all
|
|
62
|
-
vibecheck preflight --env --secrets --database
|
|
63
|
-
`);
|
|
64
|
-
return 0;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
console.log(`${c.cyan}${c.bold}🚀 Vibecheck Preflight Check${c.reset}\n`);
|
|
68
|
-
|
|
69
|
-
const env = process.env.NODE_ENV || "development";
|
|
70
|
-
console.log(`${c.dim}Environment: ${env.toUpperCase()}${c.reset}\n`);
|
|
71
|
-
|
|
72
|
-
// Determine which checks to run
|
|
73
|
-
const checksToRun = args.includes("--all")
|
|
74
|
-
? Object.keys(checks)
|
|
75
|
-
: args.filter(arg => arg.startsWith("--")).map(arg => arg.substring(2)).filter(arg => checks[arg]);
|
|
76
|
-
|
|
77
|
-
if (checksToRun.length === 0) {
|
|
78
|
-
checksToRun.push(...Object.keys(checks));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
console.log(`${c.blue}Running checks:${c.reset} ${checksToRun.join(", ")}\n`);
|
|
82
|
-
|
|
83
|
-
const results = {
|
|
84
|
-
passed: [],
|
|
85
|
-
failed: [],
|
|
86
|
-
warnings: []
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// Run each check
|
|
90
|
-
for (const checkName of checksToRun) {
|
|
91
|
-
console.log(`${c.yellow}⏳ Running ${checkName} check...${c.reset}`);
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const result = await checks[checkName]();
|
|
95
|
-
|
|
96
|
-
if (result.passed) {
|
|
97
|
-
console.log(`${c.green}✅ ${checkName}: PASSED${c.reset}`);
|
|
98
|
-
results.passed.push(checkName);
|
|
99
|
-
if (result.message) {
|
|
100
|
-
console.log(` ${c.dim}${result.message}${c.reset}`);
|
|
101
|
-
}
|
|
102
|
-
} else {
|
|
103
|
-
console.log(`${c.red}❌ ${checkName}: FAILED${c.reset}`);
|
|
104
|
-
console.log(` ${result.error}`);
|
|
105
|
-
results.failed.push({ name: checkName, error: result.error });
|
|
106
|
-
}
|
|
107
|
-
} catch (err) {
|
|
108
|
-
console.log(`${c.red}❌ ${checkName}: ERROR${c.reset}`);
|
|
109
|
-
console.log(` ${err.message}`);
|
|
110
|
-
results.failed.push({ name: checkName, error: err.message });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
console.log();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Summary
|
|
117
|
-
console.log(`${c.bold}${'═'.repeat(50)}${c.reset}`);
|
|
118
|
-
console.log(`${c.bold}PREFLIGHT CHECK SUMMARY${c.reset}`);
|
|
119
|
-
console.log(`${c.bold}${'═'.repeat(50)}${c.reset}`);
|
|
120
|
-
console.log(`${c.green}✅ Passed: ${results.passed.length}${c.reset}`);
|
|
121
|
-
console.log(`${c.red}❌ Failed: ${results.failed.length}${c.reset}`);
|
|
122
|
-
console.log(`${c.yellow}⚠️ Warnings: ${results.warnings.length}${c.reset}\n`);
|
|
123
|
-
|
|
124
|
-
// Fail if any critical checks failed
|
|
125
|
-
const criticalFailures = results.failed.filter(f =>
|
|
126
|
-
["env", "secrets", "database", "webhooks", "tiers"].includes(f.name)
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
if (criticalFailures.length > 0) {
|
|
130
|
-
console.log(`${c.red}${c.bold}💥 CRITICAL FAILURES DETECTED${c.reset}`);
|
|
131
|
-
console.log("Fix the following issues before deploying:\n");
|
|
132
|
-
|
|
133
|
-
for (const failure of criticalFailures) {
|
|
134
|
-
console.log(`${c.red}• ${failure.name}: ${failure.error}${c.reset}`);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
console.log(`\n${c.yellow}Run 'vibecheck preflight --fix' to attempt auto-fixes${c.reset}`);
|
|
138
|
-
return 2; // BLOCK
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (results.failed.length > 0) {
|
|
142
|
-
console.log(`${c.yellow}⚠️ Some non-critical checks failed${c.reset}`);
|
|
143
|
-
console.log("Review and fix if necessary\n");
|
|
144
|
-
return 1; // WARN
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
console.log(`${c.green}🎉 All checks passed! Ready to deploy.${c.reset}`);
|
|
148
|
-
return 0; // SHIP
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async function validateEnvironment() {
|
|
152
|
-
const { validateEnvironment } = require("../../shared/env-validator");
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
const { validated } = validateEnvironment();
|
|
156
|
-
|
|
157
|
-
// Check for production-specific requirements
|
|
158
|
-
if (process.env.NODE_ENV === "production") {
|
|
159
|
-
const prodRequired = [
|
|
160
|
-
"VIBECHECK_ENFORCE_HTTPS",
|
|
161
|
-
"VIBECHECK_CORS_ORIGIN",
|
|
162
|
-
"SENTRY_DSN",
|
|
163
|
-
"VIBECHECK_RATE_LIMIT_STRICT"
|
|
164
|
-
];
|
|
165
|
-
|
|
166
|
-
for (const key of prodRequired) {
|
|
167
|
-
if (!validated[key]) {
|
|
168
|
-
return { passed: false, error: `Missing production variable: ${key}` };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Check that dangerous dev flags are NOT set
|
|
173
|
-
const forbidden = [
|
|
174
|
-
"VIBECHECK_SKIP_AUTH",
|
|
175
|
-
"VIBECHECK_FAKE_BILLING",
|
|
176
|
-
"VIBECHECK_MOCK_AI"
|
|
177
|
-
];
|
|
178
|
-
|
|
179
|
-
for (const key of forbidden) {
|
|
180
|
-
if (validated[key] === "true") {
|
|
181
|
-
return { passed: false, error: `Dangerous flag set in production: ${key}` };
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return { passed: true, message: "All environment variables valid" };
|
|
187
|
-
} catch (err) {
|
|
188
|
-
return { passed: false, error: err.message };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
async function validateSecrets() {
|
|
193
|
-
const secrets = [
|
|
194
|
-
"VIBECHECK_JWT_SECRET",
|
|
195
|
-
"VIBECHECK_REFRESH_SECRET",
|
|
196
|
-
"VIBECHECK_API_SECRET"
|
|
197
|
-
];
|
|
198
|
-
|
|
199
|
-
for (const secret of secrets) {
|
|
200
|
-
const value = process.env[secret];
|
|
201
|
-
|
|
202
|
-
if (!value) {
|
|
203
|
-
return { passed: false, error: `Missing secret: ${secret}` };
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (value.length < 32) {
|
|
207
|
-
return { passed: false, error: `${secret} too short (min 32 chars)` };
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Check for weak patterns
|
|
211
|
-
if (value === "secret" || value === "password" || value.startsWith("test")) {
|
|
212
|
-
return { passed: false, error: `${secret} appears to be weak` };
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return { passed: true, message: "All secrets are strong" };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
async function validateDatabase() {
|
|
220
|
-
const databaseUrl = process.env.DATABASE_URL;
|
|
221
|
-
|
|
222
|
-
if (!databaseUrl) {
|
|
223
|
-
return { passed: false, error: "DATABASE_URL not set" };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
try {
|
|
227
|
-
// Parse database URL to check SSL
|
|
228
|
-
const url = new URL(databaseUrl);
|
|
229
|
-
|
|
230
|
-
if (process.env.NODE_ENV === "production" && url.searchParams.get("sslmode") !== "require") {
|
|
231
|
-
return { passed: false, error: "Database must use SSL in production" };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Test connectivity
|
|
235
|
-
const { Client } = require("pg");
|
|
236
|
-
const client = new Client({ connectionString: databaseUrl });
|
|
237
|
-
|
|
238
|
-
await client.connect();
|
|
239
|
-
|
|
240
|
-
// Check migrations
|
|
241
|
-
const result = await client.query(`
|
|
242
|
-
SELECT COUNT(*) as count FROM schema_migrations
|
|
243
|
-
`);
|
|
244
|
-
|
|
245
|
-
await client.end();
|
|
246
|
-
|
|
247
|
-
const migrationCount = parseInt(result.rows[0].count);
|
|
248
|
-
if (migrationCount === 0) {
|
|
249
|
-
return { passed: false, error: "No migrations found - run 'npm run db:migrate'" };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return { passed: true, message: `${migrationCount} migrations applied` };
|
|
253
|
-
} catch (err) {
|
|
254
|
-
return { passed: false, error: `Database connection failed: ${err.message}` };
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function validateStorage() {
|
|
259
|
-
const storageType = process.env.STORAGE_TYPE || "local";
|
|
260
|
-
|
|
261
|
-
if (storageType === "s3") {
|
|
262
|
-
const required = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_S3_BUCKET"];
|
|
263
|
-
|
|
264
|
-
for (const key of required) {
|
|
265
|
-
if (!process.env[key]) {
|
|
266
|
-
return { passed: false, error: `Missing S3 config: ${key}` };
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Test S3 connectivity
|
|
271
|
-
try {
|
|
272
|
-
const AWS = require("aws-sdk");
|
|
273
|
-
const s3 = new AWS.S3({
|
|
274
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
275
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
276
|
-
region: process.env.AWS_REGION || "us-east-1"
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
await s3.headBucket({ Bucket: process.env.AWS_S3_BUCKET }).promise();
|
|
280
|
-
|
|
281
|
-
return { passed: true, message: "S3 bucket accessible" };
|
|
282
|
-
} catch (err) {
|
|
283
|
-
return { passed: false, error: `S3 access failed: ${err.message}` };
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// For local storage, check directory exists
|
|
288
|
-
if (storageType === "local") {
|
|
289
|
-
const uploadDir = process.env.UPLOAD_DIR || "./uploads";
|
|
290
|
-
|
|
291
|
-
if (!fs.existsSync(uploadDir)) {
|
|
292
|
-
try {
|
|
293
|
-
fs.mkdirSync(uploadDir, { recursive: true });
|
|
294
|
-
} catch (err) {
|
|
295
|
-
return { passed: false, error: `Cannot create upload directory: ${err.message}` };
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
return { passed: true, message: "Local storage ready" };
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
return { passed: true, message: `${storageType} storage configured` };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function validateSSL() {
|
|
306
|
-
if (process.env.NODE_ENV !== "production") {
|
|
307
|
-
return { passed: true, message: "SSL not required in development" };
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const url = process.env.VIBECHECK_PUBLIC_URL || "https://api.vibecheck.ai";
|
|
311
|
-
|
|
312
|
-
try {
|
|
313
|
-
return new Promise((resolve) => {
|
|
314
|
-
const req = https.get(url, (res) => {
|
|
315
|
-
const cert = res.socket.getPeerCertificate();
|
|
316
|
-
|
|
317
|
-
if (!cert) {
|
|
318
|
-
resolve({ passed: false, error: "No SSL certificate found" });
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Check certificate expiry
|
|
323
|
-
const now = new Date();
|
|
324
|
-
const expiry = new Date(cert.valid_to);
|
|
325
|
-
|
|
326
|
-
if (expiry < now) {
|
|
327
|
-
resolve({ passed: false, error: "SSL certificate expired" });
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Check expiry within 30 days
|
|
332
|
-
const thirtyDays = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000);
|
|
333
|
-
if (expiry < thirtyDays) {
|
|
334
|
-
resolve({
|
|
335
|
-
passed: false,
|
|
336
|
-
error: `SSL certificate expires soon: ${cert.valid_to}`
|
|
337
|
-
});
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
resolve({
|
|
342
|
-
passed: true,
|
|
343
|
-
message: `SSL valid until ${cert.valid_to}`
|
|
344
|
-
});
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
req.on("error", (err) => {
|
|
348
|
-
resolve({ passed: false, error: `SSL check failed: ${err.message}` });
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
req.setTimeout(5000, () => {
|
|
352
|
-
req.destroy();
|
|
353
|
-
resolve({ passed: false, error: "SSL check timeout" });
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
} catch (err) {
|
|
357
|
-
return { passed: false, error: err.message };
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
async function validateWebhooks() {
|
|
362
|
-
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
363
|
-
|
|
364
|
-
if (!webhookSecret) {
|
|
365
|
-
return { passed: false, error: "STRIPE_WEBHOOK_SECRET not set" };
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (!webhookSecret.startsWith("whsec_")) {
|
|
369
|
-
return { passed: false, error: "Invalid webhook secret format" };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Test webhook endpoint
|
|
373
|
-
const testPayload = JSON.stringify({
|
|
374
|
-
id: `evt_test_${Date.now()}`,
|
|
375
|
-
type: "test",
|
|
376
|
-
data: { object: { test: true } }
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const timestamp = Math.floor(Date.now() / 1000);
|
|
380
|
-
const signedPayload = `${timestamp}.${testPayload}`;
|
|
381
|
-
|
|
382
|
-
const crypto = require("crypto");
|
|
383
|
-
const signature = crypto
|
|
384
|
-
.createHmac("sha256", webhookSecret)
|
|
385
|
-
.update(signedPayload)
|
|
386
|
-
.digest("hex");
|
|
387
|
-
|
|
388
|
-
const stripeSignature = `t=${timestamp},v1=${signature}`;
|
|
389
|
-
|
|
390
|
-
try {
|
|
391
|
-
const webhookUrl = process.env.VIBECHECK_WEBHOOK_URL || "http://localhost:3000/webhooks/stripe";
|
|
392
|
-
|
|
393
|
-
return new Promise((resolve) => {
|
|
394
|
-
const postData = testPayload;
|
|
395
|
-
|
|
396
|
-
const req = http.request(webhookUrl, {
|
|
397
|
-
method: "POST",
|
|
398
|
-
headers: {
|
|
399
|
-
"Content-Type": "application/json",
|
|
400
|
-
"Content-Length": Buffer.byteLength(postData),
|
|
401
|
-
"Stripe-Signature": stripeSignature
|
|
402
|
-
}
|
|
403
|
-
}, (res) => {
|
|
404
|
-
let data = "";
|
|
405
|
-
res.on("data", chunk => data += chunk);
|
|
406
|
-
res.on("end", () => {
|
|
407
|
-
if (res.statusCode === 200) {
|
|
408
|
-
resolve({ passed: true, message: "Webhook signature verification working" });
|
|
409
|
-
} else {
|
|
410
|
-
resolve({ passed: false, error: `Webhook returned ${res.statusCode}` });
|
|
411
|
-
}
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
req.on("error", (err) => {
|
|
416
|
-
resolve({ passed: false, error: `Webhook test failed: ${err.message}` });
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
req.setTimeout(5000, () => {
|
|
420
|
-
req.destroy();
|
|
421
|
-
resolve({ passed: false, error: "Webhook test timeout" });
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
req.write(postData);
|
|
425
|
-
req.end();
|
|
426
|
-
});
|
|
427
|
-
} catch (err) {
|
|
428
|
-
return { passed: false, error: err.message };
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
async function validateTierEnforcement() {
|
|
433
|
-
// Check CLI tier enforcement
|
|
434
|
-
try {
|
|
435
|
-
const { enforce } = require("../lib/entitlements-v2");
|
|
436
|
-
|
|
437
|
-
// Test free tier
|
|
438
|
-
const freeResult = await enforce("scan", { silent: true });
|
|
439
|
-
if (!freeResult.allowed) {
|
|
440
|
-
return { passed: false, error: "CLI incorrectly blocks free tier features" };
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
// Test pro tier enforcement
|
|
444
|
-
const proResult = await enforce("prove", { silent: true });
|
|
445
|
-
if (proResult.allowed && process.env.NODE_ENV === "production") {
|
|
446
|
-
return { passed: false, error: "CLI incorrectly allows pro features without auth" };
|
|
447
|
-
}
|
|
448
|
-
} catch (err) {
|
|
449
|
-
return { passed: false, error: `CLI tier check failed: ${err.message}` };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Check API tier enforcement if server is running
|
|
453
|
-
const apiUrl = process.env.VIBECHECK_API_URL || "http://localhost:3000";
|
|
454
|
-
|
|
455
|
-
try {
|
|
456
|
-
// Test without auth
|
|
457
|
-
const response = await fetch(`${apiUrl}/api/v1/ship`, {
|
|
458
|
-
method: "POST",
|
|
459
|
-
headers: { "Content-Type": "application/json" }
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
if (response.status !== 401 && response.status !== 403) {
|
|
463
|
-
return { passed: false, error: "API not enforcing authentication" };
|
|
464
|
-
}
|
|
465
|
-
} catch (err) {
|
|
466
|
-
// Server might not be running - that's OK for preflight
|
|
467
|
-
return { passed: true, message: "API not running - skip check" };
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return { passed: true, message: "Tier enforcement consistent" };
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
async function validateExternalServices() {
|
|
474
|
-
const services = [
|
|
475
|
-
{ name: "License API", url: process.env.VIBECHECK_LICENSE_API },
|
|
476
|
-
{ name: "AI Endpoint", url: process.env.VIBECHECK_AI_ENDPOINT }
|
|
477
|
-
];
|
|
478
|
-
|
|
479
|
-
for (const service of services) {
|
|
480
|
-
if (!service.url) {
|
|
481
|
-
return { passed: false, error: `${service.name} URL not configured` };
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
try {
|
|
485
|
-
const response = await fetch(`${service.url}/health`, {
|
|
486
|
-
method: "GET",
|
|
487
|
-
timeout: 5000
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
if (!response.ok) {
|
|
491
|
-
return { passed: false, error: `${service.name} unhealthy: ${response.status}` };
|
|
492
|
-
}
|
|
493
|
-
} catch (err) {
|
|
494
|
-
return { passed: false, error: `${service.name} unreachable: ${err.message}` };
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
return { passed: true, message: "All external services healthy" };
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
async function validateSecurityConfig() {
|
|
502
|
-
const issues = [];
|
|
503
|
-
|
|
504
|
-
// Check for secure headers in production
|
|
505
|
-
if (process.env.NODE_ENV === "production") {
|
|
506
|
-
if (!process.env.VIBECHECK_ENFORCE_HTTPS) {
|
|
507
|
-
issues.push("HTTPS enforcement not enabled");
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (!process.env.VIBECHECK_RATE_LIMIT_STRICT) {
|
|
511
|
-
issues.push("Strict rate limiting not enabled");
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
const corsOrigin = process.env.VIBECHECK_CORS_ORIGIN;
|
|
515
|
-
if (!corsOrigin || corsOrigin.includes("*") || corsOrigin.includes("localhost")) {
|
|
516
|
-
issues.push("CORS origin not properly restricted");
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Check for required security headers
|
|
521
|
-
const requiredHeaders = [
|
|
522
|
-
"helmet",
|
|
523
|
-
"cors",
|
|
524
|
-
"rateLimit"
|
|
525
|
-
];
|
|
526
|
-
|
|
527
|
-
// Check if security middleware is configured
|
|
528
|
-
try {
|
|
529
|
-
const apiFile = fs.readFileSync("./apps/api/src/app.js", "utf8");
|
|
530
|
-
|
|
531
|
-
for (const header of requiredHeaders) {
|
|
532
|
-
if (!apiFile.includes(header)) {
|
|
533
|
-
issues.push(`Security middleware '${header}' not found`);
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
} catch (err) {
|
|
537
|
-
// API file might not exist in this context
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
if (issues.length > 0) {
|
|
541
|
-
return { passed: false, error: issues.join("; ") };
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return { passed: true, message: "Security configuration valid" };
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function printHelp() {
|
|
548
|
-
console.log(`
|
|
549
|
-
${c.cyan}${c.bold}vibecheck preflight${c.reset} - Deployment validation
|
|
550
|
-
|
|
551
|
-
${c.bold}USAGE${c.reset}
|
|
552
|
-
vibecheck preflight [options]
|
|
553
|
-
|
|
554
|
-
${c.bold}OPTIONS${c.reset}
|
|
555
|
-
--all Run all checks
|
|
556
|
-
--env Validate environment variables
|
|
557
|
-
--secrets Validate secrets strength
|
|
558
|
-
--database Validate database connectivity
|
|
559
|
-
--storage Validate storage configuration
|
|
560
|
-
--ssl Validate SSL certificates
|
|
561
|
-
--webhooks Validate webhook signatures
|
|
562
|
-
--tiers Validate tier enforcement
|
|
563
|
-
--services Validate external services
|
|
564
|
-
--security Validate security configuration
|
|
565
|
-
--fix Attempt to fix issues (where possible)
|
|
566
|
-
--help, -h Show this help
|
|
567
|
-
|
|
568
|
-
${c.bold}EXAMPLES${c.reset}
|
|
569
|
-
vibecheck preflight --all # Run all checks
|
|
570
|
-
vibecheck preflight --env --secrets # Run specific checks
|
|
571
|
-
vibecheck preflight # Run all checks (default)
|
|
572
|
-
|
|
573
|
-
${c.bold}EXIT CODES${c.reset}
|
|
574
|
-
0 = All checks passed
|
|
575
|
-
1 = Non-critical warnings
|
|
576
|
-
2 = Critical failures (block deployment)
|
|
577
|
-
`);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
module.exports = { runPreflight, printHelp };
|