@vibecheckai/cli 3.4.0 → 3.5.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 +154 -338
- package/bin/runners/context/generators/mcp.js +13 -15
- package/bin/runners/context/proof-context.js +1 -248
- package/bin/runners/lib/analysis-core.js +180 -198
- package/bin/runners/lib/analyzers.js +223 -1669
- package/bin/runners/lib/cli-output.js +210 -242
- package/bin/runners/lib/detectors-v2.js +785 -547
- package/bin/runners/lib/entitlements-v2.js +458 -96
- package/bin/runners/lib/error-handler.js +9 -16
- package/bin/runners/lib/global-flags.js +0 -37
- package/bin/runners/lib/route-truth.js +322 -1167
- package/bin/runners/lib/scan-output.js +469 -448
- package/bin/runners/lib/ship-output.js +27 -280
- package/bin/runners/lib/terminal-ui.js +733 -231
- package/bin/runners/lib/truth.js +321 -1004
- package/bin/runners/lib/unified-output.js +158 -162
- package/bin/runners/lib/upsell.js +204 -104
- package/bin/runners/runAllowlist.js +324 -0
- package/bin/runners/runAuth.js +95 -324
- package/bin/runners/runCheckpoint.js +21 -39
- package/bin/runners/runContext.js +24 -136
- package/bin/runners/runDoctor.js +67 -115
- package/bin/runners/runEvidencePack.js +219 -0
- package/bin/runners/runFix.js +5 -6
- package/bin/runners/runGuard.js +118 -212
- package/bin/runners/runInit.js +2 -14
- package/bin/runners/runInstall.js +281 -0
- package/bin/runners/runLabs.js +341 -0
- package/bin/runners/runMcp.js +52 -130
- package/bin/runners/runPolish.js +20 -43
- package/bin/runners/runProve.js +3 -13
- package/bin/runners/runReality.js +0 -14
- package/bin/runners/runReport.js +2 -3
- package/bin/runners/runScan.js +44 -511
- package/bin/runners/runShip.js +14 -28
- package/bin/runners/runValidate.js +2 -19
- package/bin/runners/runWatch.js +54 -118
- package/bin/vibecheck.js +41 -148
- package/mcp-server/ARCHITECTURE.md +339 -0
- package/mcp-server/__tests__/cache.test.ts +313 -0
- package/mcp-server/__tests__/executor.test.ts +239 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.cache/webpack/cache.pack +1 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.next/server/chunk.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.turbo/cache.json +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/.venv/lib/env.py +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/dist/bundle.js +3 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/package.json +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/src/app.ts +5 -0
- package/mcp-server/__tests__/fixtures/exclusion-test/venv/lib/config.py +4 -0
- package/mcp-server/__tests__/ids.test.ts +345 -0
- package/mcp-server/__tests__/integration/tools.test.ts +410 -0
- package/mcp-server/__tests__/registry.test.ts +365 -0
- package/mcp-server/__tests__/sandbox.test.ts +323 -0
- package/mcp-server/__tests__/schemas.test.ts +372 -0
- package/mcp-server/benchmarks/run-benchmarks.ts +304 -0
- package/mcp-server/examples/doctor.request.json +14 -0
- package/mcp-server/examples/doctor.response.json +53 -0
- package/mcp-server/examples/error.response.json +15 -0
- package/mcp-server/examples/scan.request.json +14 -0
- package/mcp-server/examples/scan.response.json +108 -0
- package/mcp-server/handlers/tool-handler.ts +671 -0
- package/mcp-server/index-v3.ts +293 -0
- package/mcp-server/index.js +1072 -1573
- package/mcp-server/index.old.js +4137 -0
- package/mcp-server/lib/cache.ts +341 -0
- package/mcp-server/lib/errors.ts +346 -0
- package/mcp-server/lib/executor.ts +792 -0
- package/mcp-server/lib/ids.ts +238 -0
- package/mcp-server/lib/logger.ts +368 -0
- package/mcp-server/lib/metrics.ts +365 -0
- package/mcp-server/lib/sandbox.ts +337 -0
- package/mcp-server/lib/validator.ts +229 -0
- package/mcp-server/package-lock.json +165 -0
- package/mcp-server/package.json +32 -7
- package/mcp-server/premium-tools.js +2 -2
- package/mcp-server/registry/tools.json +476 -0
- package/mcp-server/schemas/error-envelope.schema.json +125 -0
- package/mcp-server/schemas/finding.schema.json +167 -0
- package/mcp-server/schemas/report-artifact.schema.json +88 -0
- package/mcp-server/schemas/run-request.schema.json +75 -0
- package/mcp-server/schemas/verdict.schema.json +168 -0
- package/mcp-server/tier-auth.d.ts +71 -0
- package/mcp-server/tier-auth.js +371 -183
- package/mcp-server/truth-context.js +90 -131
- package/mcp-server/truth-firewall-tools.js +1000 -1611
- package/mcp-server/tsconfig.json +34 -0
- package/mcp-server/vibecheck-tools.js +2 -2
- package/mcp-server/vitest.config.ts +16 -0
- package/package.json +3 -4
- package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +0 -474
- package/bin/runners/lib/agent-firewall/change-packet/builder.js +0 -488
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +0 -228
- package/bin/runners/lib/agent-firewall/change-packet/store.js +0 -200
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +0 -21
- package/bin/runners/lib/agent-firewall/claims/extractor.js +0 -303
- package/bin/runners/lib/agent-firewall/claims/patterns.js +0 -24
- package/bin/runners/lib/agent-firewall/critic/index.js +0 -151
- package/bin/runners/lib/agent-firewall/critic/judge.js +0 -432
- package/bin/runners/lib/agent-firewall/critic/prompts.js +0 -305
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +0 -88
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +0 -75
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +0 -127
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +0 -102
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +0 -213
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +0 -145
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +0 -19
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +0 -87
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +0 -184
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +0 -163
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +0 -107
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +0 -68
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +0 -66
- package/bin/runners/lib/agent-firewall/interceptor/base.js +0 -304
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +0 -35
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +0 -34
- package/bin/runners/lib/agent-firewall/lawbook/distributor.js +0 -465
- package/bin/runners/lib/agent-firewall/lawbook/evaluator.js +0 -604
- package/bin/runners/lib/agent-firewall/lawbook/index.js +0 -304
- package/bin/runners/lib/agent-firewall/lawbook/registry.js +0 -514
- package/bin/runners/lib/agent-firewall/lawbook/schema.js +0 -420
- package/bin/runners/lib/agent-firewall/logger.js +0 -141
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +0 -90
- package/bin/runners/lib/agent-firewall/policy/engine.js +0 -103
- package/bin/runners/lib/agent-firewall/policy/loader.js +0 -451
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +0 -50
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +0 -86
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +0 -162
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +0 -189
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +0 -93
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +0 -57
- package/bin/runners/lib/agent-firewall/policy/schema.json +0 -183
- package/bin/runners/lib/agent-firewall/policy/verdict.js +0 -54
- package/bin/runners/lib/agent-firewall/proposal/extractor.js +0 -394
- package/bin/runners/lib/agent-firewall/proposal/index.js +0 -212
- package/bin/runners/lib/agent-firewall/proposal/schema.js +0 -251
- package/bin/runners/lib/agent-firewall/proposal/validator.js +0 -386
- package/bin/runners/lib/agent-firewall/reality/index.js +0 -332
- package/bin/runners/lib/agent-firewall/reality/state.js +0 -625
- package/bin/runners/lib/agent-firewall/reality/watcher.js +0 -322
- package/bin/runners/lib/agent-firewall/risk/index.js +0 -173
- package/bin/runners/lib/agent-firewall/risk/scorer.js +0 -328
- package/bin/runners/lib/agent-firewall/risk/thresholds.js +0 -321
- package/bin/runners/lib/agent-firewall/risk/vectors.js +0 -421
- package/bin/runners/lib/agent-firewall/simulator/diff-simulator.js +0 -472
- package/bin/runners/lib/agent-firewall/simulator/import-resolver.js +0 -346
- package/bin/runners/lib/agent-firewall/simulator/index.js +0 -181
- package/bin/runners/lib/agent-firewall/simulator/route-validator.js +0 -380
- package/bin/runners/lib/agent-firewall/time-machine/incident-correlator.js +0 -661
- package/bin/runners/lib/agent-firewall/time-machine/index.js +0 -267
- package/bin/runners/lib/agent-firewall/time-machine/replay-engine.js +0 -436
- package/bin/runners/lib/agent-firewall/time-machine/state-reconstructor.js +0 -490
- package/bin/runners/lib/agent-firewall/time-machine/timeline-builder.js +0 -530
- package/bin/runners/lib/agent-firewall/truthpack/index.js +0 -67
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +0 -137
- package/bin/runners/lib/agent-firewall/unblock/planner.js +0 -337
- package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +0 -118
- package/bin/runners/lib/api-client.js +0 -269
- package/bin/runners/lib/authority-badge.js +0 -425
- package/bin/runners/lib/engines/accessibility-engine.js +0 -190
- package/bin/runners/lib/engines/api-consistency-engine.js +0 -162
- package/bin/runners/lib/engines/ast-cache.js +0 -99
- package/bin/runners/lib/engines/code-quality-engine.js +0 -255
- package/bin/runners/lib/engines/console-logs-engine.js +0 -115
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +0 -268
- package/bin/runners/lib/engines/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/deprecated-api-engine.js +0 -226
- package/bin/runners/lib/engines/empty-catch-engine.js +0 -150
- package/bin/runners/lib/engines/file-filter.js +0 -131
- package/bin/runners/lib/engines/hardcoded-secrets-engine.js +0 -251
- package/bin/runners/lib/engines/mock-data-engine.js +0 -272
- package/bin/runners/lib/engines/parallel-processor.js +0 -71
- package/bin/runners/lib/engines/performance-issues-engine.js +0 -265
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +0 -243
- package/bin/runners/lib/engines/todo-fixme-engine.js +0 -115
- package/bin/runners/lib/engines/type-aware-engine.js +0 -152
- package/bin/runners/lib/engines/unsafe-regex-engine.js +0 -225
- package/bin/runners/lib/engines/vibecheck-engines/README.md +0 -53
- package/bin/runners/lib/engines/vibecheck-engines/index.js +0 -15
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +0 -291
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +0 -83
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +0 -198
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +0 -275
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +0 -167
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +0 -139
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +0 -140
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +0 -164
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +0 -234
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +0 -217
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +0 -78
- package/bin/runners/lib/engines/vibecheck-engines/package.json +0 -13
- package/bin/runners/lib/exit-codes.js +0 -275
- package/bin/runners/lib/fingerprint.js +0 -377
- package/bin/runners/lib/help-formatter.js +0 -413
- package/bin/runners/lib/logger.js +0 -38
- package/bin/runners/lib/ship-output-enterprise.js +0 -239
- package/bin/runners/lib/unified-cli-output.js +0 -604
- package/bin/runners/runAgent.d.ts +0 -5
- package/bin/runners/runAgent.js +0 -161
- package/bin/runners/runApprove.js +0 -1200
- package/bin/runners/runClassify.js +0 -859
- package/bin/runners/runContext.d.ts +0 -4
- package/bin/runners/runFirewall.d.ts +0 -5
- package/bin/runners/runFirewall.js +0 -134
- package/bin/runners/runFirewallHook.d.ts +0 -5
- package/bin/runners/runFirewallHook.js +0 -56
- package/bin/runners/runPolish.d.ts +0 -4
- package/bin/runners/runProof.zip +0 -0
- package/bin/runners/runTruth.d.ts +0 -5
- package/bin/runners/runTruth.js +0 -101
- package/mcp-server/HARDENING_SUMMARY.md +0 -299
- package/mcp-server/agent-firewall-interceptor.js +0 -500
- package/mcp-server/authority-tools.js +0 -569
- 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/lib/api-client.cjs +0 -13
- package/mcp-server/lib/logger.cjs +0 -30
- package/mcp-server/logger.js +0 -173
- package/mcp-server/tools-v3.js +0 -706
- package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Registry Tests
|
|
3
|
+
*
|
|
4
|
+
* Ensures the registry is valid and consistent.
|
|
5
|
+
*
|
|
6
|
+
* CRITICAL: CI must fail if:
|
|
7
|
+
* - Tool count exceeds 20 without VIBECHECK_ALLOW_TOOL_GROWTH=1
|
|
8
|
+
* - A tool is added without CLI mapping (unless helper)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
12
|
+
import { readFileSync, existsSync } from 'fs';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
// Maximum allowed tools without explicit override
|
|
19
|
+
const MAX_TOOLS = 20;
|
|
20
|
+
const ALLOW_TOOL_GROWTH = process.env.VIBECHECK_ALLOW_TOOL_GROWTH === '1';
|
|
21
|
+
|
|
22
|
+
interface Tool {
|
|
23
|
+
name: string;
|
|
24
|
+
cli: string | null;
|
|
25
|
+
tier: string;
|
|
26
|
+
description: string;
|
|
27
|
+
category: string;
|
|
28
|
+
cacheable?: boolean;
|
|
29
|
+
cacheMaxAge?: number;
|
|
30
|
+
timeout?: number;
|
|
31
|
+
inputSchema: Record<string, unknown>;
|
|
32
|
+
outputSchema?: Record<string, unknown>;
|
|
33
|
+
tierGated?: Record<string, string>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Registry {
|
|
37
|
+
version: string;
|
|
38
|
+
tools: Tool[];
|
|
39
|
+
aliases: Record<string, string>;
|
|
40
|
+
deprecated: Array<{ name: string; reason: string; replacement: string | null; removeIn: string }>;
|
|
41
|
+
categories: Record<string, { description: string; order: number }>;
|
|
42
|
+
tiers: Record<string, { name: string; order: number; price?: number }>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('Tool Registry', () => {
|
|
46
|
+
let registry: Registry;
|
|
47
|
+
|
|
48
|
+
beforeAll(() => {
|
|
49
|
+
const registryPath = join(__dirname, '..', 'registry', 'tools.json');
|
|
50
|
+
registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('Registry Structure', () => {
|
|
54
|
+
it('should have a version', () => {
|
|
55
|
+
expect(registry.version).toBeDefined();
|
|
56
|
+
expect(registry.version).toMatch(/^\d+\.\d+\.\d+$/);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should have tools array', () => {
|
|
60
|
+
expect(Array.isArray(registry.tools)).toBe(true);
|
|
61
|
+
expect(registry.tools.length).toBeGreaterThan(0);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should have aliases object', () => {
|
|
65
|
+
expect(typeof registry.aliases).toBe('object');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should have categories', () => {
|
|
69
|
+
expect(typeof registry.categories).toBe('object');
|
|
70
|
+
expect(Object.keys(registry.categories).length).toBeGreaterThan(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should have tiers', () => {
|
|
74
|
+
expect(typeof registry.tiers).toBe('object');
|
|
75
|
+
expect(registry.tiers.free).toBeDefined();
|
|
76
|
+
expect(registry.tiers.starter).toBeDefined();
|
|
77
|
+
expect(registry.tiers.pro).toBeDefined();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Tool Definitions', () => {
|
|
82
|
+
it('all tools should have required fields', () => {
|
|
83
|
+
for (const tool of registry.tools) {
|
|
84
|
+
expect(tool.name).toBeDefined();
|
|
85
|
+
expect(tool.name).toMatch(/^vibecheck\.[a-z_]+$/);
|
|
86
|
+
expect(tool.tier).toBeDefined();
|
|
87
|
+
expect(tool.description).toBeDefined();
|
|
88
|
+
expect(tool.description.length).toBeGreaterThan(10);
|
|
89
|
+
expect(tool.category).toBeDefined();
|
|
90
|
+
expect(tool.inputSchema).toBeDefined();
|
|
91
|
+
expect(typeof tool.inputSchema).toBe('object');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('all tools should have valid tiers', () => {
|
|
96
|
+
const validTiers = Object.keys(registry.tiers);
|
|
97
|
+
|
|
98
|
+
for (const tool of registry.tools) {
|
|
99
|
+
expect(validTiers).toContain(tool.tier);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('all tools should have valid categories', () => {
|
|
104
|
+
const validCategories = Object.keys(registry.categories);
|
|
105
|
+
|
|
106
|
+
for (const tool of registry.tools) {
|
|
107
|
+
expect(validCategories).toContain(tool.category);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('tool names should be unique', () => {
|
|
112
|
+
const names = registry.tools.map(t => t.name);
|
|
113
|
+
const uniqueNames = new Set(names);
|
|
114
|
+
expect(names.length).toBe(uniqueNames.size);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('cacheable tools should have cacheMaxAge', () => {
|
|
118
|
+
for (const tool of registry.tools) {
|
|
119
|
+
if (tool.cacheable) {
|
|
120
|
+
expect(tool.cacheMaxAge).toBeDefined();
|
|
121
|
+
expect(tool.cacheMaxAge).toBeGreaterThan(0);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('all tools should have reasonable timeouts', () => {
|
|
127
|
+
for (const tool of registry.tools) {
|
|
128
|
+
if (tool.timeout !== undefined) {
|
|
129
|
+
expect(tool.timeout).toBeGreaterThanOrEqual(1000);
|
|
130
|
+
expect(tool.timeout).toBeLessThanOrEqual(600000);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('CLI Mapping', () => {
|
|
137
|
+
it('CLI-backed tools should have valid cli field', () => {
|
|
138
|
+
const cliTools = registry.tools.filter(t => t.cli !== null);
|
|
139
|
+
|
|
140
|
+
for (const tool of cliTools) {
|
|
141
|
+
expect(typeof tool.cli).toBe('string');
|
|
142
|
+
expect(tool.cli!.length).toBeGreaterThan(0);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('query tools should have null cli', () => {
|
|
147
|
+
const queryTools = registry.tools.filter(t => t.category === 'query');
|
|
148
|
+
|
|
149
|
+
for (const tool of queryTools) {
|
|
150
|
+
expect(tool.cli).toBeNull();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('Aliases', () => {
|
|
156
|
+
it('all alias targets should exist', () => {
|
|
157
|
+
const toolNames = new Set(registry.tools.map(t => t.name));
|
|
158
|
+
|
|
159
|
+
for (const [alias, target] of Object.entries(registry.aliases)) {
|
|
160
|
+
expect(toolNames.has(target)).toBe(true);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('aliases should not conflict with tool names', () => {
|
|
165
|
+
const toolNames = new Set(registry.tools.map(t => t.name));
|
|
166
|
+
|
|
167
|
+
for (const alias of Object.keys(registry.aliases)) {
|
|
168
|
+
expect(toolNames.has(alias)).toBe(false);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe('Deprecated Tools', () => {
|
|
174
|
+
it('deprecated tools should have required fields', () => {
|
|
175
|
+
for (const dep of registry.deprecated) {
|
|
176
|
+
expect(dep.name).toBeDefined();
|
|
177
|
+
expect(dep.reason).toBeDefined();
|
|
178
|
+
expect(dep.removeIn).toBeDefined();
|
|
179
|
+
expect(dep.removeIn).toMatch(/^\d+\.\d+\.\d+$/);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('replacement tools should exist if specified', () => {
|
|
184
|
+
const toolNames = new Set(registry.tools.map(t => t.name));
|
|
185
|
+
|
|
186
|
+
for (const dep of registry.deprecated) {
|
|
187
|
+
if (dep.replacement !== null) {
|
|
188
|
+
expect(toolNames.has(dep.replacement)).toBe(true);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('Input Schemas', () => {
|
|
195
|
+
it('all input schemas should be valid JSON Schema', () => {
|
|
196
|
+
for (const tool of registry.tools) {
|
|
197
|
+
expect(tool.inputSchema.type).toBe('object');
|
|
198
|
+
expect(tool.inputSchema.properties).toBeDefined();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('projectPath should have default value', () => {
|
|
203
|
+
for (const tool of registry.tools) {
|
|
204
|
+
const props = tool.inputSchema.properties as Record<string, { default?: string }>;
|
|
205
|
+
if (props.projectPath) {
|
|
206
|
+
expect(props.projectPath.default).toBe('.');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('required fields should be in properties', () => {
|
|
212
|
+
for (const tool of registry.tools) {
|
|
213
|
+
const required = tool.inputSchema.required as string[] | undefined;
|
|
214
|
+
const props = tool.inputSchema.properties as Record<string, unknown>;
|
|
215
|
+
|
|
216
|
+
if (required) {
|
|
217
|
+
for (const field of required) {
|
|
218
|
+
expect(props[field]).toBeDefined();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('Tier Gating', () => {
|
|
226
|
+
it('tierGated options should reference valid tiers', () => {
|
|
227
|
+
const validTiers = Object.keys(registry.tiers);
|
|
228
|
+
|
|
229
|
+
for (const tool of registry.tools) {
|
|
230
|
+
if (tool.tierGated) {
|
|
231
|
+
for (const tier of Object.values(tool.tierGated)) {
|
|
232
|
+
expect(validTiers).toContain(tier);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('tierGated options should be in input schema', () => {
|
|
239
|
+
for (const tool of registry.tools) {
|
|
240
|
+
if (tool.tierGated) {
|
|
241
|
+
const props = tool.inputSchema.properties as Record<string, unknown>;
|
|
242
|
+
for (const option of Object.keys(tool.tierGated)) {
|
|
243
|
+
expect(props[option]).toBeDefined();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('Core Tools Present', () => {
|
|
251
|
+
const coreTools = [
|
|
252
|
+
'vibecheck.doctor',
|
|
253
|
+
'vibecheck.init',
|
|
254
|
+
'vibecheck.scan',
|
|
255
|
+
'vibecheck.ship',
|
|
256
|
+
'vibecheck.prove',
|
|
257
|
+
'vibecheck.reality',
|
|
258
|
+
'vibecheck.fix',
|
|
259
|
+
'vibecheck.guard',
|
|
260
|
+
'vibecheck.ctx',
|
|
261
|
+
'vibecheck.report',
|
|
262
|
+
'vibecheck.status',
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
for (const toolName of coreTools) {
|
|
266
|
+
it(`should have ${toolName}`, () => {
|
|
267
|
+
const tool = registry.tools.find(t => t.name === toolName);
|
|
268
|
+
expect(tool).toBeDefined();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe('Category Order', () => {
|
|
274
|
+
it('categories should have sequential order', () => {
|
|
275
|
+
const orders = Object.values(registry.categories).map(c => c.order);
|
|
276
|
+
const sorted = [...orders].sort((a, b) => a - b);
|
|
277
|
+
|
|
278
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
279
|
+
expect(sorted[i]).toBe(i + 1);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('Tier Order', () => {
|
|
285
|
+
it('tiers should have sequential order', () => {
|
|
286
|
+
const tierEntries = Object.entries(registry.tiers).sort((a, b) => a[1].order - b[1].order);
|
|
287
|
+
|
|
288
|
+
expect(tierEntries[0][0]).toBe('free');
|
|
289
|
+
expect(tierEntries[0][1].order).toBe(0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('paid tiers should have prices', () => {
|
|
293
|
+
expect(registry.tiers.starter.price).toBeDefined();
|
|
294
|
+
expect(registry.tiers.pro.price).toBeDefined();
|
|
295
|
+
expect(registry.tiers.starter.price).toBeLessThan(registry.tiers.pro.price!);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('Tool Count Enforcement', () => {
|
|
300
|
+
it(`should not exceed ${MAX_TOOLS} tools without override`, () => {
|
|
301
|
+
const toolCount = registry.tools.length;
|
|
302
|
+
|
|
303
|
+
if (!ALLOW_TOOL_GROWTH) {
|
|
304
|
+
expect(toolCount).toBeLessThanOrEqual(MAX_TOOLS);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Always log warning if approaching limit
|
|
308
|
+
if (toolCount >= MAX_TOOLS - 2) {
|
|
309
|
+
console.warn(`Warning: Tool count (${toolCount}) is approaching limit (${MAX_TOOLS})`);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('all CLI-backed tools should have valid CLI mapping', () => {
|
|
314
|
+
const helperCategories = ['query', 'utility'];
|
|
315
|
+
|
|
316
|
+
for (const tool of registry.tools) {
|
|
317
|
+
// Helper tools are allowed to have null CLI
|
|
318
|
+
const isHelper = helperCategories.includes(tool.category) && tool.cli === null;
|
|
319
|
+
|
|
320
|
+
if (!isHelper) {
|
|
321
|
+
// Non-helper tools must have CLI mapping or be explicitly a helper
|
|
322
|
+
if (tool.cli === null) {
|
|
323
|
+
// This is a query/utility helper - allowed
|
|
324
|
+
expect(tool.description.toLowerCase()).toMatch(/artifact|query|list|get|search/);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('should not add tools without CLI mapping (unless helper)', () => {
|
|
331
|
+
const toolsWithoutCli = registry.tools.filter(t => t.cli === null);
|
|
332
|
+
const allowedHelpers = [
|
|
333
|
+
'vibecheck.get_truthpack',
|
|
334
|
+
'vibecheck.validate_claim',
|
|
335
|
+
'vibecheck.search_evidence',
|
|
336
|
+
'vibecheck.list_reports',
|
|
337
|
+
'vibecheck.get_last_verdict',
|
|
338
|
+
'vibecheck.get_finding',
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
for (const tool of toolsWithoutCli) {
|
|
342
|
+
expect(allowedHelpers).toContain(tool.name);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
describe('Deprecation Handling', () => {
|
|
348
|
+
it('deprecated tools should have valid replacement or null', () => {
|
|
349
|
+
const toolNames = new Set(registry.tools.map(t => t.name));
|
|
350
|
+
|
|
351
|
+
for (const dep of registry.deprecated) {
|
|
352
|
+
if (dep.replacement) {
|
|
353
|
+
expect(toolNames.has(dep.replacement)).toBe(true);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('aliases should warn about deprecation', () => {
|
|
359
|
+
// All aliases should have a target that exists
|
|
360
|
+
for (const [alias, target] of Object.entries(registry.aliases)) {
|
|
361
|
+
expect(registry.tools.some(t => t.name === target)).toBe(true);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Security Tests
|
|
3
|
+
*
|
|
4
|
+
* Ensures path sandboxing is secure and prevents:
|
|
5
|
+
* - Path traversal attacks (../)
|
|
6
|
+
* - Absolute path escape
|
|
7
|
+
* - Symlink escape
|
|
8
|
+
* - Access to sensitive files
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
12
|
+
import { mkdirSync, writeFileSync, symlinkSync, rmSync, existsSync } from 'fs';
|
|
13
|
+
import { join, resolve } from 'path';
|
|
14
|
+
import { tmpdir } from 'os';
|
|
15
|
+
import { createSandbox, redactPath, redactSensitive } from '../lib/sandbox.js';
|
|
16
|
+
|
|
17
|
+
describe('Path Sandboxing', () => {
|
|
18
|
+
let testDir: string;
|
|
19
|
+
let workspace: string;
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
testDir = join(tmpdir(), `vibecheck-sandbox-test-${Date.now()}`);
|
|
23
|
+
workspace = join(testDir, 'workspace');
|
|
24
|
+
mkdirSync(workspace, { recursive: true });
|
|
25
|
+
mkdirSync(join(workspace, 'src'));
|
|
26
|
+
mkdirSync(join(workspace, 'node_modules', 'pkg'), { recursive: true });
|
|
27
|
+
mkdirSync(join(workspace, 'venv', 'lib'), { recursive: true });
|
|
28
|
+
|
|
29
|
+
writeFileSync(join(workspace, 'src', 'app.ts'), 'export const app = 1;');
|
|
30
|
+
writeFileSync(join(workspace, 'node_modules', 'pkg', 'index.js'), 'module.exports = {}');
|
|
31
|
+
|
|
32
|
+
// Create a directory outside workspace for escape tests
|
|
33
|
+
mkdirSync(join(testDir, 'outside'));
|
|
34
|
+
writeFileSync(join(testDir, 'outside', 'secret.txt'), 'secret');
|
|
35
|
+
|
|
36
|
+
// Create a symlink pointing outside (if platform supports)
|
|
37
|
+
try {
|
|
38
|
+
symlinkSync(join(testDir, 'outside'), join(workspace, 'escape-link'));
|
|
39
|
+
} catch {
|
|
40
|
+
// Symlinks may not be supported on all platforms
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterAll(() => {
|
|
45
|
+
if (existsSync(testDir)) {
|
|
46
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('validate()', () => {
|
|
51
|
+
it('should allow paths within workspace', () => {
|
|
52
|
+
const sandbox = createSandbox({ workspace });
|
|
53
|
+
|
|
54
|
+
const result = sandbox.validate(join(workspace, 'src', 'app.ts'));
|
|
55
|
+
expect(result.allowed).toBe(true);
|
|
56
|
+
expect(result.relativePath).toBe(join('src', 'app.ts'));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should allow relative paths', () => {
|
|
60
|
+
const sandbox = createSandbox({ workspace });
|
|
61
|
+
|
|
62
|
+
const result = sandbox.validate('src/app.ts');
|
|
63
|
+
expect(result.allowed).toBe(true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should block path traversal with ../', () => {
|
|
67
|
+
const sandbox = createSandbox({ workspace });
|
|
68
|
+
|
|
69
|
+
const result = sandbox.validate('../outside/secret.txt');
|
|
70
|
+
expect(result.allowed).toBe(false);
|
|
71
|
+
expect(result.reason).toMatch(/traversal|outside/i);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should block path traversal with multiple ../', () => {
|
|
75
|
+
const sandbox = createSandbox({ workspace });
|
|
76
|
+
|
|
77
|
+
const result = sandbox.validate('src/../../outside/secret.txt');
|
|
78
|
+
expect(result.allowed).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should block absolute paths outside workspace', () => {
|
|
82
|
+
const sandbox = createSandbox({ workspace });
|
|
83
|
+
|
|
84
|
+
const result = sandbox.validate(join(testDir, 'outside', 'secret.txt'));
|
|
85
|
+
expect(result.allowed).toBe(false);
|
|
86
|
+
expect(result.reason).toMatch(/outside|sandbox/i);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should block access to /etc/passwd (Unix)', () => {
|
|
90
|
+
const sandbox = createSandbox({ workspace });
|
|
91
|
+
|
|
92
|
+
const result = sandbox.validate('/etc/passwd');
|
|
93
|
+
expect(result.allowed).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should block access to C:\\Windows (Windows)', () => {
|
|
97
|
+
const sandbox = createSandbox({ workspace });
|
|
98
|
+
|
|
99
|
+
const result = sandbox.validate('C:\\Windows\\System32\\config');
|
|
100
|
+
expect(result.allowed).toBe(false);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should block access to .ssh directory', () => {
|
|
104
|
+
const sandbox = createSandbox({ workspace });
|
|
105
|
+
|
|
106
|
+
const result = sandbox.validate('/home/user/.ssh/id_rsa');
|
|
107
|
+
expect(result.allowed).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should block access to .aws directory', () => {
|
|
111
|
+
const sandbox = createSandbox({ workspace });
|
|
112
|
+
|
|
113
|
+
const result = sandbox.validate('/home/user/.aws/credentials');
|
|
114
|
+
expect(result.allowed).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle path depth limit', () => {
|
|
118
|
+
const sandbox = createSandbox({ workspace, maxDepth: 5 });
|
|
119
|
+
|
|
120
|
+
// Deep path
|
|
121
|
+
const deepPath = 'a/b/c/d/e/f/g/h/i/j/file.ts';
|
|
122
|
+
const result = sandbox.validate(deepPath);
|
|
123
|
+
expect(result.allowed).toBe(false);
|
|
124
|
+
expect(result.reason).toMatch(/depth/i);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('shouldExclude()', () => {
|
|
129
|
+
it('should exclude node_modules by default', () => {
|
|
130
|
+
const sandbox = createSandbox({ workspace });
|
|
131
|
+
|
|
132
|
+
expect(sandbox.shouldExclude('node_modules/pkg/index.js')).toBe(true);
|
|
133
|
+
expect(sandbox.shouldExclude(join(workspace, 'node_modules', 'pkg', 'index.js'))).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('should exclude venv by default', () => {
|
|
137
|
+
const sandbox = createSandbox({ workspace });
|
|
138
|
+
|
|
139
|
+
expect(sandbox.shouldExclude('venv/lib/config.py')).toBe(true);
|
|
140
|
+
expect(sandbox.shouldExclude('.venv/lib/config.py')).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should exclude .git by default', () => {
|
|
144
|
+
const sandbox = createSandbox({ workspace });
|
|
145
|
+
|
|
146
|
+
expect(sandbox.shouldExclude('.git/config')).toBe(true);
|
|
147
|
+
expect(sandbox.shouldExclude('.git/objects/abc')).toBe(true);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should exclude dist/build/out by default', () => {
|
|
151
|
+
const sandbox = createSandbox({ workspace });
|
|
152
|
+
|
|
153
|
+
expect(sandbox.shouldExclude('dist/bundle.js')).toBe(true);
|
|
154
|
+
expect(sandbox.shouldExclude('build/output.js')).toBe(true);
|
|
155
|
+
expect(sandbox.shouldExclude('out/compiled.js')).toBe(true);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should exclude .next by default', () => {
|
|
159
|
+
const sandbox = createSandbox({ workspace });
|
|
160
|
+
|
|
161
|
+
expect(sandbox.shouldExclude('.next/server/app.js')).toBe(true);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should exclude coverage by default', () => {
|
|
165
|
+
const sandbox = createSandbox({ workspace });
|
|
166
|
+
|
|
167
|
+
expect(sandbox.shouldExclude('coverage/lcov.info')).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should exclude .cache and .turbo by default', () => {
|
|
171
|
+
const sandbox = createSandbox({ workspace });
|
|
172
|
+
|
|
173
|
+
expect(sandbox.shouldExclude('.cache/webpack/chunk.js')).toBe(true);
|
|
174
|
+
expect(sandbox.shouldExclude('.turbo/cache.json')).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should NOT exclude src files', () => {
|
|
178
|
+
const sandbox = createSandbox({ workspace });
|
|
179
|
+
|
|
180
|
+
expect(sandbox.shouldExclude('src/app.ts')).toBe(false);
|
|
181
|
+
expect(sandbox.shouldExclude('lib/utils.ts')).toBe(false);
|
|
182
|
+
expect(sandbox.shouldExclude('app/page.tsx')).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should apply custom exclusion patterns', () => {
|
|
186
|
+
const sandbox = createSandbox({
|
|
187
|
+
workspace,
|
|
188
|
+
excludePatterns: [/custom_exclude\//],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
expect(sandbox.shouldExclude('custom_exclude/file.ts')).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('requireValid()', () => {
|
|
196
|
+
it('should return normalized path for valid paths', () => {
|
|
197
|
+
const sandbox = createSandbox({ workspace });
|
|
198
|
+
|
|
199
|
+
const result = sandbox.requireValid('src/app.ts');
|
|
200
|
+
expect(result).toBe(resolve(workspace, 'src/app.ts'));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should throw for invalid paths', () => {
|
|
204
|
+
const sandbox = createSandbox({ workspace });
|
|
205
|
+
|
|
206
|
+
expect(() => sandbox.requireValid('../outside/secret.txt')).toThrow();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('exists()', () => {
|
|
211
|
+
it('should return true for existing files in sandbox', () => {
|
|
212
|
+
const sandbox = createSandbox({ workspace });
|
|
213
|
+
|
|
214
|
+
expect(sandbox.exists('src/app.ts')).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('should return false for non-existent files', () => {
|
|
218
|
+
const sandbox = createSandbox({ workspace });
|
|
219
|
+
|
|
220
|
+
expect(sandbox.exists('src/nonexistent.ts')).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('should return false for paths outside sandbox', () => {
|
|
224
|
+
const sandbox = createSandbox({ workspace });
|
|
225
|
+
|
|
226
|
+
// Even if file exists outside, should return false
|
|
227
|
+
expect(sandbox.exists('../outside/secret.txt')).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('path conversion', () => {
|
|
232
|
+
it('toRelative should convert absolute to relative', () => {
|
|
233
|
+
const sandbox = createSandbox({ workspace });
|
|
234
|
+
|
|
235
|
+
const relative = sandbox.toRelative(join(workspace, 'src', 'app.ts'));
|
|
236
|
+
expect(relative).toBe(join('src', 'app.ts'));
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('toAbsolute should convert relative to absolute', () => {
|
|
240
|
+
const sandbox = createSandbox({ workspace });
|
|
241
|
+
|
|
242
|
+
const absolute = sandbox.toAbsolute('src/app.ts');
|
|
243
|
+
expect(absolute).toBe(resolve(workspace, 'src/app.ts'));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('getWorkspace should return workspace root', () => {
|
|
247
|
+
const sandbox = createSandbox({ workspace });
|
|
248
|
+
|
|
249
|
+
expect(sandbox.getWorkspace()).toBe(resolve(workspace));
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe('Path Redaction', () => {
|
|
255
|
+
it('should redact paths outside workspace', () => {
|
|
256
|
+
const result = redactPath('/etc/passwd', '/home/user/project');
|
|
257
|
+
expect(result).toBe('[REDACTED]');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('should return relative path for paths within workspace', () => {
|
|
261
|
+
const result = redactPath('/home/user/project/src/app.ts', '/home/user/project');
|
|
262
|
+
expect(result).toBe('src/app.ts');
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe('Sensitive Data Redaction', () => {
|
|
267
|
+
it('should redact password fields', () => {
|
|
268
|
+
const obj = { username: 'admin', password: 'secret123' };
|
|
269
|
+
const result = redactSensitive(obj);
|
|
270
|
+
expect(result.username).toBe('admin');
|
|
271
|
+
expect(result.password).toBe('[REDACTED]');
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it('should redact apiKey fields', () => {
|
|
275
|
+
const obj = { name: 'test', apiKey: 'sk-xxx-yyy' };
|
|
276
|
+
const result = redactSensitive(obj);
|
|
277
|
+
expect(result.name).toBe('test');
|
|
278
|
+
expect(result.apiKey).toBe('[REDACTED]');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('should redact nested sensitive fields', () => {
|
|
282
|
+
const obj = {
|
|
283
|
+
config: {
|
|
284
|
+
database: {
|
|
285
|
+
password: 'dbpass',
|
|
286
|
+
host: 'localhost',
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
const result = redactSensitive(obj) as { config: { database: { password: string; host: string } } };
|
|
291
|
+
expect(result.config.database.host).toBe('localhost');
|
|
292
|
+
expect(result.config.database.password).toBe('[REDACTED]');
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should redact arrays with sensitive objects', () => {
|
|
296
|
+
const obj = {
|
|
297
|
+
users: [
|
|
298
|
+
{ name: 'alice', secret: 'alice-secret' },
|
|
299
|
+
{ name: 'bob', secret: 'bob-secret' },
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
const result = redactSensitive(obj) as { users: Array<{ name: string; secret: string }> };
|
|
303
|
+
expect(result.users[0].name).toBe('alice');
|
|
304
|
+
expect(result.users[0].secret).toBe('[REDACTED]');
|
|
305
|
+
expect(result.users[1].secret).toBe('[REDACTED]');
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should handle various sensitive key patterns', () => {
|
|
309
|
+
const obj = {
|
|
310
|
+
auth_token: 'token',
|
|
311
|
+
credential: 'cred',
|
|
312
|
+
private_key: 'key',
|
|
313
|
+
session_cookie: 'cookie',
|
|
314
|
+
safe_value: 'value',
|
|
315
|
+
};
|
|
316
|
+
const result = redactSensitive(obj);
|
|
317
|
+
expect(result.auth_token).toBe('[REDACTED]');
|
|
318
|
+
expect(result.credential).toBe('[REDACTED]');
|
|
319
|
+
expect(result.private_key).toBe('[REDACTED]');
|
|
320
|
+
expect(result.session_cookie).toBe('[REDACTED]');
|
|
321
|
+
expect(result.safe_value).toBe('value');
|
|
322
|
+
});
|
|
323
|
+
});
|