@vibecheckai/cli 3.1.8 → 3.2.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/bin/registry.js +106 -116
- 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/agent-firewall/change-packet/builder.js +214 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/analysis-core.js +198 -180
- package/bin/runners/lib/analyzers.js +1394 -224
- package/bin/runners/lib/detectors-v2.js +560 -641
- package/bin/runners/lib/entitlements-v2.js +48 -1
- package/bin/runners/lib/evidence-pack.js +678 -0
- package/bin/runners/lib/fingerprint.js +377 -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/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +558 -235
- package/bin/runners/lib/ship-output.js +901 -641
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runCheckpoint.js +44 -3
- package/bin/runners/runContext.d.ts +4 -0
- package/bin/runners/runDoctor.js +10 -2
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runFix.js +51 -341
- package/bin/runners/runInit.js +11 -0
- 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 +846 -101
- package/bin/runners/runScan.js +351 -14
- package/bin/runners/runShip.js +19 -3
- package/bin/runners/runTruth.js +89 -0
- package/bin/runners/runWatch.js +14 -1
- package/bin/vibecheck.js +32 -2
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/consolidated-tools.js +408 -42
- package/mcp-server/index.js +498 -327
- 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-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1494 -941
- package/package.json +3 -1
- package/bin/runners/runInstall.js +0 -281
- package/bin/runners/runLabs.js +0 -341
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Common logic for all IDE interceptors.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const { extractClaims } = require("../claims/extractor");
|
|
12
|
+
const { resolveEvidence } = require("../evidence/resolver");
|
|
13
|
+
const { evaluatePolicy } = require("../policy/engine");
|
|
14
|
+
const { buildChangePacket, buildMultiFileChangePacket } = require("../change-packet/builder");
|
|
15
|
+
const { storePacket } = require("../change-packet/store");
|
|
16
|
+
const { generateUnblockPlan } = require("../unblock/planner");
|
|
17
|
+
const { loadPolicy } = require("../policy/loader");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Intercept a file write attempt
|
|
21
|
+
* @param {object} params
|
|
22
|
+
* @param {string} params.projectRoot - Project root directory
|
|
23
|
+
* @param {string} params.agentId - Agent identifier
|
|
24
|
+
* @param {string} params.intent - Agent intent message
|
|
25
|
+
* @param {string} params.filePath - File path (relative to project root)
|
|
26
|
+
* @param {string} params.content - New file content
|
|
27
|
+
* @param {string} params.oldContent - Old file content (optional)
|
|
28
|
+
* @returns {object} Interception result
|
|
29
|
+
*/
|
|
30
|
+
async function interceptFileWrite({
|
|
31
|
+
projectRoot,
|
|
32
|
+
agentId,
|
|
33
|
+
intent,
|
|
34
|
+
filePath,
|
|
35
|
+
content,
|
|
36
|
+
oldContent = null
|
|
37
|
+
}) {
|
|
38
|
+
// Load policy
|
|
39
|
+
const policy = loadPolicy(projectRoot);
|
|
40
|
+
|
|
41
|
+
// Generate diff
|
|
42
|
+
const diff = generateDiff(oldContent, content);
|
|
43
|
+
|
|
44
|
+
// Extract claims - write content to temp file if file doesn't exist
|
|
45
|
+
const fileAbs = path.join(projectRoot, filePath);
|
|
46
|
+
let tempFile = null;
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(fileAbs) && content) {
|
|
49
|
+
// Write content to temp file for extraction
|
|
50
|
+
const tempDir = path.join(projectRoot, ".vibecheck", "temp");
|
|
51
|
+
if (!fs.existsSync(tempDir)) {
|
|
52
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
tempFile = path.join(tempDir, path.basename(filePath));
|
|
55
|
+
fs.writeFileSync(tempFile, content, "utf8");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const fileToExtract = fs.existsSync(fileAbs) ? fileAbs : (tempFile || fileAbs);
|
|
59
|
+
const { claims } = extractClaims({
|
|
60
|
+
repoRoot: projectRoot,
|
|
61
|
+
changedFilesAbs: [fileToExtract]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Clean up temp file
|
|
65
|
+
if (tempFile && fs.existsSync(tempFile)) {
|
|
66
|
+
fs.unlinkSync(tempFile);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Resolve evidence
|
|
70
|
+
const evidence = resolveEvidence(projectRoot, claims);
|
|
71
|
+
|
|
72
|
+
// Build file info
|
|
73
|
+
const files = [{
|
|
74
|
+
path: filePath,
|
|
75
|
+
linesChanged: calculateLinesChanged(diff),
|
|
76
|
+
domain: classifyFileDomain(filePath)
|
|
77
|
+
}];
|
|
78
|
+
|
|
79
|
+
// Evaluate policy
|
|
80
|
+
const verdict = evaluatePolicy({
|
|
81
|
+
policy,
|
|
82
|
+
claims,
|
|
83
|
+
evidence,
|
|
84
|
+
files,
|
|
85
|
+
intent
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Generate unblock plan if blocked
|
|
89
|
+
let unblockPlan = null;
|
|
90
|
+
if (verdict.decision === "BLOCK") {
|
|
91
|
+
unblockPlan = generateUnblockPlan({
|
|
92
|
+
violations: verdict.violations,
|
|
93
|
+
claims,
|
|
94
|
+
projectRoot
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Build change packet
|
|
99
|
+
const packet = buildChangePacket({
|
|
100
|
+
agentId,
|
|
101
|
+
intent,
|
|
102
|
+
diff,
|
|
103
|
+
filePath,
|
|
104
|
+
claims,
|
|
105
|
+
evidence,
|
|
106
|
+
verdict,
|
|
107
|
+
unblockPlan,
|
|
108
|
+
policy
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Store packet if policy requires it
|
|
112
|
+
if (policy.output?.write_change_packets !== false) {
|
|
113
|
+
storePacket(projectRoot, packet);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Return interception result
|
|
117
|
+
return {
|
|
118
|
+
allowed: verdict.decision === "ALLOW",
|
|
119
|
+
verdict: verdict.decision,
|
|
120
|
+
violations: verdict.violations,
|
|
121
|
+
unblockPlan,
|
|
122
|
+
packetId: packet.id,
|
|
123
|
+
message: verdict.message
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Intercept multiple file writes
|
|
129
|
+
*/
|
|
130
|
+
async function interceptMultiFileWrite({
|
|
131
|
+
projectRoot,
|
|
132
|
+
agentId,
|
|
133
|
+
intent,
|
|
134
|
+
changes
|
|
135
|
+
}) {
|
|
136
|
+
// Load policy
|
|
137
|
+
const policy = loadPolicy(projectRoot);
|
|
138
|
+
|
|
139
|
+
// Extract claims from all files
|
|
140
|
+
const allClaims = [];
|
|
141
|
+
const allFiles = [];
|
|
142
|
+
|
|
143
|
+
for (const change of changes) {
|
|
144
|
+
const fileAbs = path.join(projectRoot, change.filePath);
|
|
145
|
+
const { claims } = extractClaims({
|
|
146
|
+
repoRoot: projectRoot,
|
|
147
|
+
changedFilesAbs: [fileAbs]
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
allClaims.push(...claims.map(c => ({ ...c, file: change.filePath })));
|
|
151
|
+
allFiles.push({
|
|
152
|
+
path: change.filePath,
|
|
153
|
+
linesChanged: calculateLinesChanged(change.diff),
|
|
154
|
+
domain: classifyFileDomain(change.filePath)
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Resolve evidence
|
|
159
|
+
const evidence = resolveEvidence(projectRoot, allClaims);
|
|
160
|
+
|
|
161
|
+
// Evaluate policy
|
|
162
|
+
const verdict = evaluatePolicy({
|
|
163
|
+
policy,
|
|
164
|
+
claims: allClaims,
|
|
165
|
+
evidence,
|
|
166
|
+
files: allFiles,
|
|
167
|
+
intent
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Generate unblock plan if blocked
|
|
171
|
+
let unblockPlan = null;
|
|
172
|
+
if (verdict.decision === "BLOCK") {
|
|
173
|
+
unblockPlan = generateUnblockPlan({
|
|
174
|
+
violations: verdict.violations,
|
|
175
|
+
claims: allClaims,
|
|
176
|
+
projectRoot
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Build change packet
|
|
181
|
+
const packet = buildMultiFileChangePacket({
|
|
182
|
+
agentId,
|
|
183
|
+
intent,
|
|
184
|
+
changes: changes.map(c => ({
|
|
185
|
+
filePath: c.filePath,
|
|
186
|
+
diff: c.diff,
|
|
187
|
+
claims: allClaims.filter(cl => cl.file === c.filePath)
|
|
188
|
+
})),
|
|
189
|
+
evidence,
|
|
190
|
+
verdict,
|
|
191
|
+
unblockPlan,
|
|
192
|
+
policy
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Store packet if policy requires it
|
|
196
|
+
if (policy.output?.write_change_packets !== false) {
|
|
197
|
+
storePacket(projectRoot, packet);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Return interception result
|
|
201
|
+
return {
|
|
202
|
+
allowed: verdict.decision === "ALLOW",
|
|
203
|
+
verdict: verdict.decision,
|
|
204
|
+
violations: verdict.violations,
|
|
205
|
+
unblockPlan,
|
|
206
|
+
packetId: packet.id,
|
|
207
|
+
message: verdict.message
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate unified diff
|
|
213
|
+
*/
|
|
214
|
+
function generateDiff(oldContent, newContent) {
|
|
215
|
+
if (!oldContent) {
|
|
216
|
+
return {
|
|
217
|
+
before: "",
|
|
218
|
+
after: newContent,
|
|
219
|
+
unified: generateUnifiedDiff("", newContent)
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
before: oldContent,
|
|
225
|
+
after: newContent,
|
|
226
|
+
unified: generateUnifiedDiff(oldContent, newContent)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate unified diff format
|
|
232
|
+
*/
|
|
233
|
+
function generateUnifiedDiff(oldContent, newContent) {
|
|
234
|
+
const oldLines = oldContent.split(/\r?\n/);
|
|
235
|
+
const newLines = newContent.split(/\r?\n/);
|
|
236
|
+
|
|
237
|
+
const diff = [];
|
|
238
|
+
let i = 0, j = 0;
|
|
239
|
+
|
|
240
|
+
while (i < oldLines.length || j < newLines.length) {
|
|
241
|
+
if (i >= oldLines.length) {
|
|
242
|
+
diff.push(`+${newLines[j]}`);
|
|
243
|
+
j++;
|
|
244
|
+
} else if (j >= newLines.length) {
|
|
245
|
+
diff.push(`-${oldLines[i]}`);
|
|
246
|
+
i++;
|
|
247
|
+
} else if (oldLines[i] === newLines[j]) {
|
|
248
|
+
diff.push(` ${oldLines[i]}`);
|
|
249
|
+
i++;
|
|
250
|
+
j++;
|
|
251
|
+
} else {
|
|
252
|
+
// Try to find matching line ahead
|
|
253
|
+
let found = false;
|
|
254
|
+
for (let k = j + 1; k < Math.min(j + 10, newLines.length); k++) {
|
|
255
|
+
if (oldLines[i] === newLines[k]) {
|
|
256
|
+
// Add new lines
|
|
257
|
+
for (let l = j; l < k; l++) {
|
|
258
|
+
diff.push(`+${newLines[l]}`);
|
|
259
|
+
}
|
|
260
|
+
j = k;
|
|
261
|
+
found = true;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!found) {
|
|
267
|
+
diff.push(`-${oldLines[i]}`);
|
|
268
|
+
diff.push(`+${newLines[j]}`);
|
|
269
|
+
i++;
|
|
270
|
+
j++;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return diff.join("\n");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Calculate lines changed from diff
|
|
280
|
+
*/
|
|
281
|
+
function calculateLinesChanged(diff) {
|
|
282
|
+
if (!diff || !diff.unified) return 0;
|
|
283
|
+
return diff.unified.split('\n').filter(line =>
|
|
284
|
+
line.startsWith('+') || line.startsWith('-')
|
|
285
|
+
).length;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Classify file domain
|
|
290
|
+
*/
|
|
291
|
+
function classifyFileDomain(filePath) {
|
|
292
|
+
const s = filePath.toLowerCase();
|
|
293
|
+
if (s.includes("auth")) return "auth";
|
|
294
|
+
if (s.includes("stripe") || s.includes("payment")) return "payments";
|
|
295
|
+
if (s.includes("routes") || s.includes("router") || s.includes("api")) return "routes";
|
|
296
|
+
if (s.includes("schema") || s.includes("contract") || s.includes("openapi")) return "contracts";
|
|
297
|
+
if (s.includes("ui") || s.includes("components") || s.includes("pages")) return "ui";
|
|
298
|
+
return "general";
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
module.exports = {
|
|
302
|
+
interceptFileWrite,
|
|
303
|
+
interceptMultiFileWrite
|
|
304
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor IDE Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Cursor-specific integration for agent firewall.
|
|
5
|
+
* Hooks into Cursor's file write events.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const { interceptFileWrite } = require("./base");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Intercept file write in Cursor
|
|
14
|
+
* This would be called by Cursor's extension/plugin system
|
|
15
|
+
*/
|
|
16
|
+
async function interceptCursorWrite({
|
|
17
|
+
projectRoot,
|
|
18
|
+
filePath,
|
|
19
|
+
content,
|
|
20
|
+
oldContent,
|
|
21
|
+
agentId = "cursor"
|
|
22
|
+
}) {
|
|
23
|
+
return await interceptFileWrite({
|
|
24
|
+
projectRoot,
|
|
25
|
+
agentId,
|
|
26
|
+
intent: "Cursor AI edit",
|
|
27
|
+
filePath,
|
|
28
|
+
content,
|
|
29
|
+
oldContent
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
interceptCursorWrite
|
|
35
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VS Code LSP Interceptor
|
|
3
|
+
*
|
|
4
|
+
* VS Code LSP integration for agent firewall.
|
|
5
|
+
* Generic LSP hook for file writes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const { interceptFileWrite } = require("./base");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Intercept file write via LSP
|
|
14
|
+
* This would be called by VS Code's LSP server
|
|
15
|
+
*/
|
|
16
|
+
async function interceptVSCodeWrite({
|
|
17
|
+
projectRoot,
|
|
18
|
+
filePath,
|
|
19
|
+
content,
|
|
20
|
+
oldContent,
|
|
21
|
+
agentId = "vscode"
|
|
22
|
+
}) {
|
|
23
|
+
return await interceptFileWrite({
|
|
24
|
+
projectRoot,
|
|
25
|
+
agentId,
|
|
26
|
+
intent: "VS Code AI edit",
|
|
27
|
+
filePath,
|
|
28
|
+
content,
|
|
29
|
+
oldContent
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
interceptVSCodeWrite
|
|
35
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windsurf IDE Interceptor
|
|
3
|
+
*
|
|
4
|
+
* Windsurf-specific integration for agent firewall.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { interceptFileWrite } = require("./base");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Intercept file write in Windsurf
|
|
13
|
+
* This would be called by Windsurf's extension/plugin system
|
|
14
|
+
*/
|
|
15
|
+
async function interceptWindsurfWrite({
|
|
16
|
+
projectRoot,
|
|
17
|
+
filePath,
|
|
18
|
+
content,
|
|
19
|
+
oldContent,
|
|
20
|
+
agentId = "windsurf"
|
|
21
|
+
}) {
|
|
22
|
+
return await interceptFileWrite({
|
|
23
|
+
projectRoot,
|
|
24
|
+
agentId,
|
|
25
|
+
intent: "Windsurf AI edit",
|
|
26
|
+
filePath,
|
|
27
|
+
content,
|
|
28
|
+
oldContent
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
interceptWindsurfWrite
|
|
34
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"mode": "enforce",
|
|
4
|
+
"profile": "repo-lock",
|
|
5
|
+
"scope": {
|
|
6
|
+
"max_files_touched": 10,
|
|
7
|
+
"max_lines_changed": 600,
|
|
8
|
+
"blocked_paths": [
|
|
9
|
+
"**/node_modules/**",
|
|
10
|
+
"**/dist/**",
|
|
11
|
+
"**/.next/**",
|
|
12
|
+
"**/.vibecheck/packets/**"
|
|
13
|
+
],
|
|
14
|
+
"allowed_paths": [
|
|
15
|
+
"apps/**",
|
|
16
|
+
"packages/**",
|
|
17
|
+
"src/**"
|
|
18
|
+
],
|
|
19
|
+
"require_intent_for_expand_scope": true
|
|
20
|
+
},
|
|
21
|
+
"hard_domains": {
|
|
22
|
+
"routes": true,
|
|
23
|
+
"env": true,
|
|
24
|
+
"auth": true,
|
|
25
|
+
"contracts": true,
|
|
26
|
+
"payments": true,
|
|
27
|
+
"side_effects": true
|
|
28
|
+
},
|
|
29
|
+
"rules": {
|
|
30
|
+
"ghost_route": {
|
|
31
|
+
"severity": "block",
|
|
32
|
+
"enabled": true
|
|
33
|
+
},
|
|
34
|
+
"ghost_env": {
|
|
35
|
+
"severity": "block",
|
|
36
|
+
"enabled": true
|
|
37
|
+
},
|
|
38
|
+
"auth_drift": {
|
|
39
|
+
"severity": "block",
|
|
40
|
+
"enabled": true
|
|
41
|
+
},
|
|
42
|
+
"contract_drift": {
|
|
43
|
+
"severity": "block",
|
|
44
|
+
"enabled": true
|
|
45
|
+
},
|
|
46
|
+
"fake_success_ui": {
|
|
47
|
+
"severity": "warn",
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"block_if_domain": ["payments", "auth", "side_effects"]
|
|
50
|
+
},
|
|
51
|
+
"scope_explosion": {
|
|
52
|
+
"severity": "block",
|
|
53
|
+
"enabled": true
|
|
54
|
+
},
|
|
55
|
+
"unsafe_side_effect": {
|
|
56
|
+
"severity": "block",
|
|
57
|
+
"enabled": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"evidence": {
|
|
61
|
+
"require_pointers": true,
|
|
62
|
+
"acceptable_sources": [
|
|
63
|
+
"truthpack.routes",
|
|
64
|
+
"truthpack.env",
|
|
65
|
+
"truthpack.auth",
|
|
66
|
+
"truthpack.contracts",
|
|
67
|
+
"repo.search"
|
|
68
|
+
],
|
|
69
|
+
"pointer_format": "file:lineStart-lineEnd"
|
|
70
|
+
},
|
|
71
|
+
"verification": {
|
|
72
|
+
"require_for_domains": ["auth", "payments", "side_effects"],
|
|
73
|
+
"accepted": ["tests", "reality"],
|
|
74
|
+
"reality": {
|
|
75
|
+
"enabled": true,
|
|
76
|
+
"block_on": ["fake_success", "no_mutation", "network_error"]
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"output": {
|
|
80
|
+
"write_change_packets": true,
|
|
81
|
+
"packet_dir": ".vibecheck/packets",
|
|
82
|
+
"report_formats": ["md", "html"]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Engine
|
|
3
|
+
*
|
|
4
|
+
* Main policy engine that evaluates all rules deterministically.
|
|
5
|
+
* No LLM opinions - pure rule-based evaluation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const ghostRoute = require("./rules/ghost-route");
|
|
11
|
+
const ghostEnv = require("./rules/ghost-env");
|
|
12
|
+
const authDrift = require("./rules/auth-drift");
|
|
13
|
+
const contractDrift = require("./rules/contract-drift");
|
|
14
|
+
const fakeSuccess = require("./rules/fake-success");
|
|
15
|
+
const scope = require("./rules/scope");
|
|
16
|
+
const unsafeSideEffect = require("./rules/unsafe-side-effect");
|
|
17
|
+
const { generateVerdict } = require("./verdict");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Evaluate policy against change packet
|
|
21
|
+
* @param {object} params
|
|
22
|
+
* @param {object} params.policy - Policy configuration
|
|
23
|
+
* @param {array} params.claims - Extracted claims
|
|
24
|
+
* @param {array} params.evidence - Evidence resolution results
|
|
25
|
+
* @param {array} params.files - Changed files
|
|
26
|
+
* @param {string} params.intent - Agent intent message
|
|
27
|
+
* @returns {object} Verdict object
|
|
28
|
+
*/
|
|
29
|
+
function evaluatePolicy({ policy, claims, evidence, files, intent }) {
|
|
30
|
+
const violations = [];
|
|
31
|
+
|
|
32
|
+
// Evaluate all rules
|
|
33
|
+
const ruleEvaluators = [
|
|
34
|
+
ghostRoute,
|
|
35
|
+
ghostEnv,
|
|
36
|
+
authDrift,
|
|
37
|
+
contractDrift,
|
|
38
|
+
fakeSuccess,
|
|
39
|
+
unsafeSideEffect
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Evaluate claim-based rules
|
|
43
|
+
for (const evaluator of ruleEvaluators) {
|
|
44
|
+
try {
|
|
45
|
+
const violation = evaluator.evaluate({ claims, evidence, policy });
|
|
46
|
+
if (violation) {
|
|
47
|
+
violations.push(violation);
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn(`Rule evaluation error: ${error.message}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Evaluate scope rule (file-based)
|
|
55
|
+
try {
|
|
56
|
+
const violation = scope.evaluate({ files, intent, policy });
|
|
57
|
+
if (violation) {
|
|
58
|
+
violations.push(violation);
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn(`Scope rule evaluation error: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Generate final verdict
|
|
65
|
+
const verdict = generateVerdict(violations);
|
|
66
|
+
|
|
67
|
+
return verdict;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
evaluatePolicy
|
|
72
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Policy Loader
|
|
3
|
+
*
|
|
4
|
+
* Loads and validates agent firewall policy from .vibecheck/policy.json
|
|
5
|
+
* Falls back to default policy if not found.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const Ajv = require("ajv").default || require("ajv");
|
|
13
|
+
|
|
14
|
+
// Load schema
|
|
15
|
+
const schemaPath = path.join(__dirname, "schema.json");
|
|
16
|
+
const schema = JSON.parse(fs.readFileSync(schemaPath, "utf8"));
|
|
17
|
+
|
|
18
|
+
// Load default policy
|
|
19
|
+
const defaultPolicyPath = path.join(__dirname, "default-policy.json");
|
|
20
|
+
const defaultPolicy = JSON.parse(fs.readFileSync(defaultPolicyPath, "utf8"));
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Load policy from project root
|
|
24
|
+
* @param {string} projectRoot - Project root directory
|
|
25
|
+
* @param {object} overrides - Optional policy overrides
|
|
26
|
+
* @returns {object} Loaded and validated policy
|
|
27
|
+
*/
|
|
28
|
+
function loadPolicy(projectRoot, overrides = {}) {
|
|
29
|
+
const policyPath = path.join(projectRoot, ".vibecheck", "policy.json");
|
|
30
|
+
|
|
31
|
+
let policy;
|
|
32
|
+
|
|
33
|
+
// Try to load project policy
|
|
34
|
+
if (fs.existsSync(policyPath)) {
|
|
35
|
+
try {
|
|
36
|
+
policy = JSON.parse(fs.readFileSync(policyPath, "utf8"));
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Failed to parse policy file: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
// Use default policy
|
|
42
|
+
policy = JSON.parse(JSON.stringify(defaultPolicy));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Apply overrides (deep merge)
|
|
46
|
+
if (Object.keys(overrides).length > 0) {
|
|
47
|
+
policy = deepMerge(policy, overrides);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Validate against schema
|
|
51
|
+
const ajv = new Ajv({ allErrors: true });
|
|
52
|
+
const validate = ajv.compile(schema);
|
|
53
|
+
const valid = validate(policy);
|
|
54
|
+
|
|
55
|
+
if (!valid) {
|
|
56
|
+
const errors = validate.errors.map(e =>
|
|
57
|
+
`${e.instancePath || 'root'} ${e.message}`
|
|
58
|
+
).join(', ');
|
|
59
|
+
throw new Error(`Policy validation failed: ${errors}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return policy;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Deep merge two objects
|
|
67
|
+
* @param {object} target - Target object
|
|
68
|
+
* @param {object} source - Source object to merge
|
|
69
|
+
* @returns {object} Merged object
|
|
70
|
+
*/
|
|
71
|
+
function deepMerge(target, source) {
|
|
72
|
+
const output = Object.assign({}, target);
|
|
73
|
+
|
|
74
|
+
if (isObject(target) && isObject(source)) {
|
|
75
|
+
Object.keys(source).forEach(key => {
|
|
76
|
+
if (isObject(source[key])) {
|
|
77
|
+
if (!(key in target)) {
|
|
78
|
+
Object.assign(output, { [key]: source[key] });
|
|
79
|
+
} else {
|
|
80
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
81
|
+
}
|
|
82
|
+
} else {
|
|
83
|
+
Object.assign(output, { [key]: source[key] });
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return output;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check if value is a plain object
|
|
93
|
+
* @param {*} item - Value to check
|
|
94
|
+
* @returns {boolean}
|
|
95
|
+
*/
|
|
96
|
+
function isObject(item) {
|
|
97
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get default policy
|
|
102
|
+
* @returns {object} Default policy
|
|
103
|
+
*/
|
|
104
|
+
function getDefaultPolicy() {
|
|
105
|
+
return JSON.parse(JSON.stringify(defaultPolicy));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Save policy to project root
|
|
110
|
+
* @param {string} projectRoot - Project root directory
|
|
111
|
+
* @param {object} policy - Policy to save
|
|
112
|
+
*/
|
|
113
|
+
function savePolicy(projectRoot, policy) {
|
|
114
|
+
const vibecheckDir = path.join(projectRoot, ".vibecheck");
|
|
115
|
+
const policyPath = path.join(vibecheckDir, "policy.json");
|
|
116
|
+
|
|
117
|
+
// Ensure .vibecheck directory exists
|
|
118
|
+
if (!fs.existsSync(vibecheckDir)) {
|
|
119
|
+
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate before saving
|
|
123
|
+
const ajv = new Ajv({ allErrors: true });
|
|
124
|
+
const validate = ajv.compile(schema);
|
|
125
|
+
const valid = validate(policy);
|
|
126
|
+
|
|
127
|
+
if (!valid) {
|
|
128
|
+
const errors = validate.errors.map(e =>
|
|
129
|
+
`${e.instancePath || 'root'} ${e.message}`
|
|
130
|
+
).join(', ');
|
|
131
|
+
throw new Error(`Policy validation failed: ${errors}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(policyPath, JSON.stringify(policy, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
loadPolicy,
|
|
139
|
+
getDefaultPolicy,
|
|
140
|
+
savePolicy,
|
|
141
|
+
schema,
|
|
142
|
+
defaultPolicy
|
|
143
|
+
};
|