@vibecheckai/cli 3.0.2 → 3.0.3
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/package.json +9 -1
- package/bin/cli-hygiene.js +0 -241
- package/bin/guardrail.js +0 -834
- package/bin/runners/cli-utils.js +0 -1070
- package/bin/runners/context/ai-task-decomposer.js +0 -337
- package/bin/runners/context/analyzer.js +0 -462
- package/bin/runners/context/api-contracts.js +0 -427
- package/bin/runners/context/context-diff.js +0 -342
- package/bin/runners/context/context-pruner.js +0 -291
- package/bin/runners/context/dependency-graph.js +0 -414
- package/bin/runners/context/generators/claude.js +0 -107
- package/bin/runners/context/generators/codex.js +0 -108
- package/bin/runners/context/generators/copilot.js +0 -119
- package/bin/runners/context/generators/cursor.js +0 -514
- package/bin/runners/context/generators/mcp.js +0 -151
- package/bin/runners/context/generators/windsurf.js +0 -180
- package/bin/runners/context/git-context.js +0 -302
- package/bin/runners/context/index.js +0 -1042
- package/bin/runners/context/insights.js +0 -173
- package/bin/runners/context/mcp-server/generate-rules.js +0 -337
- package/bin/runners/context/mcp-server/index.js +0 -1176
- package/bin/runners/context/mcp-server/package.json +0 -24
- package/bin/runners/context/memory.js +0 -200
- package/bin/runners/context/monorepo.js +0 -215
- package/bin/runners/context/multi-repo-federation.js +0 -404
- package/bin/runners/context/patterns.js +0 -253
- package/bin/runners/context/proof-context.js +0 -972
- package/bin/runners/context/security-scanner.js +0 -303
- package/bin/runners/context/semantic-search.js +0 -350
- package/bin/runners/context/shared.js +0 -264
- package/bin/runners/context/team-conventions.js +0 -310
- package/bin/runners/lib/ai-bridge.js +0 -416
- package/bin/runners/lib/analysis-core.js +0 -271
- package/bin/runners/lib/analyzers.js +0 -541
- package/bin/runners/lib/audit-bridge.js +0 -391
- package/bin/runners/lib/auth-truth.js +0 -193
- package/bin/runners/lib/auth.js +0 -215
- package/bin/runners/lib/backup.js +0 -62
- package/bin/runners/lib/billing.js +0 -107
- package/bin/runners/lib/claims.js +0 -118
- package/bin/runners/lib/cli-ui.js +0 -540
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +0 -165
- package/bin/runners/lib/contracts/auth-contract.js +0 -194
- package/bin/runners/lib/contracts/env-contract.js +0 -178
- package/bin/runners/lib/contracts/external-contract.js +0 -198
- package/bin/runners/lib/contracts/guard.js +0 -168
- package/bin/runners/lib/contracts/index.js +0 -89
- package/bin/runners/lib/contracts/plan-validator.js +0 -311
- package/bin/runners/lib/contracts/route-contract.js +0 -192
- package/bin/runners/lib/detect.js +0 -89
- package/bin/runners/lib/doctor/autofix.js +0 -254
- package/bin/runners/lib/doctor/index.js +0 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
- package/bin/runners/lib/doctor/modules/index.js +0 -46
- package/bin/runners/lib/doctor/modules/network.js +0 -250
- package/bin/runners/lib/doctor/modules/project.js +0 -312
- package/bin/runners/lib/doctor/modules/runtime.js +0 -224
- package/bin/runners/lib/doctor/modules/security.js +0 -348
- package/bin/runners/lib/doctor/modules/system.js +0 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
- package/bin/runners/lib/doctor/reporter.js +0 -262
- package/bin/runners/lib/doctor/service.js +0 -262
- package/bin/runners/lib/doctor/types.js +0 -113
- package/bin/runners/lib/doctor/ui.js +0 -263
- package/bin/runners/lib/doctor-enhanced.js +0 -233
- package/bin/runners/lib/doctor-v2.js +0 -608
- package/bin/runners/lib/enforcement.js +0 -72
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Auth Contract Builder
|
|
3
|
-
* Builds auth.json contract from truthpack
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Build auth contract from truthpack
|
|
10
|
-
*/
|
|
11
|
-
function buildAuthContract(truthpack) {
|
|
12
|
-
const contract = {
|
|
13
|
-
version: "1.0.0",
|
|
14
|
-
generatedAt: new Date().toISOString(),
|
|
15
|
-
protectedPatterns: [],
|
|
16
|
-
publicPatterns: [],
|
|
17
|
-
roles: [],
|
|
18
|
-
evidence: []
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
// Extract protected patterns from Next middleware
|
|
22
|
-
const nextMiddleware = truthpack?.auth?.nextMiddleware || [];
|
|
23
|
-
const matcherPatterns = truthpack?.auth?.nextMatcherPatterns || [];
|
|
24
|
-
|
|
25
|
-
contract.protectedPatterns = [...new Set(matcherPatterns)];
|
|
26
|
-
|
|
27
|
-
// Add evidence from middleware files
|
|
28
|
-
for (const mw of nextMiddleware) {
|
|
29
|
-
contract.evidence.push({
|
|
30
|
-
file: mw.file,
|
|
31
|
-
type: "next_middleware",
|
|
32
|
-
signals: mw.signalTypes || []
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Extract Fastify auth info
|
|
37
|
-
const fastify = truthpack?.auth?.fastify || {};
|
|
38
|
-
if (fastify.hooks?.length) {
|
|
39
|
-
for (const hook of fastify.hooks) {
|
|
40
|
-
contract.evidence.push({
|
|
41
|
-
file: hook.file,
|
|
42
|
-
type: "fastify_hook",
|
|
43
|
-
hookType: hook.hookType,
|
|
44
|
-
line: hook.line
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Infer roles from truthpack
|
|
50
|
-
contract.roles = inferRoles(truthpack);
|
|
51
|
-
|
|
52
|
-
// Default public patterns
|
|
53
|
-
contract.publicPatterns = [
|
|
54
|
-
"/api/health",
|
|
55
|
-
"/api/status",
|
|
56
|
-
"/api/public/*",
|
|
57
|
-
"/_next/*",
|
|
58
|
-
"/favicon.ico"
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
return contract;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Infer roles from truthpack
|
|
66
|
-
*/
|
|
67
|
-
function inferRoles(truthpack) {
|
|
68
|
-
const roles = [];
|
|
69
|
-
const routes = truthpack?.routes?.server || [];
|
|
70
|
-
|
|
71
|
-
// Check for admin routes
|
|
72
|
-
const adminRoutes = routes.filter(r => r.path.includes("/admin"));
|
|
73
|
-
if (adminRoutes.length > 0) {
|
|
74
|
-
roles.push({
|
|
75
|
-
name: "admin",
|
|
76
|
-
routes: adminRoutes.map(r => r.path),
|
|
77
|
-
evidence: adminRoutes.flatMap(r => r.evidence || [])
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Check for user routes (default authenticated)
|
|
82
|
-
const userRoutes = routes.filter(r =>
|
|
83
|
-
!r.path.includes("/admin") &&
|
|
84
|
-
!r.path.includes("/public") &&
|
|
85
|
-
!r.path.includes("/health")
|
|
86
|
-
);
|
|
87
|
-
if (userRoutes.length > 0) {
|
|
88
|
-
roles.push({
|
|
89
|
-
name: "user",
|
|
90
|
-
routes: userRoutes.map(r => r.path),
|
|
91
|
-
evidence: []
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return roles;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Validate auth coverage
|
|
100
|
-
*/
|
|
101
|
-
function validateAuthContract(contract, routes, realityResults) {
|
|
102
|
-
const violations = [];
|
|
103
|
-
|
|
104
|
-
// Check that all non-public routes are protected
|
|
105
|
-
for (const route of routes) {
|
|
106
|
-
const isPublic = contract.publicPatterns.some(p => matchesPattern(route.path, p));
|
|
107
|
-
const isProtected = contract.protectedPatterns.some(p => matchesPattern(route.path, p));
|
|
108
|
-
|
|
109
|
-
if (!isPublic && !isProtected) {
|
|
110
|
-
// Check if route looks sensitive
|
|
111
|
-
if (looksLikeSensitiveRoute(route.path)) {
|
|
112
|
-
violations.push({
|
|
113
|
-
type: "unprotected_sensitive",
|
|
114
|
-
severity: "WARN",
|
|
115
|
-
route: route.path,
|
|
116
|
-
message: `Sensitive route ${route.path} not covered by auth patterns`,
|
|
117
|
-
evidence: route.evidence || []
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Check reality results for auth bypass
|
|
124
|
-
if (realityResults) {
|
|
125
|
-
for (const result of realityResults) {
|
|
126
|
-
if (result.type === "AuthCoverage" && result.severity === "BLOCK") {
|
|
127
|
-
violations.push({
|
|
128
|
-
type: "auth_bypass",
|
|
129
|
-
severity: "BLOCK",
|
|
130
|
-
route: result.page,
|
|
131
|
-
message: result.title,
|
|
132
|
-
evidence: []
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return violations;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function matchesPattern(path, pattern) {
|
|
142
|
-
const normPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
|
|
143
|
-
try {
|
|
144
|
-
const rx = new RegExp(`^${normPattern}`, "i");
|
|
145
|
-
return rx.test(path);
|
|
146
|
-
} catch {
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function looksLikeSensitiveRoute(path) {
|
|
152
|
-
const sensitivePatterns = [
|
|
153
|
-
/\/api\/users/i,
|
|
154
|
-
/\/api\/billing/i,
|
|
155
|
-
/\/api\/payment/i,
|
|
156
|
-
/\/api\/subscription/i,
|
|
157
|
-
/\/api\/settings/i,
|
|
158
|
-
/\/api\/profile/i,
|
|
159
|
-
/\/api\/account/i,
|
|
160
|
-
/\/api\/admin/i,
|
|
161
|
-
/\/api\/webhook/i,
|
|
162
|
-
];
|
|
163
|
-
|
|
164
|
-
return sensitivePatterns.some(p => p.test(path));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Diff two auth contracts
|
|
169
|
-
*/
|
|
170
|
-
function diffAuthContracts(before, after) {
|
|
171
|
-
const diff = {
|
|
172
|
-
protectedAdded: [],
|
|
173
|
-
protectedRemoved: [],
|
|
174
|
-
rolesChanged: []
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const beforeProtected = new Set(before.protectedPatterns);
|
|
178
|
-
const afterProtected = new Set(after.protectedPatterns);
|
|
179
|
-
|
|
180
|
-
for (const p of afterProtected) {
|
|
181
|
-
if (!beforeProtected.has(p)) diff.protectedAdded.push(p);
|
|
182
|
-
}
|
|
183
|
-
for (const p of beforeProtected) {
|
|
184
|
-
if (!afterProtected.has(p)) diff.protectedRemoved.push(p);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return diff;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
module.exports = {
|
|
191
|
-
buildAuthContract,
|
|
192
|
-
validateAuthContract,
|
|
193
|
-
diffAuthContracts
|
|
194
|
-
};
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Env Contract Builder
|
|
3
|
-
* Builds env.json contract from truthpack
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Build env contract from truthpack
|
|
10
|
-
*/
|
|
11
|
-
function buildEnvContract(truthpack) {
|
|
12
|
-
const contract = {
|
|
13
|
-
version: "1.0.0",
|
|
14
|
-
generatedAt: new Date().toISOString(),
|
|
15
|
-
vars: []
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const envVars = truthpack?.env?.vars || [];
|
|
19
|
-
const declared = new Set(truthpack?.env?.declared || []);
|
|
20
|
-
const declaredSources = truthpack?.env?.declaredSources || [];
|
|
21
|
-
|
|
22
|
-
for (const v of envVars) {
|
|
23
|
-
const varSpec = {
|
|
24
|
-
name: v.name,
|
|
25
|
-
required: inferRequired(v, declared),
|
|
26
|
-
usedIn: (v.references || []).map(r => r.file).filter(Boolean),
|
|
27
|
-
declaredIn: declaredSources.filter(s => isDeclaredInSource(v.name, s)),
|
|
28
|
-
description: inferDescription(v.name),
|
|
29
|
-
evidence: v.references || []
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
contract.vars.push(varSpec);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Add declared vars that aren't used (might be optional)
|
|
36
|
-
for (const name of declared) {
|
|
37
|
-
if (!envVars.find(v => v.name === name)) {
|
|
38
|
-
contract.vars.push({
|
|
39
|
-
name,
|
|
40
|
-
required: false,
|
|
41
|
-
usedIn: [],
|
|
42
|
-
declaredIn: declaredSources,
|
|
43
|
-
description: inferDescription(name),
|
|
44
|
-
evidence: []
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return contract;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Infer if env var is required
|
|
54
|
-
*/
|
|
55
|
-
function inferRequired(envVar, declared) {
|
|
56
|
-
const name = envVar.name;
|
|
57
|
-
|
|
58
|
-
// Common required patterns
|
|
59
|
-
const requiredPatterns = [
|
|
60
|
-
/^DATABASE_URL$/i,
|
|
61
|
-
/^NEXTAUTH_SECRET$/i,
|
|
62
|
-
/^NEXTAUTH_URL$/i,
|
|
63
|
-
/^JWT_SECRET$/i,
|
|
64
|
-
/^API_KEY$/i,
|
|
65
|
-
/^STRIPE_SECRET_KEY$/i,
|
|
66
|
-
/^STRIPE_WEBHOOK_SECRET$/i,
|
|
67
|
-
/^AUTH0_/i,
|
|
68
|
-
/^CLERK_/i,
|
|
69
|
-
];
|
|
70
|
-
|
|
71
|
-
for (const pattern of requiredPatterns) {
|
|
72
|
-
if (pattern.test(name)) return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// If used but not declared, likely required
|
|
76
|
-
if (!declared.has(name) && envVar.references?.length > 0) {
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function isDeclaredInSource(name, source) {
|
|
84
|
-
// Simple heuristic - would need to parse files for accurate check
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function inferDescription(name) {
|
|
89
|
-
const descriptions = {
|
|
90
|
-
DATABASE_URL: "Database connection string",
|
|
91
|
-
NEXTAUTH_SECRET: "NextAuth.js encryption secret",
|
|
92
|
-
NEXTAUTH_URL: "NextAuth.js base URL",
|
|
93
|
-
JWT_SECRET: "JWT signing secret",
|
|
94
|
-
STRIPE_SECRET_KEY: "Stripe API secret key",
|
|
95
|
-
STRIPE_PUBLISHABLE_KEY: "Stripe publishable key",
|
|
96
|
-
STRIPE_WEBHOOK_SECRET: "Stripe webhook signing secret",
|
|
97
|
-
NODE_ENV: "Node environment (development/production)",
|
|
98
|
-
PORT: "Server port",
|
|
99
|
-
HOST: "Server host",
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
return descriptions[name] || undefined;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Validate code against env contract
|
|
107
|
-
*/
|
|
108
|
-
function validateAgainstEnvContract(contract, usedVars) {
|
|
109
|
-
const violations = [];
|
|
110
|
-
const contractVars = new Map(contract.vars.map(v => [v.name, v]));
|
|
111
|
-
|
|
112
|
-
for (const used of usedVars) {
|
|
113
|
-
if (!contractVars.has(used.name)) {
|
|
114
|
-
violations.push({
|
|
115
|
-
type: "undeclared_env",
|
|
116
|
-
severity: "WARN",
|
|
117
|
-
name: used.name,
|
|
118
|
-
usedIn: used.references?.map(r => r.file) || [],
|
|
119
|
-
message: `Env var ${used.name} used but not declared in contract`,
|
|
120
|
-
evidence: used.references || []
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Check for required vars that aren't used
|
|
126
|
-
for (const [name, spec] of contractVars) {
|
|
127
|
-
if (spec.required && spec.usedIn.length === 0) {
|
|
128
|
-
violations.push({
|
|
129
|
-
type: "unused_required",
|
|
130
|
-
severity: "WARN",
|
|
131
|
-
name,
|
|
132
|
-
message: `Required env var ${name} declared but not used`,
|
|
133
|
-
evidence: []
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return violations;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Diff two env contracts
|
|
143
|
-
*/
|
|
144
|
-
function diffEnvContracts(before, after) {
|
|
145
|
-
const diff = {
|
|
146
|
-
added: [],
|
|
147
|
-
removed: [],
|
|
148
|
-
changed: []
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const beforeMap = new Map(before.vars.map(v => [v.name, v]));
|
|
152
|
-
const afterMap = new Map(after.vars.map(v => [v.name, v]));
|
|
153
|
-
|
|
154
|
-
for (const [name, spec] of afterMap) {
|
|
155
|
-
if (!beforeMap.has(name)) {
|
|
156
|
-
diff.added.push(spec);
|
|
157
|
-
} else {
|
|
158
|
-
const prev = beforeMap.get(name);
|
|
159
|
-
if (prev.required !== spec.required) {
|
|
160
|
-
diff.changed.push({ before: prev, after: spec });
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
for (const [name, spec] of beforeMap) {
|
|
166
|
-
if (!afterMap.has(name)) {
|
|
167
|
-
diff.removed.push(spec);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
return diff;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
module.exports = {
|
|
175
|
-
buildEnvContract,
|
|
176
|
-
validateAgainstEnvContract,
|
|
177
|
-
diffEnvContracts
|
|
178
|
-
};
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External Contract Builder
|
|
3
|
-
* Builds external.json contract from truthpack (Stripe, GitHub, etc.)
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
"use strict";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Build external services contract from truthpack
|
|
10
|
-
*/
|
|
11
|
-
function buildExternalContract(truthpack) {
|
|
12
|
-
const contract = {
|
|
13
|
-
version: "1.0.0",
|
|
14
|
-
generatedAt: new Date().toISOString(),
|
|
15
|
-
services: []
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// Extract billing/Stripe info
|
|
19
|
-
const billing = truthpack?.billing || {};
|
|
20
|
-
if (billing.hasStripe || billing.webhookCandidates?.length) {
|
|
21
|
-
const stripeService = {
|
|
22
|
-
name: "stripe",
|
|
23
|
-
envVars: extractStripeEnvVars(truthpack),
|
|
24
|
-
usedIn: billing.webhookCandidates?.map(w => w.file) || [],
|
|
25
|
-
webhooks: billing.webhookCandidates?.map(w => ({
|
|
26
|
-
path: w.path,
|
|
27
|
-
verified: w.hasSignatureVerification || false,
|
|
28
|
-
idempotent: w.hasIdempotency || false
|
|
29
|
-
})) || [],
|
|
30
|
-
evidence: billing.webhookCandidates?.flatMap(w => w.evidence || []) || []
|
|
31
|
-
};
|
|
32
|
-
contract.services.push(stripeService);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Detect other external services from env vars
|
|
36
|
-
const envVars = truthpack?.env?.vars || [];
|
|
37
|
-
|
|
38
|
-
// GitHub
|
|
39
|
-
const githubVars = envVars.filter(v => /github/i.test(v.name));
|
|
40
|
-
if (githubVars.length) {
|
|
41
|
-
contract.services.push({
|
|
42
|
-
name: "github",
|
|
43
|
-
envVars: githubVars.map(v => v.name),
|
|
44
|
-
usedIn: githubVars.flatMap(v => v.references?.map(r => r.file) || []),
|
|
45
|
-
evidence: githubVars.flatMap(v => v.references || [])
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// SendGrid
|
|
50
|
-
const sendgridVars = envVars.filter(v => /sendgrid/i.test(v.name));
|
|
51
|
-
if (sendgridVars.length) {
|
|
52
|
-
contract.services.push({
|
|
53
|
-
name: "sendgrid",
|
|
54
|
-
envVars: sendgridVars.map(v => v.name),
|
|
55
|
-
usedIn: sendgridVars.flatMap(v => v.references?.map(r => r.file) || []),
|
|
56
|
-
evidence: sendgridVars.flatMap(v => v.references || [])
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Twilio
|
|
61
|
-
const twilioVars = envVars.filter(v => /twilio/i.test(v.name));
|
|
62
|
-
if (twilioVars.length) {
|
|
63
|
-
contract.services.push({
|
|
64
|
-
name: "twilio",
|
|
65
|
-
envVars: twilioVars.map(v => v.name),
|
|
66
|
-
usedIn: twilioVars.flatMap(v => v.references?.map(r => r.file) || []),
|
|
67
|
-
evidence: twilioVars.flatMap(v => v.references || [])
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// AWS
|
|
72
|
-
const awsVars = envVars.filter(v => /^aws/i.test(v.name));
|
|
73
|
-
if (awsVars.length) {
|
|
74
|
-
contract.services.push({
|
|
75
|
-
name: "aws",
|
|
76
|
-
envVars: awsVars.map(v => v.name),
|
|
77
|
-
usedIn: awsVars.flatMap(v => v.references?.map(r => r.file) || []),
|
|
78
|
-
evidence: awsVars.flatMap(v => v.references || [])
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Supabase
|
|
83
|
-
const supabaseVars = envVars.filter(v => /supabase/i.test(v.name));
|
|
84
|
-
if (supabaseVars.length) {
|
|
85
|
-
contract.services.push({
|
|
86
|
-
name: "supabase",
|
|
87
|
-
envVars: supabaseVars.map(v => v.name),
|
|
88
|
-
usedIn: supabaseVars.flatMap(v => v.references?.map(r => r.file) || []),
|
|
89
|
-
evidence: supabaseVars.flatMap(v => v.references || [])
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return contract;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function extractStripeEnvVars(truthpack) {
|
|
97
|
-
const envVars = truthpack?.env?.vars || [];
|
|
98
|
-
return envVars
|
|
99
|
-
.filter(v => /stripe/i.test(v.name))
|
|
100
|
-
.map(v => v.name);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Validate external services contract
|
|
105
|
-
*/
|
|
106
|
-
function validateExternalContract(contract) {
|
|
107
|
-
const violations = [];
|
|
108
|
-
|
|
109
|
-
for (const service of contract.services) {
|
|
110
|
-
// Check Stripe webhook verification
|
|
111
|
-
if (service.name === "stripe") {
|
|
112
|
-
for (const webhook of service.webhooks || []) {
|
|
113
|
-
if (!webhook.verified) {
|
|
114
|
-
violations.push({
|
|
115
|
-
type: "unverified_webhook",
|
|
116
|
-
severity: "BLOCK",
|
|
117
|
-
service: "stripe",
|
|
118
|
-
path: webhook.path,
|
|
119
|
-
message: `Stripe webhook at ${webhook.path} missing signature verification`,
|
|
120
|
-
evidence: []
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
if (!webhook.idempotent) {
|
|
124
|
-
violations.push({
|
|
125
|
-
type: "non_idempotent_webhook",
|
|
126
|
-
severity: "WARN",
|
|
127
|
-
service: "stripe",
|
|
128
|
-
path: webhook.path,
|
|
129
|
-
message: `Stripe webhook at ${webhook.path} may not be idempotent`,
|
|
130
|
-
evidence: []
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Check for missing required env vars
|
|
137
|
-
const requiredVars = getRequiredVarsForService(service.name);
|
|
138
|
-
for (const required of requiredVars) {
|
|
139
|
-
if (!service.envVars.includes(required)) {
|
|
140
|
-
violations.push({
|
|
141
|
-
type: "missing_env",
|
|
142
|
-
severity: "WARN",
|
|
143
|
-
service: service.name,
|
|
144
|
-
envVar: required,
|
|
145
|
-
message: `Service ${service.name} typically requires ${required}`,
|
|
146
|
-
evidence: []
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return violations;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function getRequiredVarsForService(name) {
|
|
156
|
-
const requirements = {
|
|
157
|
-
stripe: ["STRIPE_SECRET_KEY", "STRIPE_WEBHOOK_SECRET"],
|
|
158
|
-
github: ["GITHUB_TOKEN"],
|
|
159
|
-
sendgrid: ["SENDGRID_API_KEY"],
|
|
160
|
-
twilio: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN"],
|
|
161
|
-
supabase: ["SUPABASE_URL", "SUPABASE_ANON_KEY"]
|
|
162
|
-
};
|
|
163
|
-
return requirements[name] || [];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Diff two external contracts
|
|
168
|
-
*/
|
|
169
|
-
function diffExternalContracts(before, after) {
|
|
170
|
-
const diff = {
|
|
171
|
-
added: [],
|
|
172
|
-
removed: [],
|
|
173
|
-
changed: []
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const beforeMap = new Map(before.services.map(s => [s.name, s]));
|
|
177
|
-
const afterMap = new Map(after.services.map(s => [s.name, s]));
|
|
178
|
-
|
|
179
|
-
for (const [name, service] of afterMap) {
|
|
180
|
-
if (!beforeMap.has(name)) {
|
|
181
|
-
diff.added.push(service);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (const [name, service] of beforeMap) {
|
|
186
|
-
if (!afterMap.has(name)) {
|
|
187
|
-
diff.removed.push(service);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return diff;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
module.exports = {
|
|
195
|
-
buildExternalContract,
|
|
196
|
-
validateExternalContract,
|
|
197
|
-
diffExternalContracts
|
|
198
|
-
};
|