@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,228 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"required": ["id", "timestamp", "agentId", "intent", "files", "claims", "verdict"],
|
|
5
|
+
"properties": {
|
|
6
|
+
"id": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "Unique packet ID (hash-based)"
|
|
9
|
+
},
|
|
10
|
+
"timestamp": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"format": "date-time",
|
|
13
|
+
"description": "ISO timestamp of the change"
|
|
14
|
+
},
|
|
15
|
+
"agentId": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"description": "Identifier for the AI agent (e.g., 'cursor', 'windsurf', 'copilot')"
|
|
18
|
+
},
|
|
19
|
+
"intent": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"description": "Agent's stated intent for the change"
|
|
22
|
+
},
|
|
23
|
+
"diff": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"before": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "File content before change"
|
|
29
|
+
},
|
|
30
|
+
"after": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "File content after change"
|
|
33
|
+
},
|
|
34
|
+
"unified": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Unified diff format"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "object",
|
|
44
|
+
"required": ["path", "linesChanged"],
|
|
45
|
+
"properties": {
|
|
46
|
+
"path": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Relative file path"
|
|
49
|
+
},
|
|
50
|
+
"linesChanged": {
|
|
51
|
+
"type": "number",
|
|
52
|
+
"description": "Number of lines changed"
|
|
53
|
+
},
|
|
54
|
+
"domain": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"enum": ["auth", "payments", "routes", "contracts", "ui", "general"],
|
|
57
|
+
"description": "File domain classification"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"description": "List of changed files"
|
|
62
|
+
},
|
|
63
|
+
"claims": {
|
|
64
|
+
"type": "array",
|
|
65
|
+
"items": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"required": ["type", "value", "criticality"],
|
|
68
|
+
"properties": {
|
|
69
|
+
"type": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"enum": ["route", "env_used", "auth_boundary", "data_contract", "http_call", "ui_success_claim", "side_effect"],
|
|
72
|
+
"description": "Claim type"
|
|
73
|
+
},
|
|
74
|
+
"value": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"description": "Claim value (e.g., route path, env var name)"
|
|
77
|
+
},
|
|
78
|
+
"criticality": {
|
|
79
|
+
"type": "string",
|
|
80
|
+
"enum": ["hard", "soft"],
|
|
81
|
+
"description": "Claim criticality"
|
|
82
|
+
},
|
|
83
|
+
"pointer": {
|
|
84
|
+
"type": "string",
|
|
85
|
+
"description": "File:line pointer (e.g., 'file.ts:42-45')"
|
|
86
|
+
},
|
|
87
|
+
"reason": {
|
|
88
|
+
"type": "string",
|
|
89
|
+
"description": "Reason for claim extraction"
|
|
90
|
+
},
|
|
91
|
+
"file": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"description": "File where claim was found"
|
|
94
|
+
},
|
|
95
|
+
"domain": {
|
|
96
|
+
"type": "string",
|
|
97
|
+
"enum": ["auth", "payments", "routes", "contracts", "ui", "general"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"description": "Extracted claims from the change"
|
|
102
|
+
},
|
|
103
|
+
"evidence": {
|
|
104
|
+
"type": "array",
|
|
105
|
+
"items": {
|
|
106
|
+
"type": "object",
|
|
107
|
+
"required": ["claimId", "result"],
|
|
108
|
+
"properties": {
|
|
109
|
+
"claimId": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"description": "Reference to claim in claims array"
|
|
112
|
+
},
|
|
113
|
+
"result": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"enum": ["PROVEN", "UNPROVEN", "CONTRADICTS"],
|
|
116
|
+
"description": "Evidence resolution result"
|
|
117
|
+
},
|
|
118
|
+
"sources": {
|
|
119
|
+
"type": "array",
|
|
120
|
+
"items": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"type": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"enum": ["truthpack.routes", "truthpack.env", "truthpack.auth", "truthpack.contracts", "repo.search"]
|
|
126
|
+
},
|
|
127
|
+
"pointer": {
|
|
128
|
+
"type": "string"
|
|
129
|
+
},
|
|
130
|
+
"confidence": {
|
|
131
|
+
"type": "number",
|
|
132
|
+
"minimum": 0,
|
|
133
|
+
"maximum": 1
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
"description": "Evidence resolution results"
|
|
141
|
+
},
|
|
142
|
+
"verdict": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"required": ["decision", "violations"],
|
|
145
|
+
"properties": {
|
|
146
|
+
"decision": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"enum": ["ALLOW", "WARN", "BLOCK"],
|
|
149
|
+
"description": "Final verdict"
|
|
150
|
+
},
|
|
151
|
+
"violations": {
|
|
152
|
+
"type": "array",
|
|
153
|
+
"items": {
|
|
154
|
+
"type": "object",
|
|
155
|
+
"required": ["rule", "severity", "message"],
|
|
156
|
+
"properties": {
|
|
157
|
+
"rule": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"description": "Rule ID that was violated"
|
|
160
|
+
},
|
|
161
|
+
"severity": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"enum": ["allow", "warn", "block"]
|
|
164
|
+
},
|
|
165
|
+
"message": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"description": "Violation message"
|
|
168
|
+
},
|
|
169
|
+
"claimId": {
|
|
170
|
+
"type": "string",
|
|
171
|
+
"description": "Related claim ID"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"message": {
|
|
177
|
+
"type": "string",
|
|
178
|
+
"description": "Human-readable verdict message"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"unblockPlan": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"properties": {
|
|
185
|
+
"steps": {
|
|
186
|
+
"type": "array",
|
|
187
|
+
"items": {
|
|
188
|
+
"type": "object",
|
|
189
|
+
"required": ["action", "file", "description"],
|
|
190
|
+
"properties": {
|
|
191
|
+
"action": {
|
|
192
|
+
"type": "string",
|
|
193
|
+
"enum": ["add", "modify", "create"]
|
|
194
|
+
},
|
|
195
|
+
"file": {
|
|
196
|
+
"type": "string"
|
|
197
|
+
},
|
|
198
|
+
"line": {
|
|
199
|
+
"type": "number"
|
|
200
|
+
},
|
|
201
|
+
"description": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"description": "Plan to unblock if verdict is BLOCK"
|
|
209
|
+
},
|
|
210
|
+
"metadata": {
|
|
211
|
+
"type": "object",
|
|
212
|
+
"properties": {
|
|
213
|
+
"totalFiles": {
|
|
214
|
+
"type": "number"
|
|
215
|
+
},
|
|
216
|
+
"totalLines": {
|
|
217
|
+
"type": "number"
|
|
218
|
+
},
|
|
219
|
+
"policyVersion": {
|
|
220
|
+
"type": "string"
|
|
221
|
+
},
|
|
222
|
+
"policyProfile": {
|
|
223
|
+
"type": "string"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change Packet Store
|
|
3
|
+
*
|
|
4
|
+
* Stores change packets in .vibecheck/packets/YYYY-MM-DD/<hash>.json
|
|
5
|
+
* Provides querying capabilities by agent, date, verdict.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Store a change packet to disk
|
|
15
|
+
* @param {string} projectRoot - Project root directory
|
|
16
|
+
* @param {object} packet - Change packet to store
|
|
17
|
+
* @returns {string} Path where packet was stored
|
|
18
|
+
*/
|
|
19
|
+
function storePacket(projectRoot, packet) {
|
|
20
|
+
const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
|
|
21
|
+
|
|
22
|
+
// Create date-based directory
|
|
23
|
+
const date = new Date(packet.timestamp);
|
|
24
|
+
const dateDir = date.toISOString().split('T')[0]; // YYYY-MM-DD
|
|
25
|
+
const dayDir = path.join(packetsDir, dateDir);
|
|
26
|
+
|
|
27
|
+
// Ensure directory exists
|
|
28
|
+
if (!fs.existsSync(dayDir)) {
|
|
29
|
+
fs.mkdirSync(dayDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Write packet file
|
|
33
|
+
const packetPath = path.join(dayDir, `${packet.id}.json`);
|
|
34
|
+
fs.writeFileSync(packetPath, JSON.stringify(packet, null, 2));
|
|
35
|
+
|
|
36
|
+
return packetPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Load a change packet by ID
|
|
41
|
+
* @param {string} projectRoot - Project root directory
|
|
42
|
+
* @param {string} packetId - Packet ID
|
|
43
|
+
* @returns {object|null} Change packet or null if not found
|
|
44
|
+
*/
|
|
45
|
+
function loadPacket(projectRoot, packetId) {
|
|
46
|
+
const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
|
|
47
|
+
|
|
48
|
+
// Search all date directories
|
|
49
|
+
if (!fs.existsSync(packetsDir)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const dateDirs = fs.readdirSync(packetsDir, { withFileTypes: true })
|
|
54
|
+
.filter(dirent => dirent.isDirectory())
|
|
55
|
+
.map(dirent => dirent.name);
|
|
56
|
+
|
|
57
|
+
for (const dateDir of dateDirs) {
|
|
58
|
+
const dayDir = path.join(packetsDir, dateDir);
|
|
59
|
+
const packetPath = path.join(dayDir, `${packetId}.json`);
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(packetPath)) {
|
|
62
|
+
return JSON.parse(fs.readFileSync(packetPath, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Query change packets
|
|
71
|
+
* @param {string} projectRoot - Project root directory
|
|
72
|
+
* @param {object} filters - Query filters
|
|
73
|
+
* @param {string} filters.agentId - Filter by agent ID
|
|
74
|
+
* @param {string} filters.date - Filter by date (YYYY-MM-DD)
|
|
75
|
+
* @param {string} filters.verdict - Filter by verdict (ALLOW, WARN, BLOCK)
|
|
76
|
+
* @param {number} filters.limit - Maximum number of results
|
|
77
|
+
* @returns {array} Array of change packets
|
|
78
|
+
*/
|
|
79
|
+
function queryPackets(projectRoot, filters = {}) {
|
|
80
|
+
const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
|
|
81
|
+
|
|
82
|
+
if (!fs.existsSync(packetsDir)) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const results = [];
|
|
87
|
+
const { agentId, date, verdict, limit } = filters;
|
|
88
|
+
|
|
89
|
+
// Determine which date directories to search
|
|
90
|
+
const dateDirs = date
|
|
91
|
+
? [date]
|
|
92
|
+
: fs.readdirSync(packetsDir, { withFileTypes: true })
|
|
93
|
+
.filter(dirent => dirent.isDirectory())
|
|
94
|
+
.map(dirent => dirent.name)
|
|
95
|
+
.sort()
|
|
96
|
+
.reverse(); // Most recent first
|
|
97
|
+
|
|
98
|
+
for (const dateDir of dateDirs) {
|
|
99
|
+
const dayDir = path.join(packetsDir, dateDir);
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(dayDir)) continue;
|
|
102
|
+
|
|
103
|
+
const packetFiles = fs.readdirSync(dayDir)
|
|
104
|
+
.filter(file => file.endsWith('.json'))
|
|
105
|
+
.map(file => path.join(dayDir, file));
|
|
106
|
+
|
|
107
|
+
for (const packetPath of packetFiles) {
|
|
108
|
+
try {
|
|
109
|
+
const packet = JSON.parse(fs.readFileSync(packetPath, "utf8"));
|
|
110
|
+
|
|
111
|
+
// Apply filters
|
|
112
|
+
if (agentId && packet.agentId !== agentId) continue;
|
|
113
|
+
if (verdict && packet.verdict?.decision !== verdict) continue;
|
|
114
|
+
|
|
115
|
+
results.push(packet);
|
|
116
|
+
|
|
117
|
+
// Apply limit
|
|
118
|
+
if (limit && results.length >= limit) {
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// Skip invalid packets
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// If we have a date filter, we only need to check that one directory
|
|
128
|
+
if (date) break;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return results;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get statistics about stored packets
|
|
136
|
+
* @param {string} projectRoot - Project root directory
|
|
137
|
+
* @returns {object} Statistics
|
|
138
|
+
*/
|
|
139
|
+
function getPacketStats(projectRoot) {
|
|
140
|
+
const packetsDir = path.join(projectRoot, ".vibecheck", "packets");
|
|
141
|
+
|
|
142
|
+
if (!fs.existsSync(packetsDir)) {
|
|
143
|
+
return {
|
|
144
|
+
total: 0,
|
|
145
|
+
byAgent: {},
|
|
146
|
+
byVerdict: { ALLOW: 0, WARN: 0, BLOCK: 0 },
|
|
147
|
+
byDate: {}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const stats = {
|
|
152
|
+
total: 0,
|
|
153
|
+
byAgent: {},
|
|
154
|
+
byVerdict: { ALLOW: 0, WARN: 0, BLOCK: 0 },
|
|
155
|
+
byDate: {}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const dateDirs = fs.readdirSync(packetsDir, { withFileTypes: true })
|
|
159
|
+
.filter(dirent => dirent.isDirectory())
|
|
160
|
+
.map(dirent => dirent.name);
|
|
161
|
+
|
|
162
|
+
for (const dateDir of dateDirs) {
|
|
163
|
+
const dayDir = path.join(packetsDir, dateDir);
|
|
164
|
+
if (!fs.existsSync(dayDir)) continue;
|
|
165
|
+
|
|
166
|
+
const packetFiles = fs.readdirSync(dayDir)
|
|
167
|
+
.filter(file => file.endsWith('.json'));
|
|
168
|
+
|
|
169
|
+
stats.byDate[dateDir] = packetFiles.length;
|
|
170
|
+
|
|
171
|
+
for (const file of packetFiles) {
|
|
172
|
+
try {
|
|
173
|
+
const packet = JSON.parse(
|
|
174
|
+
fs.readFileSync(path.join(dayDir, file), "utf8")
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
stats.total++;
|
|
178
|
+
|
|
179
|
+
// Count by agent
|
|
180
|
+
stats.byAgent[packet.agentId] = (stats.byAgent[packet.agentId] || 0) + 1;
|
|
181
|
+
|
|
182
|
+
// Count by verdict
|
|
183
|
+
const verdict = packet.verdict?.decision || "ALLOW";
|
|
184
|
+
stats.byVerdict[verdict] = (stats.byVerdict[verdict] || 0) + 1;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// Skip invalid packets
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return stats;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
module.exports = {
|
|
196
|
+
storePacket,
|
|
197
|
+
loadPacket,
|
|
198
|
+
queryPackets,
|
|
199
|
+
getPacketStats
|
|
200
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim Types
|
|
3
|
+
*
|
|
4
|
+
* Enumeration of claim types and criticality levels.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
CLAIM_TYPES: {
|
|
9
|
+
ROUTE: "route",
|
|
10
|
+
ENV: "env_used",
|
|
11
|
+
AUTH: "auth_boundary",
|
|
12
|
+
CONTRACT: "data_contract",
|
|
13
|
+
HTTP_CALL: "http_call",
|
|
14
|
+
UI_SUCCESS: "ui_success_claim",
|
|
15
|
+
SIDE_EFFECT: "side_effect"
|
|
16
|
+
},
|
|
17
|
+
CRITICALITY: {
|
|
18
|
+
HARD: "hard",
|
|
19
|
+
SOFT: "soft"
|
|
20
|
+
}
|
|
21
|
+
};
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts "drift-causing claims" from changed files.
|
|
5
|
+
* Uses AST-based extraction with Babel parser.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const parser = require("@babel/parser");
|
|
11
|
+
const traverse = require("@babel/traverse").default;
|
|
12
|
+
const t = require("@babel/types");
|
|
13
|
+
const { CLAIM_TYPES, CRITICALITY } = require("./claim-types");
|
|
14
|
+
const { ROUTE_LIKE, AUTH_HINTS, SUCCESS_UI } = require("./patterns");
|
|
15
|
+
|
|
16
|
+
function parse(code) {
|
|
17
|
+
return parser.parse(code, {
|
|
18
|
+
sourceType: "unambiguous",
|
|
19
|
+
plugins: ["typescript", "jsx"]
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function locPtr(fileRel, node) {
|
|
24
|
+
const loc = node && node.loc;
|
|
25
|
+
if (!loc) return null;
|
|
26
|
+
return `${fileRel}:${loc.start.line}-${loc.end.line}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function pushClaim(out, claim) {
|
|
30
|
+
const key = `${claim.type}|${claim.value}|${claim.pointer || ""}`;
|
|
31
|
+
if (!out._dedupe.has(key)) {
|
|
32
|
+
out._dedupe.add(key);
|
|
33
|
+
out.claims.push(claim);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function extractStringLiterals(code) {
|
|
38
|
+
// Cheap scan for route-ish strings even if AST misses template parts.
|
|
39
|
+
const hits = [];
|
|
40
|
+
for (const rx of ROUTE_LIKE) {
|
|
41
|
+
const m = code.match(rx);
|
|
42
|
+
if (m) hits.push(...m);
|
|
43
|
+
}
|
|
44
|
+
return [...new Set(hits)];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function classifyFileDomain(fileRel) {
|
|
48
|
+
const s = fileRel.toLowerCase();
|
|
49
|
+
if (s.includes("auth")) return "auth";
|
|
50
|
+
if (s.includes("stripe") || s.includes("payment")) return "payments";
|
|
51
|
+
if (s.includes("routes") || s.includes("router") || s.includes("api")) return "routes";
|
|
52
|
+
if (s.includes("schema") || s.includes("contract") || s.includes("openapi")) return "contracts";
|
|
53
|
+
if (s.includes("ui") || s.includes("components") || s.includes("pages")) return "ui";
|
|
54
|
+
return "general";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function extractClaimsFromFile({ repoRoot, fileAbs }) {
|
|
58
|
+
const fileRel = path.relative(repoRoot, fileAbs).replace(/\\/g, "/");
|
|
59
|
+
const code = fs.readFileSync(fileAbs, "utf8");
|
|
60
|
+
const ast = parse(code);
|
|
61
|
+
|
|
62
|
+
const out = { fileRel, domain: classifyFileDomain(fileRel), claims: [], _dedupe: new Set() };
|
|
63
|
+
|
|
64
|
+
// 1) quick string route-ish scan
|
|
65
|
+
for (const r of extractStringLiterals(code)) {
|
|
66
|
+
pushClaim(out, {
|
|
67
|
+
type: CLAIM_TYPES.ROUTE,
|
|
68
|
+
value: r,
|
|
69
|
+
criticality: CRITICALITY.HARD,
|
|
70
|
+
pointer: `${fileRel}:1-1`,
|
|
71
|
+
reason: "route-like string literal"
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2) AST-based env extraction
|
|
76
|
+
traverse(ast, {
|
|
77
|
+
MemberExpression(p) {
|
|
78
|
+
// process.env.X
|
|
79
|
+
const n = p.node;
|
|
80
|
+
if (
|
|
81
|
+
t.isMemberExpression(n.object) &&
|
|
82
|
+
t.isIdentifier(n.object.object, { name: "process" }) &&
|
|
83
|
+
t.isIdentifier(n.object.property, { name: "env" }) &&
|
|
84
|
+
(t.isIdentifier(n.property) || t.isStringLiteral(n.property))
|
|
85
|
+
) {
|
|
86
|
+
const name = t.isIdentifier(n.property) ? n.property.name : n.property.value;
|
|
87
|
+
pushClaim(out, {
|
|
88
|
+
type: CLAIM_TYPES.ENV,
|
|
89
|
+
value: name,
|
|
90
|
+
criticality: CRITICALITY.HARD,
|
|
91
|
+
pointer: locPtr(fileRel, n),
|
|
92
|
+
reason: "process.env usage"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// import.meta.env.X (Vite)
|
|
97
|
+
if (
|
|
98
|
+
t.isMemberExpression(n.object) &&
|
|
99
|
+
t.isMemberExpression(n.object.object) &&
|
|
100
|
+
t.isIdentifier(n.object.object.object, { name: "import" }) &&
|
|
101
|
+
t.isIdentifier(n.object.object.property, { name: "meta" }) &&
|
|
102
|
+
t.isIdentifier(n.object.property, { name: "env" }) &&
|
|
103
|
+
(t.isIdentifier(n.property) || t.isStringLiteral(n.property))
|
|
104
|
+
) {
|
|
105
|
+
const name = t.isIdentifier(n.property) ? n.property.name : n.property.value;
|
|
106
|
+
pushClaim(out, {
|
|
107
|
+
type: CLAIM_TYPES.ENV,
|
|
108
|
+
value: name,
|
|
109
|
+
criticality: CRITICALITY.HARD,
|
|
110
|
+
pointer: locPtr(fileRel, n),
|
|
111
|
+
reason: "import.meta.env usage"
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
CallExpression(p) {
|
|
117
|
+
const n = p.node;
|
|
118
|
+
|
|
119
|
+
// fetch("/api/...")
|
|
120
|
+
if (t.isIdentifier(n.callee, { name: "fetch" }) && n.arguments[0]) {
|
|
121
|
+
const a0 = n.arguments[0];
|
|
122
|
+
if (t.isStringLiteral(a0)) {
|
|
123
|
+
pushClaim(out, {
|
|
124
|
+
type: CLAIM_TYPES.HTTP_CALL,
|
|
125
|
+
value: a0.value,
|
|
126
|
+
criticality: CRITICALITY.HARD,
|
|
127
|
+
pointer: locPtr(fileRel, n),
|
|
128
|
+
reason: "fetch call"
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// axios.get("/api/...")
|
|
134
|
+
if (t.isMemberExpression(n.callee) && t.isIdentifier(n.callee.object, { name: "axios" })) {
|
|
135
|
+
const method = t.isIdentifier(n.callee.property) ? n.callee.property.name : "call";
|
|
136
|
+
const a0 = n.arguments[0];
|
|
137
|
+
if (a0 && t.isStringLiteral(a0)) {
|
|
138
|
+
pushClaim(out, {
|
|
139
|
+
type: CLAIM_TYPES.HTTP_CALL,
|
|
140
|
+
value: `${method.toUpperCase()} ${a0.value}`,
|
|
141
|
+
criticality: CRITICALITY.HARD,
|
|
142
|
+
pointer: locPtr(fileRel, n),
|
|
143
|
+
reason: "axios call"
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// toast.success("Saved")
|
|
149
|
+
if (
|
|
150
|
+
t.isMemberExpression(n.callee) &&
|
|
151
|
+
t.isIdentifier(n.callee.object, { name: "toast" }) &&
|
|
152
|
+
t.isIdentifier(n.callee.property, { name: "success" })
|
|
153
|
+
) {
|
|
154
|
+
const msg = n.arguments[0] && t.isStringLiteral(n.arguments[0]) ? n.arguments[0].value : "toast.success";
|
|
155
|
+
pushClaim(out, {
|
|
156
|
+
type: CLAIM_TYPES.UI_SUCCESS,
|
|
157
|
+
value: msg,
|
|
158
|
+
criticality: CRITICALITY.SOFT,
|
|
159
|
+
pointer: locPtr(fileRel, n),
|
|
160
|
+
reason: "success UI signal"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// 3) auth-ish keyword scan (cheap but effective)
|
|
167
|
+
const lines = code.split(/\r?\n/);
|
|
168
|
+
for (let i = 0; i < lines.length; i++) {
|
|
169
|
+
const line = lines[i];
|
|
170
|
+
if (AUTH_HINTS.some((rx) => rx.test(line))) {
|
|
171
|
+
pushClaim(out, {
|
|
172
|
+
type: CLAIM_TYPES.AUTH,
|
|
173
|
+
value: line.trim().slice(0, 140),
|
|
174
|
+
criticality: CRITICALITY.HARD,
|
|
175
|
+
pointer: `${fileRel}:${i + 1}-${i + 1}`,
|
|
176
|
+
reason: "auth/role/scope hint"
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (SUCCESS_UI.some((rx) => rx.test(line))) {
|
|
180
|
+
pushClaim(out, {
|
|
181
|
+
type: CLAIM_TYPES.UI_SUCCESS,
|
|
182
|
+
value: line.trim().slice(0, 140),
|
|
183
|
+
criticality: CRITICALITY.SOFT,
|
|
184
|
+
pointer: `${fileRel}:${i + 1}-${i + 1}`,
|
|
185
|
+
reason: "success UI hint"
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
delete out._dedupe;
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function extractClaims({ repoRoot, changedFilesAbs }) {
|
|
195
|
+
const files = changedFilesAbs.filter((f) => fs.existsSync(f));
|
|
196
|
+
const perFile = files.map((fileAbs) => extractClaimsFromFile({ repoRoot, fileAbs }));
|
|
197
|
+
|
|
198
|
+
// Flatten + dedupe across files
|
|
199
|
+
const dedupe = new Set();
|
|
200
|
+
const claims = [];
|
|
201
|
+
for (const f of perFile) {
|
|
202
|
+
for (const c of f.claims) {
|
|
203
|
+
const k = `${c.type}|${c.value}`;
|
|
204
|
+
if (!dedupe.has(k)) {
|
|
205
|
+
dedupe.add(k);
|
|
206
|
+
claims.push({ ...c, file: f.fileRel, domain: f.domain });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { claims, perFile };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { extractClaims, extractClaimsFromFile };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claim Patterns
|
|
3
|
+
*
|
|
4
|
+
* Regex patterns for detecting claims in code.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const ROUTE_LIKE = [
|
|
8
|
+
/\/api\/[a-zA-Z0-9_\/\-]+/g,
|
|
9
|
+
/\/health\/[a-zA-Z0-9_\/\-]+/g
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const AUTH_HINTS = [
|
|
13
|
+
/\b(admin|owner|staff|superadmin|root)\b/i,
|
|
14
|
+
/\b(role|roles|scope|scopes|permission|permissions)\b/i,
|
|
15
|
+
/\b(auth|authorize|authorization|requireAuth|requireRole|rbac)\b/i
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const SUCCESS_UI = [
|
|
19
|
+
/\b(success|saved|updated|complete|done)\b/i,
|
|
20
|
+
/\btoast\.(success|info)\b/i,
|
|
21
|
+
/\benqueueSnackbar\b/i
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
module.exports = { ROUTE_LIKE, AUTH_HINTS, SUCCESS_UI };
|