@vibecheckai/cli 3.2.0 → 3.2.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/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 +1119 -536
- package/bin/runners/lib/cli-output.js +236 -210
- package/bin/runners/lib/detectors-v2.js +547 -785
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +144 -738
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +188 -770
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runScan.js +113 -10
- package/bin/runners/runShip.js +7 -8
- package/bin/runners/runTruth.js +89 -0
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/index.js +347 -313
- package/mcp-server/truth-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1412 -1045
- package/package.json +1 -1
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firewall Hook Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages file system hook installation and control.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const { installFileSystemHook, startFileSystemHook, stopFileSystemHook } = require("./lib/agent-firewall/fs-hook/installer");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run firewall hook command
|
|
15
|
+
* @param {object} options - Command options
|
|
16
|
+
* @param {string} options.action - Action: 'install', 'start', 'stop', 'status'
|
|
17
|
+
* @param {string} options.projectRoot - Project root directory
|
|
18
|
+
*/
|
|
19
|
+
async function runFirewallHook(options = {}) {
|
|
20
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
21
|
+
const action = options.action || "status";
|
|
22
|
+
|
|
23
|
+
switch (action) {
|
|
24
|
+
case "install":
|
|
25
|
+
return installFileSystemHook(projectRoot);
|
|
26
|
+
|
|
27
|
+
case "start":
|
|
28
|
+
startFileSystemHook(projectRoot);
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
message: "File system hook started"
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
case "stop":
|
|
35
|
+
stopFileSystemHook();
|
|
36
|
+
return {
|
|
37
|
+
success: true,
|
|
38
|
+
message: "File system hook stopped"
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
case "status":
|
|
42
|
+
const markerFile = path.join(projectRoot, ".vibecheck", "fs-hook-enabled");
|
|
43
|
+
const installed = fs.existsSync(markerFile);
|
|
44
|
+
return {
|
|
45
|
+
installed,
|
|
46
|
+
running: false // Would need process management to check
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
default:
|
|
50
|
+
throw new Error(`Unknown action: ${action}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
runFirewallHook
|
|
56
|
+
};
|
package/bin/runners/runScan.js
CHANGED
|
@@ -42,6 +42,11 @@ const {
|
|
|
42
42
|
calculateScore,
|
|
43
43
|
} = require("./lib/scan-output");
|
|
44
44
|
|
|
45
|
+
const {
|
|
46
|
+
enrichFindings,
|
|
47
|
+
saveBaseline,
|
|
48
|
+
} = require("./lib/fingerprint");
|
|
49
|
+
|
|
45
50
|
const BANNER = `
|
|
46
51
|
${ansi.rgb(0, 200, 255)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${ansi.reset}
|
|
47
52
|
${ansi.rgb(30, 180, 255)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${ansi.reset}
|
|
@@ -103,6 +108,9 @@ function parseArgs(args) {
|
|
|
103
108
|
noBanner: globalFlags.noBanner || false,
|
|
104
109
|
ci: globalFlags.ci || false,
|
|
105
110
|
quiet: globalFlags.quiet || false,
|
|
111
|
+
// Baseline tracking (fingerprints)
|
|
112
|
+
baseline: true, // Compare against baseline by default
|
|
113
|
+
updateBaseline: false, // --update-baseline to save current findings as baseline
|
|
106
114
|
// Allowlist subcommand
|
|
107
115
|
allowlist: null, // null = not using allowlist, or 'list' | 'add' | 'remove' | 'check'
|
|
108
116
|
allowlistId: null,
|
|
@@ -124,6 +132,8 @@ function parseArgs(args) {
|
|
|
124
132
|
else if (arg === '--sarif') opts.sarif = true;
|
|
125
133
|
else if (arg === '--autofix' || arg === '--fix' || arg === '-f') opts.autofix = true;
|
|
126
134
|
else if (arg === '--no-save') opts.save = false;
|
|
135
|
+
else if (arg === '--no-baseline') opts.baseline = false;
|
|
136
|
+
else if (arg === '--update-baseline' || arg === '--set-baseline') opts.updateBaseline = true;
|
|
127
137
|
else if (arg === '--path' || arg === '-p') opts.path = cleanArgs[++i] || process.cwd();
|
|
128
138
|
else if (arg.startsWith('--path=')) opts.path = arg.split('=')[1];
|
|
129
139
|
// Allowlist subcommand support
|
|
@@ -753,6 +763,7 @@ async function runScan(args) {
|
|
|
753
763
|
findDeprecatedApis,
|
|
754
764
|
findEmptyCatch,
|
|
755
765
|
findUnsafeRegex,
|
|
766
|
+
clearFileCache, // V3: Memory management
|
|
756
767
|
} = require('./lib/analyzers');
|
|
757
768
|
|
|
758
769
|
scanRouteIntegrity = async function({ projectPath, layers, baseUrl, verbose }) {
|
|
@@ -778,6 +789,9 @@ async function runScan(args) {
|
|
|
778
789
|
findings.push(...findEmptyCatch(projectPath));
|
|
779
790
|
findings.push(...findUnsafeRegex(projectPath));
|
|
780
791
|
|
|
792
|
+
// V3: Clear file cache to prevent memory leaks in large monorepos
|
|
793
|
+
clearFileCache();
|
|
794
|
+
|
|
781
795
|
// Convert to scan format matching TypeScript scanner output
|
|
782
796
|
const shipBlockers = findings.map((f, i) => ({
|
|
783
797
|
id: f.id || `finding-${i}`,
|
|
@@ -1056,6 +1070,48 @@ async function runScan(args) {
|
|
|
1056
1070
|
return getExitCodeFromUnified ? getExitCodeFromUnified(verdict) : getExitCode(verdict);
|
|
1057
1071
|
}
|
|
1058
1072
|
|
|
1073
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1074
|
+
// FINGERPRINTING & BASELINE COMPARISON
|
|
1075
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1076
|
+
|
|
1077
|
+
let diff = null;
|
|
1078
|
+
if (opts.baseline) {
|
|
1079
|
+
try {
|
|
1080
|
+
const enrichResult = enrichFindings(normalizedFindings, projectPath, true);
|
|
1081
|
+
diff = enrichResult.diff;
|
|
1082
|
+
|
|
1083
|
+
// Update findings with fingerprints and status
|
|
1084
|
+
for (let i = 0; i < normalizedFindings.length; i++) {
|
|
1085
|
+
if (enrichResult.findings[i]) {
|
|
1086
|
+
normalizedFindings[i].fingerprint = enrichResult.findings[i].fingerprint;
|
|
1087
|
+
normalizedFindings[i].status = enrichResult.findings[i].status;
|
|
1088
|
+
normalizedFindings[i].firstSeen = enrichResult.findings[i].firstSeen;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
} catch (fpError) {
|
|
1092
|
+
if (opts.verbose) {
|
|
1093
|
+
console.warn(` ${ansi.dim}Fingerprinting skipped: ${fpError.message}${ansi.reset}`);
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Update baseline if requested
|
|
1099
|
+
if (opts.updateBaseline) {
|
|
1100
|
+
try {
|
|
1101
|
+
saveBaseline(projectPath, normalizedFindings, {
|
|
1102
|
+
verdict: verdict?.verdict,
|
|
1103
|
+
scanTime: new Date().toISOString(),
|
|
1104
|
+
});
|
|
1105
|
+
if (!opts.json && !opts.quiet) {
|
|
1106
|
+
console.log(` ${colors.success}✓${ansi.reset} Baseline updated with ${normalizedFindings.length} findings`);
|
|
1107
|
+
}
|
|
1108
|
+
} catch (blError) {
|
|
1109
|
+
if (opts.verbose) {
|
|
1110
|
+
console.warn(` ${ansi.dim}Baseline save failed: ${blError.message}${ansi.reset}`);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1059
1115
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
1060
1116
|
// ENHANCED OUTPUT
|
|
1061
1117
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1069,6 +1125,7 @@ async function runScan(args) {
|
|
|
1069
1125
|
breakdown: report.score?.breakdown,
|
|
1070
1126
|
timings,
|
|
1071
1127
|
cached,
|
|
1128
|
+
diff, // Include diff for display
|
|
1072
1129
|
};
|
|
1073
1130
|
console.log(formatScanOutput(resultForOutput, { verbose: opts.verbose }));
|
|
1074
1131
|
|
|
@@ -1164,6 +1221,60 @@ async function runScan(args) {
|
|
|
1164
1221
|
|
|
1165
1222
|
const verdict = criticalCount > 0 ? 'BLOCK' : warningCount > 0 ? 'WARN' : 'SHIP';
|
|
1166
1223
|
|
|
1224
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1225
|
+
// FINGERPRINTING & BASELINE COMPARISON (Legacy path)
|
|
1226
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
1227
|
+
|
|
1228
|
+
const normalizedLegacyFindings = findings.map(f => ({
|
|
1229
|
+
severity: f.severity === 'critical' || f.severity === 'BLOCK' ? 'critical' :
|
|
1230
|
+
f.severity === 'warning' || f.severity === 'WARN' ? 'medium' : 'low',
|
|
1231
|
+
category: f.category || 'ROUTE',
|
|
1232
|
+
title: f.title || f.message,
|
|
1233
|
+
message: f.message || f.title,
|
|
1234
|
+
file: f.file || f.evidence?.[0]?.file,
|
|
1235
|
+
line: f.line || parseInt(f.evidence?.[0]?.lines?.split('-')[0]) || 1,
|
|
1236
|
+
evidence: f.evidence,
|
|
1237
|
+
fix: f.fixSuggestion,
|
|
1238
|
+
}));
|
|
1239
|
+
|
|
1240
|
+
let diff = null;
|
|
1241
|
+
if (opts.baseline) {
|
|
1242
|
+
try {
|
|
1243
|
+
const enrichResult = enrichFindings(normalizedLegacyFindings, projectPath, true);
|
|
1244
|
+
diff = enrichResult.diff;
|
|
1245
|
+
|
|
1246
|
+
// Update findings with fingerprints and status
|
|
1247
|
+
for (let i = 0; i < normalizedLegacyFindings.length; i++) {
|
|
1248
|
+
if (enrichResult.findings[i]) {
|
|
1249
|
+
normalizedLegacyFindings[i].fingerprint = enrichResult.findings[i].fingerprint;
|
|
1250
|
+
normalizedLegacyFindings[i].status = enrichResult.findings[i].status;
|
|
1251
|
+
normalizedLegacyFindings[i].firstSeen = enrichResult.findings[i].firstSeen;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
} catch (fpError) {
|
|
1255
|
+
if (opts.verbose) {
|
|
1256
|
+
console.warn(` ${ansi.dim}Fingerprinting skipped: ${fpError.message}${ansi.reset}`);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// Update baseline if requested
|
|
1262
|
+
if (opts.updateBaseline) {
|
|
1263
|
+
try {
|
|
1264
|
+
saveBaseline(projectPath, normalizedLegacyFindings, {
|
|
1265
|
+
verdict,
|
|
1266
|
+
scanTime: new Date().toISOString(),
|
|
1267
|
+
});
|
|
1268
|
+
if (!opts.json && !opts.quiet) {
|
|
1269
|
+
console.log(` ${colors.success}✓${ansi.reset} Baseline updated with ${normalizedLegacyFindings.length} findings`);
|
|
1270
|
+
}
|
|
1271
|
+
} catch (blError) {
|
|
1272
|
+
if (opts.verbose) {
|
|
1273
|
+
console.warn(` ${ansi.dim}Baseline save failed: ${blError.message}${ansi.reset}`);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1167
1278
|
// Use enhanced output formatter for legacy fallback
|
|
1168
1279
|
const severityCounts = {
|
|
1169
1280
|
critical: criticalCount,
|
|
@@ -1175,18 +1286,10 @@ async function runScan(args) {
|
|
|
1175
1286
|
|
|
1176
1287
|
const result = {
|
|
1177
1288
|
verdict: { verdict, score },
|
|
1178
|
-
findings:
|
|
1179
|
-
severity: f.severity === 'critical' || f.severity === 'BLOCK' ? 'critical' :
|
|
1180
|
-
f.severity === 'warning' || f.severity === 'WARN' ? 'medium' : 'low',
|
|
1181
|
-
category: f.category || 'ROUTE',
|
|
1182
|
-
title: f.title || f.message,
|
|
1183
|
-
message: f.message || f.title,
|
|
1184
|
-
file: f.file,
|
|
1185
|
-
line: f.line,
|
|
1186
|
-
fix: f.fixSuggestion,
|
|
1187
|
-
})),
|
|
1289
|
+
findings: normalizedLegacyFindings,
|
|
1188
1290
|
layers: [],
|
|
1189
1291
|
timings,
|
|
1292
|
+
diff,
|
|
1190
1293
|
};
|
|
1191
1294
|
|
|
1192
1295
|
console.log(formatScanOutput(result, { verbose: opts.verbose }));
|
package/bin/runners/runShip.js
CHANGED
|
@@ -54,7 +54,7 @@ const {
|
|
|
54
54
|
} = require("./lib/terminal-ui");
|
|
55
55
|
|
|
56
56
|
const {
|
|
57
|
-
formatShipOutput,
|
|
57
|
+
formatShipOutput: formatShipOutputLegacy,
|
|
58
58
|
renderVerdictCard,
|
|
59
59
|
renderFixModeHeader,
|
|
60
60
|
renderFixResults,
|
|
@@ -64,6 +64,10 @@ const {
|
|
|
64
64
|
shipIcons,
|
|
65
65
|
} = require("./lib/ship-output");
|
|
66
66
|
|
|
67
|
+
const {
|
|
68
|
+
formatShipOutput,
|
|
69
|
+
} = require("./lib/ship-output-enterprise");
|
|
70
|
+
|
|
67
71
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
68
72
|
// PREMIUM BANNER
|
|
69
73
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
@@ -1031,7 +1035,7 @@ async function runShip(args, context = {}) {
|
|
|
1031
1035
|
if (spinner) spinner.succeed('Safe fixes applied');
|
|
1032
1036
|
}
|
|
1033
1037
|
|
|
1034
|
-
// Human-readable output using ship-output module
|
|
1038
|
+
// Human-readable output using enterprise ship-output module
|
|
1035
1039
|
const result = {
|
|
1036
1040
|
verdict,
|
|
1037
1041
|
score: results.score,
|
|
@@ -1047,14 +1051,9 @@ async function runShip(args, context = {}) {
|
|
|
1047
1051
|
// Get current tier for output formatting
|
|
1048
1052
|
const currentTier = context?.authInfo?.access?.tier || getCurrentTier() || "free";
|
|
1049
1053
|
|
|
1054
|
+
// Use enterprise format
|
|
1050
1055
|
console.log(formatShipOutput(result, {
|
|
1051
|
-
verbose: opts.verbose,
|
|
1052
|
-
showFix: opts.fix,
|
|
1053
|
-
showBadge: opts.badge,
|
|
1054
|
-
outputDir,
|
|
1055
|
-
projectPath,
|
|
1056
1056
|
tier: currentTier,
|
|
1057
|
-
isVerified: opts.withRuntime || false, // Reality testing = verified
|
|
1058
1057
|
}));
|
|
1059
1058
|
|
|
1060
1059
|
// Badge file generation (STARTER+ only)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truth Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Enhanced truth command to generate truthpack files.
|
|
5
|
+
* Generates: routes.json, env.json, auth.json, contracts.json, ui.graph.json
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { buildTruthpackV2 } = require("./lib/extractors/truthpack-v2");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Run truth command
|
|
16
|
+
* @param {object} options - Command options
|
|
17
|
+
* @param {string} options.scope - Scope: 'routes', 'env', 'auth', 'contracts', 'all'
|
|
18
|
+
* @param {string} options.projectRoot - Project root directory
|
|
19
|
+
*/
|
|
20
|
+
async function runTruth(options = {}) {
|
|
21
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
22
|
+
const scope = options.scope || "all";
|
|
23
|
+
|
|
24
|
+
const truthpackDir = path.join(projectRoot, ".vibecheck", "truthpack");
|
|
25
|
+
|
|
26
|
+
// Ensure directory exists
|
|
27
|
+
if (!fs.existsSync(truthpackDir)) {
|
|
28
|
+
fs.mkdirSync(truthpackDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Build truthpack
|
|
33
|
+
const truthpack = await buildTruthpackV2({
|
|
34
|
+
repoRoot: projectRoot
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Write truthpack files based on scope
|
|
38
|
+
if (scope === "all" || scope === "routes") {
|
|
39
|
+
const routesFile = path.join(truthpackDir, "routes.json");
|
|
40
|
+
fs.writeFileSync(routesFile, JSON.stringify({
|
|
41
|
+
routes: truthpack.routes || [],
|
|
42
|
+
gaps: truthpack.gaps || [],
|
|
43
|
+
stack: truthpack.stack || {}
|
|
44
|
+
}, null, 2));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (scope === "all" || scope === "env") {
|
|
48
|
+
const envFile = path.join(truthpackDir, "env.json");
|
|
49
|
+
fs.writeFileSync(envFile, JSON.stringify(truthpack.env || {}, null, 2));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (scope === "all" || scope === "auth") {
|
|
53
|
+
const authFile = path.join(truthpackDir, "auth.json");
|
|
54
|
+
fs.writeFileSync(authFile, JSON.stringify(truthpack.auth || {}, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (scope === "all" || scope === "contracts") {
|
|
58
|
+
const contractsFile = path.join(truthpackDir, "contracts.json");
|
|
59
|
+
fs.writeFileSync(contractsFile, JSON.stringify(truthpack.contracts || {}, null, 2));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// UI graph (if Reality enabled)
|
|
63
|
+
if (scope === "all" && truthpack.uiGraph) {
|
|
64
|
+
const uiGraphFile = path.join(truthpackDir, "ui.graph.json");
|
|
65
|
+
fs.writeFileSync(uiGraphFile, JSON.stringify(truthpack.uiGraph, null, 2));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
message: `Truthpack generated successfully (scope: ${scope})`,
|
|
71
|
+
files: {
|
|
72
|
+
routes: scope === "all" || scope === "routes" ? "routes.json" : null,
|
|
73
|
+
env: scope === "all" || scope === "env" ? "env.json" : null,
|
|
74
|
+
auth: scope === "all" || scope === "auth" ? "auth.json" : null,
|
|
75
|
+
contracts: scope === "all" || scope === "contracts" ? "contracts.json" : null,
|
|
76
|
+
uiGraph: scope === "all" && truthpack.uiGraph ? "ui.graph.json" : null
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return {
|
|
81
|
+
success: false,
|
|
82
|
+
error: error.message
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
runTruth
|
|
89
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Firewall Interceptor - MCP Tool
|
|
3
|
+
*
|
|
4
|
+
* Intercepts file write/patch tool calls from AI agents.
|
|
5
|
+
* Validates changes against truthpack and policy before allowing writes.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const fs = require("fs");
|
|
12
|
+
const { interceptFileWrite, interceptMultiFileWrite } = require("../../bin/runners/lib/agent-firewall/interceptor/base");
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* MCP Tool Definition
|
|
16
|
+
*/
|
|
17
|
+
const AGENT_FIREWALL_TOOL = {
|
|
18
|
+
name: "vibecheck_agent_firewall_intercept",
|
|
19
|
+
description: `🛡️ Agent Firewall - Intercepts AI code changes and validates against repo truth.
|
|
20
|
+
|
|
21
|
+
This tool MUST be called before any file write/patch operations.
|
|
22
|
+
It validates changes against truthpack and policy rules.
|
|
23
|
+
|
|
24
|
+
Returns: { allowed, verdict, violations, unblockPlan }`,
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
required: ["agentId", "filePath", "content"],
|
|
28
|
+
properties: {
|
|
29
|
+
agentId: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Agent identifier (e.g., 'cursor', 'windsurf', 'copilot')"
|
|
32
|
+
},
|
|
33
|
+
filePath: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "File path relative to project root"
|
|
36
|
+
},
|
|
37
|
+
content: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "New file content"
|
|
40
|
+
},
|
|
41
|
+
oldContent: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "Old file content (optional, for diff generation)"
|
|
44
|
+
},
|
|
45
|
+
intent: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Agent's stated intent for this change"
|
|
48
|
+
},
|
|
49
|
+
projectRoot: {
|
|
50
|
+
type: "string",
|
|
51
|
+
default: ".",
|
|
52
|
+
description: "Project root directory"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle MCP tool call
|
|
60
|
+
* @param {string} name - Tool name (unused, for consistency)
|
|
61
|
+
* @param {object} args - Tool arguments
|
|
62
|
+
* @returns {object} MCP tool response
|
|
63
|
+
*/
|
|
64
|
+
async function handleAgentFirewallIntercept(name, args) {
|
|
65
|
+
const projectRoot = path.resolve(args.projectRoot || ".");
|
|
66
|
+
const agentId = args.agentId || "unknown";
|
|
67
|
+
const filePath = args.filePath;
|
|
68
|
+
const content = args.content;
|
|
69
|
+
const oldContent = args.oldContent || null;
|
|
70
|
+
const intent = args.intent || "No intent provided";
|
|
71
|
+
|
|
72
|
+
// Validate file path is within project root
|
|
73
|
+
const fileAbs = path.resolve(projectRoot, filePath);
|
|
74
|
+
if (!fileAbs.startsWith(projectRoot + path.sep) && fileAbs !== projectRoot) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: "text",
|
|
78
|
+
text: `❌ BLOCKED: File path outside project root: ${filePath}`
|
|
79
|
+
}],
|
|
80
|
+
isError: true
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
// Read old content if not provided
|
|
86
|
+
let actualOldContent = oldContent;
|
|
87
|
+
if (!actualOldContent && fs.existsSync(fileAbs)) {
|
|
88
|
+
actualOldContent = fs.readFileSync(fileAbs, "utf8");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Intercept the write
|
|
92
|
+
const result = await interceptFileWrite({
|
|
93
|
+
projectRoot,
|
|
94
|
+
agentId,
|
|
95
|
+
intent,
|
|
96
|
+
filePath,
|
|
97
|
+
content,
|
|
98
|
+
oldContent: actualOldContent
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Check policy mode (already loaded in interceptFileWrite, but we need it for mode check)
|
|
102
|
+
const { loadPolicy } = require("../../bin/runners/lib/agent-firewall/policy/loader");
|
|
103
|
+
const policy = loadPolicy(projectRoot);
|
|
104
|
+
|
|
105
|
+
if (policy.mode === "observe") {
|
|
106
|
+
// Observe mode - log but don't block
|
|
107
|
+
return {
|
|
108
|
+
content: [{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: `📊 OBSERVE MODE: ${result.verdict}\n\n${result.message}\n\nPacket ID: ${result.packetId}`
|
|
111
|
+
}]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Enforce mode - block if not allowed
|
|
116
|
+
if (!result.allowed) {
|
|
117
|
+
let message = `❌ BLOCKED: ${result.message}\n\n`;
|
|
118
|
+
|
|
119
|
+
if (result.violations && result.violations.length > 0) {
|
|
120
|
+
message += "Violations:\n";
|
|
121
|
+
for (const violation of result.violations) {
|
|
122
|
+
message += ` - ${violation.rule}: ${violation.message}\n`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (result.unblockPlan && result.unblockPlan.steps.length > 0) {
|
|
127
|
+
message += "\nTo unblock:\n";
|
|
128
|
+
for (const step of result.unblockPlan.steps) {
|
|
129
|
+
message += ` ${step.action === "create" ? "Create" : "Modify"} ${step.file || "file"}: ${step.description}\n`;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
content: [{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: message
|
|
137
|
+
}],
|
|
138
|
+
isError: true
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Allowed
|
|
143
|
+
return {
|
|
144
|
+
content: [{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: `✅ ALLOWED: ${result.message}\n\nPacket ID: ${result.packetId}`
|
|
147
|
+
}]
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `❌ Error intercepting write: ${error.message}\n\n${error.stack}`
|
|
155
|
+
}],
|
|
156
|
+
isError: true
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
AGENT_FIREWALL_TOOL,
|
|
163
|
+
handleAgentFirewallIntercept
|
|
164
|
+
};
|