Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# PolicyMesh

[![CI](https://github.com/Conalh/PolicyMesh/actions/workflows/ci.yml/badge.svg)](https://github.com/Conalh/PolicyMesh/actions/workflows/ci.yml)
Expand All @@ -12,6 +12,7 @@
- `.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`
Expand Down Expand Up @@ -62,7 +63,7 @@
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.
Expand Down Expand Up @@ -103,7 +104,7 @@
steps:
- uses: actions/checkout@v6

- uses: Conalh/PolicyMesh@v0.1.17
- uses: Conalh/PolicyMesh@v0.1.18
with:
fail-on: none
```
Expand Down Expand Up @@ -132,6 +133,7 @@
- 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.
Expand Down
3 changes: 2 additions & 1 deletion dist/mesh/engine.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { isBroadAllow, isSensitiveDeny } from '../parsers/claude.js';
import { codexSandboxRank } from '../parsers/codex.js';
export function runMeshRules(policies) {
Expand Down Expand Up @@ -547,7 +547,8 @@
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'
};
Expand Down
3 changes: 2 additions & 1 deletion dist/parsers/errors.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
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'
};
Expand Down
1 change: 1 addition & 0 deletions dist/parsers/mcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 3 additions & 1 deletion dist/report.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function renderReport(report, format, options = {}) {
if (format === 'json') {
return `${JSON.stringify(report, null, 2)}\n`;
Expand Down Expand Up @@ -85,6 +85,7 @@
'root_mcp',
'cursor_mcp',
'vscode_mcp',
'codeium_mcp',
'windsurf_mcp',
'claude',
'codex'
Expand All @@ -93,7 +94,8 @@
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'
};
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand All @@ -12,7 +12,8 @@
"cursor",
"codex",
"vscode",
"codeium"
"codeium",
"windsurf"
],
"homepage": "https://github.com/Conalh/PolicyMesh#readme",
"bugs": {
Expand Down
3 changes: 2 additions & 1 deletion src/mesh/engine.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { isBroadAllow, isSensitiveDeny } from '../parsers/claude.js';
import { codexSandboxRank } from '../parsers/codex.js';
import type { Finding, MatrixRow, McpServer, RepoPolicies, SurfaceId } from '../types.js';
Expand Down Expand Up @@ -636,7 +636,8 @@
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'
};
Expand Down
3 changes: 2 additions & 1 deletion src/parsers/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { Finding, SurfaceId } from '../types.js';
import type { JsonParseError } from '../discovery.js';

Expand All @@ -5,7 +5,8 @@
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'
};
Expand Down
1 change: 1 addition & 0 deletions src/parsers/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
4 changes: 3 additions & 1 deletion src/report.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import type { Finding, MeshReport, ReportFormat, SurfaceId } from './types.js';

interface RenderOptions {
Expand Down Expand Up @@ -110,6 +110,7 @@
'root_mcp',
'cursor_mcp',
'vscode_mcp',
'codeium_mcp',
'windsurf_mcp',
'claude',
'codex'
Expand All @@ -119,7 +120,8 @@
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'
};
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export type Severity = 'low' | 'medium' | 'high' | 'critical';

export type SurfaceId =
| 'root_mcp'
| 'cursor_mcp'
| 'vscode_mcp'
| 'codeium_mcp'
| 'windsurf_mcp'
| 'claude'
| 'codex';
Expand Down
26 changes: 23 additions & 3 deletions test/cli-output.test.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { execFile } from 'node:child_process';
Expand Down Expand Up @@ -337,6 +337,26 @@
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');

Expand Down Expand Up @@ -392,10 +412,10 @@
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 () => {
Expand Down Expand Up @@ -427,7 +447,7 @@
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')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github@2.0.0"]
}
}
}
8 changes: 8 additions & 0 deletions test/fixtures/codeium-plugin-mcp-config/.mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github@1.2.3"]
}
}
}
17 changes: 10 additions & 7 deletions test/workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -41,7 +41,8 @@ test('package metadata supports OSS discovery', async () => {
'cursor',
'codex',
'vscode',
'codeium'
'codeium',
'windsurf'
]);
});

Expand Down Expand Up @@ -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/);
Expand Down
Loading