feat: remove proxyMode — all traffic goes through mcplocal proxy
proxyMode "direct" was a security hole (leaked secrets as plaintext env vars in .mcp.json) and bypassed all mcplocal features (gating, audit, RBAC, content pipeline, namespacing). Removed from schema, API, CLI, and all tests. Old configs with proxyMode are accepted but silently stripped via Zod .transform() for backward compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -332,7 +332,6 @@ rbacBindings:
|
||||
projects:
|
||||
- name: smart-home
|
||||
description: Home automation
|
||||
proxyMode: filtered
|
||||
llmProvider: gemini-cli
|
||||
llmModel: gemini-2.0-flash
|
||||
servers:
|
||||
@@ -345,7 +344,6 @@ projects:
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
||||
name: 'smart-home',
|
||||
proxyMode: 'filtered',
|
||||
llmProvider: 'gemini-cli',
|
||||
llmModel: 'gemini-2.0-flash',
|
||||
servers: ['my-grafana', 'my-ha'],
|
||||
|
||||
@@ -175,7 +175,6 @@ describe('create command', () => {
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', {
|
||||
name: 'my-project',
|
||||
description: 'A test project',
|
||||
proxyMode: 'direct',
|
||||
});
|
||||
expect(output.join('\n')).toContain("project 'test' created");
|
||||
});
|
||||
@@ -186,7 +185,6 @@ describe('create command', () => {
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', {
|
||||
name: 'minimal',
|
||||
description: '',
|
||||
proxyMode: 'direct',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,7 +193,7 @@ describe('create command', () => {
|
||||
vi.mocked(client.get).mockResolvedValueOnce([{ id: 'proj-1', name: 'my-proj' }] as never);
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['project', 'my-proj', '-d', 'updated', '--force'], { from: 'user' });
|
||||
expect(client.put).toHaveBeenCalledWith('/api/v1/projects/proj-1', { description: 'updated', proxyMode: 'direct' });
|
||||
expect(client.put).toHaveBeenCalledWith('/api/v1/projects/proj-1', { description: 'updated' });
|
||||
expect(output.join('\n')).toContain("project 'my-proj' updated");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,7 +96,6 @@ describe('describe command', () => {
|
||||
description: 'A gated project',
|
||||
ownerId: 'user-1',
|
||||
proxyModel: 'default',
|
||||
proxyMode: 'direct',
|
||||
createdAt: '2025-01-01',
|
||||
});
|
||||
const cmd = createDescribeCommand(deps);
|
||||
|
||||
@@ -179,7 +179,6 @@ describe('get command', () => {
|
||||
id: 'proj-1',
|
||||
name: 'smart-home',
|
||||
description: 'Home automation',
|
||||
proxyMode: 'filtered',
|
||||
ownerId: 'usr-1',
|
||||
servers: [{ server: { name: 'grafana' } }],
|
||||
}]);
|
||||
@@ -187,10 +186,8 @@ describe('get command', () => {
|
||||
await cmd.parseAsync(['node', 'test', 'projects']);
|
||||
|
||||
const text = deps.output.join('\n');
|
||||
expect(text).toContain('MODE');
|
||||
expect(text).toContain('SERVERS');
|
||||
expect(text).toContain('smart-home');
|
||||
expect(text).toContain('filtered');
|
||||
expect(text).toContain('1');
|
||||
});
|
||||
|
||||
@@ -341,7 +338,6 @@ describe('get command', () => {
|
||||
id: 'proj-1',
|
||||
name: 'home',
|
||||
description: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
ownerId: 'usr-1',
|
||||
@@ -362,7 +358,6 @@ describe('get command', () => {
|
||||
id: 'proj-1',
|
||||
name: 'home',
|
||||
description: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: true,
|
||||
ownerId: 'usr-1',
|
||||
@@ -381,7 +376,6 @@ describe('get command', () => {
|
||||
id: 'proj-1',
|
||||
name: 'tools',
|
||||
description: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: '',
|
||||
gated: false,
|
||||
ownerId: 'usr-1',
|
||||
@@ -400,7 +394,6 @@ describe('get command', () => {
|
||||
id: 'proj-1',
|
||||
name: 'custom',
|
||||
description: '',
|
||||
proxyMode: 'direct',
|
||||
proxyModel: 'gate',
|
||||
gated: true,
|
||||
ownerId: 'usr-1',
|
||||
|
||||
@@ -24,12 +24,11 @@ describe('project with new fields', () => {
|
||||
});
|
||||
|
||||
describe('create project with enhanced options', () => {
|
||||
it('creates project with proxy mode and servers', async () => {
|
||||
it('creates project with servers', async () => {
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync([
|
||||
'project', 'smart-home',
|
||||
'-d', 'Smart home project',
|
||||
'--proxy-mode', 'filtered',
|
||||
'--server', 'my-grafana',
|
||||
'--server', 'my-ha',
|
||||
], { from: 'user' });
|
||||
@@ -37,30 +36,19 @@ describe('project with new fields', () => {
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
||||
name: 'smart-home',
|
||||
description: 'Smart home project',
|
||||
proxyMode: 'filtered',
|
||||
servers: ['my-grafana', 'my-ha'],
|
||||
}));
|
||||
});
|
||||
|
||||
it('defaults proxy mode to direct', async () => {
|
||||
const cmd = createCreateCommand({ client, log });
|
||||
await cmd.parseAsync(['project', 'basic'], { from: 'user' });
|
||||
|
||||
expect(client.post).toHaveBeenCalledWith('/api/v1/projects', expect.objectContaining({
|
||||
proxyMode: 'direct',
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
describe('get projects shows new columns', () => {
|
||||
it('shows MODE and SERVERS columns', async () => {
|
||||
it('shows SERVERS column', async () => {
|
||||
const deps = {
|
||||
output: [] as string[],
|
||||
fetchResource: vi.fn(async () => [{
|
||||
id: 'proj-1',
|
||||
name: 'smart-home',
|
||||
description: 'Test',
|
||||
proxyMode: 'filtered',
|
||||
ownerId: 'user-1',
|
||||
servers: [{ server: { name: 'grafana' } }, { server: { name: 'ha' } }],
|
||||
}]),
|
||||
@@ -70,14 +58,13 @@ describe('project with new fields', () => {
|
||||
await cmd.parseAsync(['node', 'test', 'projects']);
|
||||
|
||||
const text = deps.output.join('\n');
|
||||
expect(text).toContain('MODE');
|
||||
expect(text).toContain('SERVERS');
|
||||
expect(text).toContain('smart-home');
|
||||
});
|
||||
});
|
||||
|
||||
describe('describe project shows full detail', () => {
|
||||
it('shows servers and proxy config', async () => {
|
||||
it('shows servers and LLM config', async () => {
|
||||
const deps = {
|
||||
output: [] as string[],
|
||||
client: mockClient(),
|
||||
@@ -85,7 +72,6 @@ describe('project with new fields', () => {
|
||||
id: 'proj-1',
|
||||
name: 'smart-home',
|
||||
description: 'Smart home',
|
||||
proxyMode: 'filtered',
|
||||
llmProvider: 'gemini-cli',
|
||||
llmModel: 'gemini-2.0-flash',
|
||||
ownerId: 'user-1',
|
||||
@@ -103,7 +89,6 @@ describe('project with new fields', () => {
|
||||
|
||||
const text = deps.output.join('\n');
|
||||
expect(text).toContain('=== Project: smart-home ===');
|
||||
expect(text).toContain('filtered');
|
||||
expect(text).toContain('gemini-cli');
|
||||
expect(text).toContain('my-grafana');
|
||||
expect(text).toContain('my-ha');
|
||||
|
||||
Reference in New Issue
Block a user