diff --git a/README.md b/README.md index 608b9aa..61fe57e 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ PolicyMesh is a free OSS CLI and GitHub Action that audits a repository for cont - `.mcp.json` - `.cursor/mcp.json` - `.vscode/mcp.json` +- `.codeium/mcp_config.json` - `.codeium/windsurf/mcp_config.json` - Codex MCP tables in `.codex/config.toml` - `.claude/settings.json` @@ -62,7 +63,7 @@ node dist/index.js audit --repo test/fixtures/conflicted --format markdown The local fixture extends that proof with: - The same `github` MCP server with different launch commands in `.mcp.json` and `.cursor/mcp.json`. -- VS Code and Codeium/Windsurf MCP configs participating in the same cross-surface mismatch. +- VS Code and Windsurf MCP configs participating in the same cross-surface mismatch. - A Codex MCP table in `.codex/config.toml` participating in the same cross-surface mismatch. - An unpinned `@latest` MCP package in Cursor config. - Broad Claude allow rules with a narrow `.env` deny and no `PreToolUse` hook. @@ -103,7 +104,7 @@ jobs: steps: - uses: actions/checkout@v6 - - uses: Conalh/PolicyMesh@v0.1.17 + - uses: Conalh/PolicyMesh@v0.1.18 with: fail-on: none ``` @@ -132,6 +133,7 @@ PolicyMesh v0 detects: - MCP server enabled/disabled drift across surfaces. - MCP server environment drift across surfaces without reporting secret values. - MCP remote header drift across surfaces without reporting secret values. +- Codeium MCP servers from `.codeium/mcp_config.json` and Windsurf MCP servers from `.codeium/windsurf/mcp_config.json` in the same MCP mismatch, missing-server, enabled-state, env, and header checks. - Codex MCP servers from `.codex/config.toml` in the same MCP mismatch, missing-server, enabled-state, env, and header checks. - Unpinned MCP launch commands such as `@latest`. - Claude broad allow rules overlapping with specific deny rules. diff --git a/dist/mesh/engine.js b/dist/mesh/engine.js index 298695e..d41de9e 100644 --- a/dist/mesh/engine.js +++ b/dist/mesh/engine.js @@ -547,7 +547,8 @@ function surfaceLabel(surface) { root_mcp: 'Root MCP', cursor_mcp: 'Cursor MCP', vscode_mcp: 'VS Code MCP', - windsurf_mcp: 'Codeium/Windsurf MCP', + codeium_mcp: 'Codeium MCP', + windsurf_mcp: 'Windsurf MCP', claude: 'Claude', codex: 'Codex' }; diff --git a/dist/parsers/errors.js b/dist/parsers/errors.js index 42842eb..6efc21a 100644 --- a/dist/parsers/errors.js +++ b/dist/parsers/errors.js @@ -2,7 +2,8 @@ const SURFACE_NAMES = { root_mcp: 'Root MCP', cursor_mcp: 'Cursor MCP', vscode_mcp: 'VS Code MCP', - windsurf_mcp: 'Codeium/Windsurf MCP', + codeium_mcp: 'Codeium MCP', + windsurf_mcp: 'Windsurf MCP', claude: 'Claude', codex: 'Codex' }; diff --git a/dist/parsers/mcp.js b/dist/parsers/mcp.js index c76d961..83bf804 100644 --- a/dist/parsers/mcp.js +++ b/dist/parsers/mcp.js @@ -4,6 +4,7 @@ const MCP_CONFIGS = [ { surfaceId: 'root_mcp', path: '.mcp.json', serverKeys: ['mcpServers'] }, { surfaceId: 'cursor_mcp', path: '.cursor/mcp.json', serverKeys: ['mcpServers', 'servers'] }, { surfaceId: 'vscode_mcp', path: '.vscode/mcp.json', serverKeys: ['servers', 'mcpServers'] }, + { surfaceId: 'codeium_mcp', path: '.codeium/mcp_config.json', serverKeys: ['mcpServers'] }, { surfaceId: 'windsurf_mcp', path: '.codeium/windsurf/mcp_config.json', serverKeys: ['mcpServers'] } ]; export async function parseMcpSurfaces(root) { diff --git a/dist/report.js b/dist/report.js index ea06166..0dd2c9c 100644 --- a/dist/report.js +++ b/dist/report.js @@ -85,6 +85,7 @@ const SURFACE_COLUMNS = [ 'root_mcp', 'cursor_mcp', 'vscode_mcp', + 'codeium_mcp', 'windsurf_mcp', 'claude', 'codex' @@ -93,7 +94,8 @@ const SURFACE_LABELS = { root_mcp: 'Root MCP', cursor_mcp: 'Cursor MCP', vscode_mcp: 'VS Code MCP', - windsurf_mcp: 'Codeium/Windsurf MCP', + codeium_mcp: 'Codeium MCP', + windsurf_mcp: 'Windsurf MCP', claude: 'Claude', codex: 'Codex' }; diff --git a/package-lock.json b/package-lock.json index 7e9a1b9..64fee24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "policymesh", - "version": "0.1.17", + "version": "0.1.18", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "policymesh", - "version": "0.1.17", + "version": "0.1.18", "license": "MIT", "bin": { "policymesh": "dist/index.js" diff --git a/package.json b/package.json index a39d10f..50d2f84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "policymesh", - "version": "0.1.17", + "version": "0.1.18", "description": "Cross-surface AI agent policy consistency review.", "type": "module", "keywords": [ @@ -12,7 +12,8 @@ "cursor", "codex", "vscode", - "codeium" + "codeium", + "windsurf" ], "homepage": "https://github.com/Conalh/PolicyMesh#readme", "bugs": { diff --git a/src/mesh/engine.ts b/src/mesh/engine.ts index 5c9f631..298d428 100644 --- a/src/mesh/engine.ts +++ b/src/mesh/engine.ts @@ -636,7 +636,8 @@ function surfaceLabel(surface: SurfaceId): string { root_mcp: 'Root MCP', cursor_mcp: 'Cursor MCP', vscode_mcp: 'VS Code MCP', - windsurf_mcp: 'Codeium/Windsurf MCP', + codeium_mcp: 'Codeium MCP', + windsurf_mcp: 'Windsurf MCP', claude: 'Claude', codex: 'Codex' }; diff --git a/src/parsers/errors.ts b/src/parsers/errors.ts index b538893..88c7669 100644 --- a/src/parsers/errors.ts +++ b/src/parsers/errors.ts @@ -5,7 +5,8 @@ const SURFACE_NAMES: Record = { root_mcp: 'Root MCP', cursor_mcp: 'Cursor MCP', vscode_mcp: 'VS Code MCP', - windsurf_mcp: 'Codeium/Windsurf MCP', + codeium_mcp: 'Codeium MCP', + windsurf_mcp: 'Windsurf MCP', claude: 'Claude', codex: 'Codex' }; diff --git a/src/parsers/mcp.ts b/src/parsers/mcp.ts index e2ea13e..f0f4b8b 100644 --- a/src/parsers/mcp.ts +++ b/src/parsers/mcp.ts @@ -6,6 +6,7 @@ const MCP_CONFIGS = [ { surfaceId: 'root_mcp' as const, path: '.mcp.json', serverKeys: ['mcpServers'] }, { surfaceId: 'cursor_mcp' as const, path: '.cursor/mcp.json', serverKeys: ['mcpServers', 'servers'] }, { surfaceId: 'vscode_mcp' as const, path: '.vscode/mcp.json', serverKeys: ['servers', 'mcpServers'] }, + { surfaceId: 'codeium_mcp' as const, path: '.codeium/mcp_config.json', serverKeys: ['mcpServers'] }, { surfaceId: 'windsurf_mcp' as const, path: '.codeium/windsurf/mcp_config.json', serverKeys: ['mcpServers'] } ] as const; diff --git a/src/report.ts b/src/report.ts index 7b4cbc0..45c669f 100644 --- a/src/report.ts +++ b/src/report.ts @@ -110,6 +110,7 @@ const SURFACE_COLUMNS: SurfaceId[] = [ 'root_mcp', 'cursor_mcp', 'vscode_mcp', + 'codeium_mcp', 'windsurf_mcp', 'claude', 'codex' @@ -119,7 +120,8 @@ const SURFACE_LABELS: Record = { root_mcp: 'Root MCP', cursor_mcp: 'Cursor MCP', vscode_mcp: 'VS Code MCP', - windsurf_mcp: 'Codeium/Windsurf MCP', + codeium_mcp: 'Codeium MCP', + windsurf_mcp: 'Windsurf MCP', claude: 'Claude', codex: 'Codex' }; diff --git a/src/types.ts b/src/types.ts index f1d2010..41c4280 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ export type SurfaceId = | 'root_mcp' | 'cursor_mcp' | 'vscode_mcp' + | 'codeium_mcp' | 'windsurf_mcp' | 'claude' | 'codex'; diff --git a/test/cli-output.test.mjs b/test/cli-output.test.mjs index 4d60556..b5657ae 100644 --- a/test/cli-output.test.mjs +++ b/test/cli-output.test.mjs @@ -337,6 +337,26 @@ test('CLI reports Codex MCP server command drift against root MCP config', async assert.ok(report.matrix.some((row) => row.capability === 'MCP: github' && row.values.codex?.includes('@modelcontextprotocol/server-github@2.0.0'))); }); +test('CLI reports Codeium plugin MCP command drift against root MCP config', async () => { + const repo = join(testDir, 'fixtures', 'codeium-plugin-mcp-config'); + + const { stdout } = await execFileAsync( + process.execPath, + ['dist/index.js', 'audit', '--repo', repo, '--format', 'json'], + { cwd: packageRoot } + ); + const report = JSON.parse(stdout); + + assert.equal(report.rating, 'high'); + assert.equal(report.findingCount, 1); + assert.equal(report.surfaceCount, 2); + assert.equal(report.findings[0].kind, 'mcp_command_mismatch'); + assert.equal(report.findings[0].subject, 'github'); + assert.deepEqual(report.findings[0].surfaces, ['root_mcp', 'codeium_mcp']); + assert.ok(report.findings[0].locations.some((location) => location.file === '.codeium/mcp_config.json' && location.surface === 'codeium_mcp')); + assert.ok(report.matrix.some((row) => row.capability === 'MCP: github' && row.values.codeium_mcp?.includes('@modelcontextprotocol/server-github@2.0.0'))); +}); + test('CLI reports only differing MCP header value keys without leaking values', async () => { const repo = join(testDir, 'fixtures', 'mcp-header-value-mismatch'); @@ -392,10 +412,10 @@ test('CLI emits Markdown with matrix and union summary', async () => { assert.match(stdout, /# PolicyMesh agent policy review: HIGH/); assert.match(stdout, /## Effective capability union/); assert.match(stdout, /## Surface matrix/); - assert.match(stdout, /\| Capability \| Root MCP \| Cursor MCP \| VS Code MCP \| Codeium\/Windsurf MCP \| Claude \| Codex \|/); + assert.match(stdout, /\| Capability \| Root MCP \| Cursor MCP \| VS Code MCP \| Codeium MCP \| Windsurf MCP \| Claude \| Codex \|/); assert.match(stdout, /MCP: github/); assert.match(stdout, /bash wildcards allowed \(Claude\)/); - assert.match(stdout, /Surfaces: Root MCP, Cursor MCP, VS Code MCP, Codeium\/Windsurf MCP/); + assert.match(stdout, /Surfaces: Root MCP, Cursor MCP, VS Code MCP, Windsurf MCP/); }); test('CLI Markdown escapes table delimiters in matrix values', async () => { @@ -427,7 +447,7 @@ test('CLI emits GitHub warning annotations', async () => { assert.match(stdout, /^::warning file=/m); assert.match(stdout, /different launch commands/); assert.match(stdout, /title=PolicyMesh high finding/); - assert.match(stdout, /Surfaces: Root MCP, Cursor MCP, VS Code MCP, Codeium\/Windsurf MCP/); + assert.match(stdout, /Surfaces: Root MCP, Cursor MCP, VS Code MCP, Windsurf MCP/); const mismatchAnnotations = stdout .split('\n') diff --git a/test/fixtures/codeium-plugin-mcp-config/.codeium/mcp_config.json b/test/fixtures/codeium-plugin-mcp-config/.codeium/mcp_config.json new file mode 100644 index 0000000..e687be1 --- /dev/null +++ b/test/fixtures/codeium-plugin-mcp-config/.codeium/mcp_config.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github@2.0.0"] + } + } +} diff --git a/test/fixtures/codeium-plugin-mcp-config/.mcp.json b/test/fixtures/codeium-plugin-mcp-config/.mcp.json new file mode 100644 index 0000000..6808998 --- /dev/null +++ b/test/fixtures/codeium-plugin-mcp-config/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github@1.2.3"] + } + } +} diff --git a/test/workflow.test.mjs b/test/workflow.test.mjs index 7bb1765..e1f9110 100644 --- a/test/workflow.test.mjs +++ b/test/workflow.test.mjs @@ -10,15 +10,15 @@ const execFileAsync = promisify(execFile); const testDir = dirname(fileURLToPath(import.meta.url)); const packageRoot = join(testDir, '..'); -test('release metadata is prepared for v0.1.17 Action users', async () => { +test('release metadata is prepared for v0.1.18 Action users', async () => { const packageJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8')); const packageLock = JSON.parse(await readFile(join(packageRoot, 'package-lock.json'), 'utf8')); const readme = await readFile(join(packageRoot, 'README.md'), 'utf8'); - assert.equal(packageJson.version, '0.1.17'); - assert.equal(packageLock.version, '0.1.17'); - assert.equal(packageLock.packages[''].version, '0.1.17'); - assert.match(readme, /uses: Conalh\/PolicyMesh@v0\.1\.17/); + assert.equal(packageJson.version, '0.1.18'); + assert.equal(packageLock.version, '0.1.18'); + assert.equal(packageLock.packages[''].version, '0.1.18'); + assert.match(readme, /uses: Conalh\/PolicyMesh@v0\.1\.18/); }); test('package metadata supports OSS discovery', async () => { @@ -41,7 +41,8 @@ test('package metadata supports OSS discovery', async () => { 'cursor', 'codex', 'vscode', - 'codeium' + 'codeium', + 'windsurf' ]); }); @@ -114,7 +115,9 @@ test('issue templates collect detector and team validation feedback', async () = test('README documents Action credibility and robustness signals', async () => { const readme = await readFile(join(packageRoot, 'README.md'), 'utf8'); - assert.match(readme, /VS Code and Codeium\/Windsurf MCP configs/); + assert.match(readme, /VS Code and Windsurf MCP configs/); + assert.match(readme, /`\.codeium\/mcp_config\.json`/); + assert.match(readme, /`\.codeium\/windsurf\/mcp_config\.json`/); assert.match(readme, /configured MCP surfaces with empty server maps/); assert.match(readme, /MCP server enabled\/disabled drift across surfaces/); assert.match(readme, /MCP server environment drift across surfaces/);