@vibecheckai/cli 3.9.0 → 4.0.0
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/README.md +1 -1
- package/bin/runners/context/generators/cursor-enhanced.js +99 -13
- package/bin/runners/lib/unified-cli-output.js +16 -0
- package/bin/runners/runCI.js +353 -0
- package/bin/runners/runCheckpoint.js +2 -2
- package/mcp-server/.eslintrc.json +24 -0
- package/mcp-server/README.md +425 -135
- package/mcp-server/SPEC.md +583 -0
- package/mcp-server/configs/README.md +172 -0
- package/mcp-server/configs/claude-desktop-pro.json +31 -0
- package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
- package/mcp-server/configs/claude-desktop.json +19 -0
- package/mcp-server/configs/cursor-mcp.json +21 -0
- package/mcp-server/configs/windsurf-mcp.json +17 -0
- package/mcp-server/mcp-config.example.json +9 -0
- package/mcp-server/package.json +49 -34
- package/mcp-server/src/cli.ts +185 -0
- package/mcp-server/src/index.ts +85 -0
- package/mcp-server/src/server.ts +1933 -0
- package/mcp-server/src/services/cache-service.ts +466 -0
- package/mcp-server/src/services/cli-service.ts +345 -0
- package/mcp-server/src/services/context-manager.ts +717 -0
- package/mcp-server/src/services/firewall-service.ts +662 -0
- package/mcp-server/src/services/git-service.ts +671 -0
- package/mcp-server/src/services/index.ts +52 -0
- package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
- package/mcp-server/src/services/session-service.ts +550 -0
- package/mcp-server/src/services/tier-service.ts +470 -0
- package/mcp-server/src/types.ts +351 -0
- package/mcp-server/tsconfig.json +16 -27
- package/package.json +6 -6
- package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
- package/mcp-server/.specs/architecture.mdc +0 -90
- package/mcp-server/.specs/security.mdc +0 -30
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-checkpoint.js +0 -364
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/architect-tools.js +0 -707
- package/mcp-server/audit-mcp.js +0 -206
- package/mcp-server/authority-tools.js +0 -569
- package/mcp-server/codebase-architect-tools.js +0 -838
- package/mcp-server/conductor/conflict-resolver.js +0 -588
- package/mcp-server/conductor/execution-planner.js +0 -544
- package/mcp-server/conductor/index.js +0 -377
- package/mcp-server/conductor/lock-manager.js +0 -615
- package/mcp-server/conductor/request-queue.js +0 -550
- package/mcp-server/conductor/session-manager.js +0 -500
- package/mcp-server/conductor/tools.js +0 -510
- package/mcp-server/consolidated-tools.js +0 -1170
- package/mcp-server/deprecation-middleware.js +0 -282
- package/mcp-server/handlers/index.ts +0 -15
- package/mcp-server/handlers/tool-handler.ts +0 -593
- package/mcp-server/hygiene-tools.js +0 -428
- package/mcp-server/index-v1.js +0 -698
- package/mcp-server/index.js +0 -2940
- package/mcp-server/intelligence-tools.js +0 -664
- package/mcp-server/intent-drift-tools.js +0 -873
- package/mcp-server/intent-firewall-interceptor.js +0 -529
- package/mcp-server/lib/api-client.cjs +0 -13
- package/mcp-server/lib/cache-wrapper.cjs +0 -383
- package/mcp-server/lib/error-envelope.js +0 -138
- package/mcp-server/lib/executor.ts +0 -499
- package/mcp-server/lib/index.ts +0 -29
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/lib/rate-limiter.js +0 -166
- package/mcp-server/lib/sandbox.test.ts +0 -519
- package/mcp-server/lib/sandbox.ts +0 -395
- package/mcp-server/lib/types.ts +0 -267
- package/mcp-server/logger.js +0 -173
- package/mcp-server/manifest.json +0 -473
- package/mcp-server/mdc-generator.js +0 -298
- package/mcp-server/premium-tools.js +0 -1275
- package/mcp-server/proof-tools.js +0 -571
- package/mcp-server/registry/tool-registry.js +0 -586
- package/mcp-server/registry/tools.json +0 -619
- package/mcp-server/registry.test.ts +0 -340
- package/mcp-server/test-mcp.js +0 -108
- package/mcp-server/test-tools.js +0 -36
- package/mcp-server/tests/tier-gating.test.js +0 -297
- package/mcp-server/tier-auth.js +0 -767
- package/mcp-server/tools/index.js +0 -72
- package/mcp-server/tools-reorganized.ts +0 -244
- package/mcp-server/tools-v3.js +0 -1004
- package/mcp-server/truth-context.js +0 -622
- package/mcp-server/truth-firewall-tools.js +0 -2183
- package/mcp-server/vibecheck-2.0-tools.js +0 -761
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
- package/mcp-server/vibecheck-tools.js +0 -1075
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Firewall Service for MCP Server
|
|
3
|
+
* Provides agent-aware firewall with intent tracking, claim verification, and action gating
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CliService } from './cli-service.js';
|
|
7
|
+
import type {
|
|
8
|
+
FirewallMode,
|
|
9
|
+
FirewallStatus,
|
|
10
|
+
FirewallVerdict,
|
|
11
|
+
Intent,
|
|
12
|
+
IntentTemplate,
|
|
13
|
+
ShieldCheckResult,
|
|
14
|
+
ClaimVerificationRequest,
|
|
15
|
+
ClaimVerificationResult,
|
|
16
|
+
ClaimEvidence,
|
|
17
|
+
} from '../types.js';
|
|
18
|
+
|
|
19
|
+
// Pre-defined intent templates for common tasks
|
|
20
|
+
const INTENT_TEMPLATES: IntentTemplate[] = [
|
|
21
|
+
{
|
|
22
|
+
name: 'Add Auth',
|
|
23
|
+
summary: 'Add authentication feature',
|
|
24
|
+
constraints: ['Use existing auth middleware', 'No new environment variables', 'Do not change billing code'],
|
|
25
|
+
description: 'Safe for adding authentication without touching sensitive areas',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'Add Route',
|
|
29
|
+
summary: 'Add new API route',
|
|
30
|
+
constraints: ['No new env vars unless declared', 'No auth changes', 'Follow existing route patterns'],
|
|
31
|
+
description: 'For adding new API endpoints with existing patterns',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'Bug Fix',
|
|
35
|
+
summary: 'Fix a specific bug',
|
|
36
|
+
constraints: ['Minimal code changes', 'No new dependencies', 'No refactoring unrelated code'],
|
|
37
|
+
description: 'Tightly scoped bug fixes only',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'Refactor',
|
|
41
|
+
summary: 'Refactor existing code',
|
|
42
|
+
constraints: ['No behavior changes', 'Preserve all tests', 'No new features'],
|
|
43
|
+
description: 'Code quality improvements without behavior changes',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'Add Feature',
|
|
47
|
+
summary: 'Add new feature',
|
|
48
|
+
constraints: ['Use existing patterns', 'Add tests for new code', 'Update documentation'],
|
|
49
|
+
description: 'Feature additions with proper testing',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'Payment Flow',
|
|
53
|
+
summary: 'Modify payment/billing code',
|
|
54
|
+
constraints: ['No auth changes', 'Preserve existing integrations', 'Add audit logging'],
|
|
55
|
+
description: 'Sensitive payment code changes with audit trail',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'Database Migration',
|
|
59
|
+
summary: 'Modify database schema',
|
|
60
|
+
constraints: ['Create rollback migration', 'No data deletion', 'Test in staging first'],
|
|
61
|
+
description: 'Safe database schema changes',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'Dependency Update',
|
|
65
|
+
summary: 'Update project dependencies',
|
|
66
|
+
constraints: ['Update one major version at a time', 'Run full test suite', 'Check for breaking changes'],
|
|
67
|
+
description: 'Controlled dependency updates',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
// Action categories for gating
|
|
72
|
+
type ActionCategory = 'read' | 'write' | 'execute' | 'network' | 'sensitive';
|
|
73
|
+
|
|
74
|
+
interface ActionGateResult {
|
|
75
|
+
allowed: boolean;
|
|
76
|
+
reason: string;
|
|
77
|
+
requiresIntent: boolean;
|
|
78
|
+
suggestedIntent?: IntentTemplate;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class FirewallService {
|
|
82
|
+
private cliService: CliService;
|
|
83
|
+
private mode: FirewallMode = 'off';
|
|
84
|
+
private currentIntent: Intent | null = null;
|
|
85
|
+
private violationCount: number = 0;
|
|
86
|
+
private blockedCount: number = 0;
|
|
87
|
+
private sessionId: string;
|
|
88
|
+
private actionLog: ActionLogEntry[] = [];
|
|
89
|
+
|
|
90
|
+
constructor(cliService: CliService) {
|
|
91
|
+
this.cliService = cliService;
|
|
92
|
+
this.sessionId = this.generateSessionId();
|
|
93
|
+
this.initialize();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async initialize(): Promise<void> {
|
|
97
|
+
const status = await this.getStatus();
|
|
98
|
+
this.mode = status.mode;
|
|
99
|
+
if (status.currentIntent) {
|
|
100
|
+
this.currentIntent = status.currentIntent;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private generateSessionId(): string {
|
|
105
|
+
return `mcp_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
109
|
+
// Status & Mode Management
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get firewall status
|
|
114
|
+
*/
|
|
115
|
+
async getStatus(): Promise<FirewallStatus> {
|
|
116
|
+
const result = await this.cliService.shieldStatus();
|
|
117
|
+
|
|
118
|
+
if (result.success && result.data) {
|
|
119
|
+
const data = result.data as Record<string, unknown>;
|
|
120
|
+
this.mode = (data.mode as FirewallMode) || 'off';
|
|
121
|
+
return {
|
|
122
|
+
mode: this.mode,
|
|
123
|
+
enabled: this.mode !== 'off',
|
|
124
|
+
violationCount: (data.violationCount as number) || this.violationCount,
|
|
125
|
+
blockedCount: (data.blockedCount as number) || this.blockedCount,
|
|
126
|
+
lastCheck: data.lastCheck as string | undefined,
|
|
127
|
+
currentIntent: this.currentIntent || undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
mode: this.mode,
|
|
133
|
+
enabled: this.mode !== 'off',
|
|
134
|
+
violationCount: this.violationCount,
|
|
135
|
+
blockedCount: this.blockedCount,
|
|
136
|
+
currentIntent: this.currentIntent || undefined,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Set firewall mode
|
|
142
|
+
*/
|
|
143
|
+
async setMode(mode: FirewallMode): Promise<boolean> {
|
|
144
|
+
const result = await this.cliService.shieldSetMode(mode);
|
|
145
|
+
if (result.success) {
|
|
146
|
+
this.mode = mode;
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get current mode
|
|
154
|
+
*/
|
|
155
|
+
getMode(): FirewallMode {
|
|
156
|
+
return this.mode;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Check if firewall is enabled
|
|
161
|
+
*/
|
|
162
|
+
isEnabled(): boolean {
|
|
163
|
+
return this.mode !== 'off';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
167
|
+
// Intent Management (Enhanced for MCP)
|
|
168
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get current intent
|
|
172
|
+
*/
|
|
173
|
+
async getIntent(): Promise<Intent | null> {
|
|
174
|
+
const result = await this.cliService.intentShow();
|
|
175
|
+
|
|
176
|
+
if (result.success && result.data) {
|
|
177
|
+
const data = result.data as Record<string, unknown>;
|
|
178
|
+
const intent: Intent = {
|
|
179
|
+
summary: (data.summary as string) || (data.intent as string) || '',
|
|
180
|
+
constraints: (data.constraints as string[]) || [],
|
|
181
|
+
timestamp: data.timestamp as string | undefined,
|
|
182
|
+
sessionId: data.sessionId as string | undefined,
|
|
183
|
+
hash: data.hash as string | undefined,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
if (intent.summary) {
|
|
187
|
+
this.currentIntent = intent;
|
|
188
|
+
return intent;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Try parsing non-JSON output
|
|
193
|
+
if (result.output && result.output.includes('Intent:')) {
|
|
194
|
+
const summaryMatch = result.output.match(/Intent:\s*(.+)/);
|
|
195
|
+
if (summaryMatch) {
|
|
196
|
+
const intent: Intent = {
|
|
197
|
+
summary: summaryMatch[1].trim(),
|
|
198
|
+
constraints: [],
|
|
199
|
+
};
|
|
200
|
+
this.currentIntent = intent;
|
|
201
|
+
return intent;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
this.currentIntent = null;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set intent with summary and constraints
|
|
211
|
+
*/
|
|
212
|
+
async setIntent(summary: string, constraints: string[] = []): Promise<boolean> {
|
|
213
|
+
const result = await this.cliService.intentSet(summary, constraints);
|
|
214
|
+
|
|
215
|
+
if (result.success) {
|
|
216
|
+
this.currentIntent = {
|
|
217
|
+
summary,
|
|
218
|
+
constraints,
|
|
219
|
+
timestamp: new Date().toISOString(),
|
|
220
|
+
sessionId: this.sessionId,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
// Auto-enable observe mode if off
|
|
224
|
+
if (this.mode === 'off') {
|
|
225
|
+
await this.setMode('observe');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.logAction('intent_set', { summary, constraints });
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Set intent from a template
|
|
237
|
+
*/
|
|
238
|
+
async setIntentFromTemplate(templateName: string, customSummary?: string): Promise<boolean> {
|
|
239
|
+
const template = INTENT_TEMPLATES.find(t =>
|
|
240
|
+
t.name.toLowerCase() === templateName.toLowerCase()
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (!template) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const summary = customSummary || template.summary;
|
|
248
|
+
return this.setIntent(summary, template.constraints);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Clear current intent
|
|
253
|
+
*/
|
|
254
|
+
async clearIntent(): Promise<boolean> {
|
|
255
|
+
const result = await this.cliService.intentClear();
|
|
256
|
+
|
|
257
|
+
if (result.success) {
|
|
258
|
+
this.currentIntent = null;
|
|
259
|
+
this.logAction('intent_cleared', {});
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get cached intent (no CLI call)
|
|
268
|
+
*/
|
|
269
|
+
getCurrentIntent(): Intent | null {
|
|
270
|
+
return this.currentIntent;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if intent is set
|
|
275
|
+
*/
|
|
276
|
+
hasIntent(): boolean {
|
|
277
|
+
return this.currentIntent !== null && this.currentIntent.summary.length > 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get all intent templates
|
|
282
|
+
*/
|
|
283
|
+
getTemplates(): IntentTemplate[] {
|
|
284
|
+
return INTENT_TEMPLATES;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get a specific template by name
|
|
289
|
+
*/
|
|
290
|
+
getTemplate(name: string): IntentTemplate | undefined {
|
|
291
|
+
return INTENT_TEMPLATES.find(t =>
|
|
292
|
+
t.name.toLowerCase() === name.toLowerCase()
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
297
|
+
// Shield Check & Verification
|
|
298
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Run comprehensive shield check
|
|
302
|
+
*/
|
|
303
|
+
async check(): Promise<ShieldCheckResult | null> {
|
|
304
|
+
const result = await this.cliService.shieldCheck();
|
|
305
|
+
|
|
306
|
+
if (result.success && result.data) {
|
|
307
|
+
const data = result.data as Record<string, unknown>;
|
|
308
|
+
const checkResult: ShieldCheckResult = {
|
|
309
|
+
passed: (data.passed as boolean) ?? data.verdict === 'SHIP',
|
|
310
|
+
score: (data.score as number) || 0,
|
|
311
|
+
verdict: (data.verdict as 'SHIP' | 'WARN' | 'BLOCK') || 'WARN',
|
|
312
|
+
findings: (data.findings as ShieldCheckResult['findings']) || [],
|
|
313
|
+
truthpack: data.truthpack as ShieldCheckResult['truthpack'],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
this.logAction('shield_check', { verdict: checkResult.verdict, score: checkResult.score });
|
|
317
|
+
return checkResult;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Verify AI claims against current state
|
|
325
|
+
*/
|
|
326
|
+
async verify(claims?: string): Promise<FirewallVerdict | null> {
|
|
327
|
+
const result = await this.cliService.shieldVerify(claims);
|
|
328
|
+
|
|
329
|
+
if (result.success && result.data) {
|
|
330
|
+
const data = result.data as Record<string, unknown>;
|
|
331
|
+
const verdict: FirewallVerdict = {
|
|
332
|
+
allowed: (data.allowed as boolean) ?? true,
|
|
333
|
+
verdict: (data.verdict as 'ALLOW' | 'WARN' | 'BLOCK') || 'ALLOW',
|
|
334
|
+
violations: (data.violations as FirewallVerdict['violations']) || [],
|
|
335
|
+
unblockPlan: data.unblockPlan as FirewallVerdict['unblockPlan'],
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if (!verdict.allowed) {
|
|
339
|
+
this.violationCount++;
|
|
340
|
+
if (verdict.verdict === 'BLOCK') {
|
|
341
|
+
this.blockedCount++;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
this.logAction('verify', { verdict: verdict.verdict, allowed: verdict.allowed });
|
|
346
|
+
return verdict;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
353
|
+
// Enhanced MCP-Specific Features
|
|
354
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Verify a specific claim made by an AI agent
|
|
358
|
+
* This is an enhanced verification that analyzes the claim semantically
|
|
359
|
+
*/
|
|
360
|
+
async verifyClaim(request: ClaimVerificationRequest): Promise<ClaimVerificationResult> {
|
|
361
|
+
// First, check if we have an intent set
|
|
362
|
+
if (this.mode === 'enforce' && !this.hasIntent()) {
|
|
363
|
+
return {
|
|
364
|
+
verified: false,
|
|
365
|
+
confidence: 0,
|
|
366
|
+
verdict: 'REJECTED',
|
|
367
|
+
reasons: ['No intent is set. In enforce mode, all actions require an intent.'],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Run shield verification
|
|
372
|
+
const shieldResult = await this.verify(request.claim);
|
|
373
|
+
|
|
374
|
+
// Build evidence array
|
|
375
|
+
const evidence: ClaimEvidence[] = [];
|
|
376
|
+
|
|
377
|
+
// Check claim against intent constraints
|
|
378
|
+
const constraintViolations: string[] = [];
|
|
379
|
+
if (this.currentIntent) {
|
|
380
|
+
for (const constraint of this.currentIntent.constraints) {
|
|
381
|
+
// Simple heuristic check for constraint violations
|
|
382
|
+
const claimLower = request.claim.toLowerCase();
|
|
383
|
+
const constraintLower = constraint.toLowerCase();
|
|
384
|
+
|
|
385
|
+
// Check for obvious violations
|
|
386
|
+
if (constraintLower.includes('no new') && claimLower.includes('add')) {
|
|
387
|
+
if (constraintLower.includes('env') && claimLower.includes('env')) {
|
|
388
|
+
constraintViolations.push(`Constraint violation: "${constraint}"`);
|
|
389
|
+
}
|
|
390
|
+
if (constraintLower.includes('dependencies') && claimLower.includes('install')) {
|
|
391
|
+
constraintViolations.push(`Constraint violation: "${constraint}"`);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (constraintLower.includes('no auth') && claimLower.includes('auth')) {
|
|
395
|
+
constraintViolations.push(`Constraint violation: "${constraint}"`);
|
|
396
|
+
}
|
|
397
|
+
if (constraintLower.includes('no behavior') && claimLower.includes('change')) {
|
|
398
|
+
constraintViolations.push(`Constraint violation: "${constraint}"`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Calculate confidence based on findings
|
|
404
|
+
let confidence = 100;
|
|
405
|
+
const reasons: string[] = [];
|
|
406
|
+
|
|
407
|
+
if (shieldResult) {
|
|
408
|
+
if (!shieldResult.allowed) {
|
|
409
|
+
confidence -= 50;
|
|
410
|
+
reasons.push('Shield verification failed');
|
|
411
|
+
}
|
|
412
|
+
for (const violation of shieldResult.violations || []) {
|
|
413
|
+
confidence -= 10;
|
|
414
|
+
reasons.push(violation.message);
|
|
415
|
+
evidence.push({
|
|
416
|
+
type: 'behavior',
|
|
417
|
+
content: violation.message,
|
|
418
|
+
relevance: 0.8,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
for (const violation of constraintViolations) {
|
|
424
|
+
confidence -= 20;
|
|
425
|
+
reasons.push(violation);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Determine verdict
|
|
429
|
+
let verdict: 'VERIFIED' | 'SUSPICIOUS' | 'REJECTED';
|
|
430
|
+
if (confidence >= 80) {
|
|
431
|
+
verdict = 'VERIFIED';
|
|
432
|
+
} else if (confidence >= 50) {
|
|
433
|
+
verdict = 'SUSPICIOUS';
|
|
434
|
+
} else {
|
|
435
|
+
verdict = 'REJECTED';
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const result: ClaimVerificationResult = {
|
|
439
|
+
verified: confidence >= 80,
|
|
440
|
+
confidence: Math.max(0, confidence),
|
|
441
|
+
verdict,
|
|
442
|
+
reasons: reasons.length > 0 ? reasons : ['Claim appears valid'],
|
|
443
|
+
evidence: evidence.length > 0 ? evidence : undefined,
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
this.logAction('claim_verification', {
|
|
447
|
+
claim: request.claim.substring(0, 100),
|
|
448
|
+
verdict,
|
|
449
|
+
confidence,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Gate an action based on current firewall state and intent
|
|
457
|
+
*/
|
|
458
|
+
gateAction(action: string, category: ActionCategory): ActionGateResult {
|
|
459
|
+
// If firewall is off, allow everything
|
|
460
|
+
if (this.mode === 'off') {
|
|
461
|
+
return {
|
|
462
|
+
allowed: true,
|
|
463
|
+
reason: 'Firewall is disabled',
|
|
464
|
+
requiresIntent: false,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if intent is required for this action category
|
|
469
|
+
const sensitiveCategories: ActionCategory[] = ['write', 'execute', 'sensitive'];
|
|
470
|
+
const requiresIntent = sensitiveCategories.includes(category);
|
|
471
|
+
|
|
472
|
+
// In observe mode, allow but warn
|
|
473
|
+
if (this.mode === 'observe') {
|
|
474
|
+
if (requiresIntent && !this.hasIntent()) {
|
|
475
|
+
return {
|
|
476
|
+
allowed: true,
|
|
477
|
+
reason: 'Action allowed (observe mode) but intent is recommended',
|
|
478
|
+
requiresIntent: true,
|
|
479
|
+
suggestedIntent: this.suggestIntentForAction(action),
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
return {
|
|
483
|
+
allowed: true,
|
|
484
|
+
reason: 'Action allowed (observe mode)',
|
|
485
|
+
requiresIntent: false,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// In enforce mode, block sensitive actions without intent
|
|
490
|
+
if (this.mode === 'enforce') {
|
|
491
|
+
if (requiresIntent && !this.hasIntent()) {
|
|
492
|
+
this.blockedCount++;
|
|
493
|
+
return {
|
|
494
|
+
allowed: false,
|
|
495
|
+
reason: 'Intent required for this action in enforce mode',
|
|
496
|
+
requiresIntent: true,
|
|
497
|
+
suggestedIntent: this.suggestIntentForAction(action),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Check if action matches intent
|
|
502
|
+
if (this.currentIntent) {
|
|
503
|
+
const matchResult = this.matchActionToIntent(action);
|
|
504
|
+
if (!matchResult.matches) {
|
|
505
|
+
this.violationCount++;
|
|
506
|
+
return {
|
|
507
|
+
allowed: false,
|
|
508
|
+
reason: `Action does not match current intent: ${matchResult.reason}`,
|
|
509
|
+
requiresIntent: true,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
allowed: true,
|
|
516
|
+
reason: 'Action allowed (matches intent)',
|
|
517
|
+
requiresIntent: false,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return {
|
|
522
|
+
allowed: true,
|
|
523
|
+
reason: 'Action allowed',
|
|
524
|
+
requiresIntent: false,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Suggest an intent template based on the action
|
|
530
|
+
*/
|
|
531
|
+
private suggestIntentForAction(action: string): IntentTemplate | undefined {
|
|
532
|
+
const actionLower = action.toLowerCase();
|
|
533
|
+
|
|
534
|
+
if (actionLower.includes('auth') || actionLower.includes('login')) {
|
|
535
|
+
return this.getTemplate('Add Auth');
|
|
536
|
+
}
|
|
537
|
+
if (actionLower.includes('route') || actionLower.includes('api') || actionLower.includes('endpoint')) {
|
|
538
|
+
return this.getTemplate('Add Route');
|
|
539
|
+
}
|
|
540
|
+
if (actionLower.includes('fix') || actionLower.includes('bug')) {
|
|
541
|
+
return this.getTemplate('Bug Fix');
|
|
542
|
+
}
|
|
543
|
+
if (actionLower.includes('refactor') || actionLower.includes('clean')) {
|
|
544
|
+
return this.getTemplate('Refactor');
|
|
545
|
+
}
|
|
546
|
+
if (actionLower.includes('payment') || actionLower.includes('billing') || actionLower.includes('stripe')) {
|
|
547
|
+
return this.getTemplate('Payment Flow');
|
|
548
|
+
}
|
|
549
|
+
if (actionLower.includes('database') || actionLower.includes('migration') || actionLower.includes('schema')) {
|
|
550
|
+
return this.getTemplate('Database Migration');
|
|
551
|
+
}
|
|
552
|
+
if (actionLower.includes('update') || actionLower.includes('upgrade') || actionLower.includes('dependency')) {
|
|
553
|
+
return this.getTemplate('Dependency Update');
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return this.getTemplate('Add Feature');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Check if an action matches the current intent
|
|
561
|
+
*/
|
|
562
|
+
private matchActionToIntent(action: string): { matches: boolean; reason: string } {
|
|
563
|
+
if (!this.currentIntent) {
|
|
564
|
+
return { matches: false, reason: 'No intent set' };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
const actionLower = action.toLowerCase();
|
|
568
|
+
const intentLower = this.currentIntent.summary.toLowerCase();
|
|
569
|
+
|
|
570
|
+
// Check constraints
|
|
571
|
+
for (const constraint of this.currentIntent.constraints) {
|
|
572
|
+
const constraintLower = constraint.toLowerCase();
|
|
573
|
+
|
|
574
|
+
// "No new X" constraints
|
|
575
|
+
if (constraintLower.startsWith('no new') || constraintLower.startsWith('no ')) {
|
|
576
|
+
const forbidden = constraintLower.replace(/^no (new )?/, '').trim();
|
|
577
|
+
if (actionLower.includes(forbidden)) {
|
|
578
|
+
return { matches: false, reason: `Violates constraint: ${constraint}` };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// "Do not X" constraints
|
|
583
|
+
if (constraintLower.startsWith('do not')) {
|
|
584
|
+
const forbidden = constraintLower.replace('do not', '').trim();
|
|
585
|
+
if (actionLower.includes(forbidden)) {
|
|
586
|
+
return { matches: false, reason: `Violates constraint: ${constraint}` };
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return { matches: true, reason: 'Action matches intent constraints' };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
595
|
+
// Action Logging (Audit Trail for MCP)
|
|
596
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
597
|
+
|
|
598
|
+
private logAction(action: string, details: Record<string, unknown>): void {
|
|
599
|
+
this.actionLog.push({
|
|
600
|
+
timestamp: new Date().toISOString(),
|
|
601
|
+
sessionId: this.sessionId,
|
|
602
|
+
action,
|
|
603
|
+
details,
|
|
604
|
+
mode: this.mode,
|
|
605
|
+
hasIntent: this.hasIntent(),
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Keep only last 1000 entries
|
|
609
|
+
if (this.actionLog.length > 1000) {
|
|
610
|
+
this.actionLog = this.actionLog.slice(-1000);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Get action log
|
|
616
|
+
*/
|
|
617
|
+
getActionLog(limit?: number): ActionLogEntry[] {
|
|
618
|
+
const entries = limit ? this.actionLog.slice(-limit) : this.actionLog;
|
|
619
|
+
return entries;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Get session ID
|
|
624
|
+
*/
|
|
625
|
+
getSessionId(): string {
|
|
626
|
+
return this.sessionId;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Get statistics
|
|
631
|
+
*/
|
|
632
|
+
getStats(): FirewallStats {
|
|
633
|
+
return {
|
|
634
|
+
sessionId: this.sessionId,
|
|
635
|
+
mode: this.mode,
|
|
636
|
+
violationCount: this.violationCount,
|
|
637
|
+
blockedCount: this.blockedCount,
|
|
638
|
+
actionLogSize: this.actionLog.length,
|
|
639
|
+
hasIntent: this.hasIntent(),
|
|
640
|
+
currentIntent: this.currentIntent?.summary,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
interface ActionLogEntry {
|
|
646
|
+
timestamp: string;
|
|
647
|
+
sessionId: string;
|
|
648
|
+
action: string;
|
|
649
|
+
details: Record<string, unknown>;
|
|
650
|
+
mode: FirewallMode;
|
|
651
|
+
hasIntent: boolean;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
interface FirewallStats {
|
|
655
|
+
sessionId: string;
|
|
656
|
+
mode: FirewallMode;
|
|
657
|
+
violationCount: number;
|
|
658
|
+
blockedCount: number;
|
|
659
|
+
actionLogSize: number;
|
|
660
|
+
hasIntent: boolean;
|
|
661
|
+
currentIntent?: string;
|
|
662
|
+
}
|