Skip to content

Commit d40e08d

Browse files
authored
Merge pull request #8 from MCPJam/feat/inspector-pr-2103-regression-tools
feat: regression tools for inspector PR #2103 mcpProfile
2 parents d3eb5d0 + 0129917 commit d40e08d

4 files changed

Lines changed: 396 additions & 7 deletions

File tree

src/host-probe-capture.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,17 @@ export function captureRuntime(): HostProbeSnapshot["runtime"] {
7171
}
7272

7373
export async function runCspProbes(
74-
urls: string[],
74+
probes: Array<{ url: string; expectation: "declared" | "canary" }>,
7575
): Promise<NonNullable<HostProbeSnapshot["runtime"]["cspProbes"]>> {
7676
const results: NonNullable<HostProbeSnapshot["runtime"]["cspProbes"]> = [];
77-
for (const url of urls) {
77+
for (const { url, expectation } of probes) {
7878
try {
7979
await fetch(url, { mode: "no-cors" });
80-
results.push({ url, ok: true, errorName: null });
80+
results.push({ url, expectation, ok: true, errorName: null });
8181
} catch (e) {
8282
results.push({
8383
url,
84+
expectation,
8485
ok: false,
8586
errorName: e instanceof Error ? e.name : String(e),
8687
});

src/host-probe-types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,13 @@ export interface HostProbeSnapshot {
3939
metaCsp: string | null;
4040
permissionsPolicy: string | null;
4141
};
42-
cspProbes?: Array<{ url: string; ok: boolean; errorName: string | null }>;
42+
cspProbes?: Array<{
43+
url: string;
44+
/** "declared" = URL is in resource's _meta.ui.csp; "canary" = NOT in declared list. */
45+
expectation: "declared" | "canary";
46+
ok: boolean;
47+
errorName: string | null;
48+
}>;
4349
};
4450
deltas: Array<{ at: string; hostContext: Partial<McpUiHostContext> }>;
4551
errors: Array<{ where: string; message: string }>;

src/host-probe.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,21 @@ reuploadBtn.addEventListener("click", () => {
132132

133133
probeBtn.addEventListener("click", async () => {
134134
probeBtn.disabled = true;
135+
// The declared URLs MUST match the resource's `_meta.ui.csp.connectDomains`
136+
// (see src/index.ts host-probe resource registration). Keeping these in
137+
// lockstep is what lets `assert-host-probe-csp` distinguish:
138+
// - declared+blocked → host over-restricted (or deny override active)
139+
// - canary+allowed → host LOOSENED declared CSP (SEP-1865 violation)
135140
const cspProbes = await runCspProbes([
136-
"https://api.openai.com/v1/models",
137-
"https://api.anthropic.com/v1/messages",
138-
"https://cdn.jsdelivr.net/npm/lodash@4.17.21/package.json",
141+
{ url: "https://api.openai.com/v1/models", expectation: "declared" },
142+
{ url: "https://api.anthropic.com/v1/messages", expectation: "declared" },
143+
{
144+
url: "https://cdn.jsdelivr.net/npm/lodash@4.17.21/package.json",
145+
expectation: "declared",
146+
},
147+
// Canary: not in declared connectDomains. If this succeeds, the host
148+
// failed to enforce CSP — strict regression.
149+
{ url: "https://canary.invalid.example/", expectation: "canary" },
139150
]);
140151
if (snapshot) {
141152
snapshot.runtime.cspProbes = cspProbes;

0 commit comments

Comments
 (0)