@vibecheckai/cli 3.2.0 → 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/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/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 +93 -9
- package/bin/runners/lib/truth.js +1004 -321
- 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/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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Management Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Install/uninstall hooks, status checks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const { loadPolicy } = require("./lib/agent-firewall/policy/loader");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run agent command
|
|
15
|
+
* @param {object} options - Command options
|
|
16
|
+
* @param {string} options.action - Action: 'install', 'status', 'lock', 'unlock'
|
|
17
|
+
* @param {string} options.projectRoot - Project root directory
|
|
18
|
+
*/
|
|
19
|
+
async function runAgent(options = {}) {
|
|
20
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
21
|
+
const action = options.action || "status";
|
|
22
|
+
|
|
23
|
+
switch (action) {
|
|
24
|
+
case "install":
|
|
25
|
+
return installHooks(projectRoot);
|
|
26
|
+
|
|
27
|
+
case "status":
|
|
28
|
+
return showAgentStatus(projectRoot);
|
|
29
|
+
|
|
30
|
+
case "lock":
|
|
31
|
+
return lockRepo(projectRoot);
|
|
32
|
+
|
|
33
|
+
case "unlock":
|
|
34
|
+
return unlockRepo(projectRoot);
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`Unknown action: ${action}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Install IDE hooks
|
|
43
|
+
*/
|
|
44
|
+
function installHooks(projectRoot) {
|
|
45
|
+
// Create .vibecheck directory if it doesn't exist
|
|
46
|
+
const vibecheckDir = path.join(projectRoot, ".vibecheck");
|
|
47
|
+
if (!fs.existsSync(vibecheckDir)) {
|
|
48
|
+
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create hook marker file
|
|
52
|
+
const hookFile = path.join(vibecheckDir, "agent-firewall-enabled");
|
|
53
|
+
fs.writeFileSync(hookFile, JSON.stringify({
|
|
54
|
+
enabled: true,
|
|
55
|
+
installedAt: new Date().toISOString()
|
|
56
|
+
}, null, 2));
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
message: "Agent Firewall hooks installed. Firewall will intercept AI code changes."
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Show agent status
|
|
66
|
+
*/
|
|
67
|
+
function showAgentStatus(projectRoot) {
|
|
68
|
+
const hookFile = path.join(projectRoot, ".vibecheck", "agent-firewall-enabled");
|
|
69
|
+
const installed = fs.existsSync(hookFile);
|
|
70
|
+
|
|
71
|
+
let policy;
|
|
72
|
+
try {
|
|
73
|
+
policy = loadPolicy(projectRoot);
|
|
74
|
+
} catch {
|
|
75
|
+
policy = { mode: "observe", profile: "default" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
installed,
|
|
80
|
+
mode: policy.mode,
|
|
81
|
+
profile: policy.profile,
|
|
82
|
+
locked: policy.mode === "enforce" && policy.profile === "repo-lock"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Lock repo (enable repo-lock mode)
|
|
88
|
+
*/
|
|
89
|
+
function lockRepo(projectRoot) {
|
|
90
|
+
const { loadPolicy, savePolicy, getDefaultPolicy } = require("./lib/agent-firewall/policy/loader");
|
|
91
|
+
|
|
92
|
+
let policy;
|
|
93
|
+
try {
|
|
94
|
+
policy = loadPolicy(projectRoot);
|
|
95
|
+
} catch {
|
|
96
|
+
policy = getDefaultPolicy();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
policy.mode = "enforce";
|
|
100
|
+
policy.profile = "repo-lock";
|
|
101
|
+
|
|
102
|
+
// Enable all hard rules
|
|
103
|
+
if (!policy.rules) {
|
|
104
|
+
policy.rules = {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hardRules = [
|
|
108
|
+
"ghost_route",
|
|
109
|
+
"ghost_env",
|
|
110
|
+
"auth_drift",
|
|
111
|
+
"contract_drift",
|
|
112
|
+
"scope_explosion",
|
|
113
|
+
"unsafe_side_effect"
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const rule of hardRules) {
|
|
117
|
+
if (!policy.rules[rule]) {
|
|
118
|
+
policy.rules[rule] = {};
|
|
119
|
+
}
|
|
120
|
+
policy.rules[rule].enabled = true;
|
|
121
|
+
policy.rules[rule].severity = "block";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
savePolicy(projectRoot, policy);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
message: "Repo Lock Mode enabled. All hard rules enforced."
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Unlock repo (disable repo-lock mode)
|
|
134
|
+
*/
|
|
135
|
+
function unlockRepo(projectRoot) {
|
|
136
|
+
const { loadPolicy, savePolicy } = require("./lib/agent-firewall/policy/loader");
|
|
137
|
+
|
|
138
|
+
let policy;
|
|
139
|
+
try {
|
|
140
|
+
policy = loadPolicy(projectRoot);
|
|
141
|
+
} catch {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
message: "No policy found to unlock"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
policy.mode = "observe";
|
|
149
|
+
policy.profile = "balanced";
|
|
150
|
+
|
|
151
|
+
savePolicy(projectRoot, policy);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
message: "Repo Lock Mode disabled. Firewall in observe mode."
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
runAgent
|
|
161
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firewall Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Main firewall command handler.
|
|
5
|
+
* Enable/disable firewall, set mode, show status and statistics.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { loadPolicy, savePolicy, getDefaultPolicy } = require("./lib/agent-firewall/policy/loader");
|
|
13
|
+
const { getPacketStats, queryPackets } = require("./lib/agent-firewall/change-packet/store");
|
|
14
|
+
const { isTruthpackFresh } = require("./lib/agent-firewall/truthpack/loader");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run firewall command
|
|
18
|
+
* @param {object} options - Command options
|
|
19
|
+
* @param {string} options.mode - Set mode: 'observe' or 'enforce'
|
|
20
|
+
* @param {boolean} options.status - Show firewall status
|
|
21
|
+
* @param {boolean} options.stats - Show statistics
|
|
22
|
+
* @param {string} options.projectRoot - Project root directory
|
|
23
|
+
*/
|
|
24
|
+
async function runFirewall(options = {}) {
|
|
25
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
26
|
+
|
|
27
|
+
if (options.status) {
|
|
28
|
+
return showStatus(projectRoot);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (options.stats) {
|
|
32
|
+
return showStats(projectRoot);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (options.mode) {
|
|
36
|
+
return setMode(projectRoot, options.mode);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Default: show status
|
|
40
|
+
return showStatus(projectRoot);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Show firewall status
|
|
45
|
+
*/
|
|
46
|
+
function showStatus(projectRoot) {
|
|
47
|
+
try {
|
|
48
|
+
const policy = loadPolicy(projectRoot);
|
|
49
|
+
const truthpackFresh = isTruthpackFresh(projectRoot);
|
|
50
|
+
|
|
51
|
+
const output = {
|
|
52
|
+
mode: policy.mode || "observe",
|
|
53
|
+
profile: policy.profile || "default",
|
|
54
|
+
truthpackFresh,
|
|
55
|
+
rulesEnabled: Object.keys(policy.rules || {}).filter(
|
|
56
|
+
key => policy.rules[key]?.enabled !== false
|
|
57
|
+
).length
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return output;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
error: error.message,
|
|
64
|
+
mode: "unknown",
|
|
65
|
+
truthpackFresh: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Show firewall statistics
|
|
72
|
+
*/
|
|
73
|
+
function showStats(projectRoot) {
|
|
74
|
+
try {
|
|
75
|
+
const stats = getPacketStats(projectRoot);
|
|
76
|
+
const recentPackets = queryPackets(projectRoot, { limit: 10 });
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
total: stats.total,
|
|
80
|
+
byAgent: stats.byAgent,
|
|
81
|
+
byVerdict: stats.byVerdict,
|
|
82
|
+
byDate: stats.byDate,
|
|
83
|
+
recent: recentPackets.map(p => ({
|
|
84
|
+
id: p.id,
|
|
85
|
+
timestamp: p.timestamp,
|
|
86
|
+
agentId: p.agentId,
|
|
87
|
+
verdict: p.verdict?.decision,
|
|
88
|
+
files: p.files.length
|
|
89
|
+
}))
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
error: error.message,
|
|
94
|
+
total: 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set firewall mode
|
|
101
|
+
*/
|
|
102
|
+
function setMode(projectRoot, mode) {
|
|
103
|
+
if (mode !== "observe" && mode !== "enforce") {
|
|
104
|
+
throw new Error(`Invalid mode: ${mode}. Must be 'observe' or 'enforce'`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
let policy;
|
|
109
|
+
try {
|
|
110
|
+
policy = loadPolicy(projectRoot);
|
|
111
|
+
} catch {
|
|
112
|
+
// Policy doesn't exist, use default
|
|
113
|
+
policy = getDefaultPolicy();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
policy.mode = mode;
|
|
117
|
+
savePolicy(projectRoot, policy);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
mode,
|
|
122
|
+
message: `Firewall mode set to: ${mode}`
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: error.message
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
runFirewall
|
|
134
|
+
};
|
|
@@ -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 }));
|
|
@@ -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
|
+
};
|