refactor(web): type the permissions approver as the boundary it is
approveSandboxPermissions screens untrusted `_meta`, so it takes
`unknown` and decides the shape itself rather than claiming its input is
already an McpUiResourcePermissions — which had forced its own tests to
cast malformed fixtures through `as unknown as`.
Also stop honoring `true` as a permission marker. The spec defines every
permission as an object (`camera: {}`), so `true` was an undocumented
shorthand, and honoring it means a server typo turns an iframe permission
on. Only the object marker grants now; arrays are rejected too.
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
45785a0aa6
commit
286dd86f6b
1 changed files with 39 additions and 31 deletions
|
|
@ -7,7 +7,6 @@ import {
|
|||
escapeHtmlAttr,
|
||||
wrapSandboxedHtml,
|
||||
} from "./sandbox-csp";
|
||||
import type { McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps/app-bridge";
|
||||
|
||||
describe("SAFE_CSP_SOURCE", () => {
|
||||
it.each([
|
||||
|
|
@ -84,28 +83,27 @@ describe("approveCspSources", () => {
|
|||
});
|
||||
|
||||
describe("approveSandboxPermissions", () => {
|
||||
it("keeps the spec's empty-object marker and `true` as shorthand", () => {
|
||||
it("keeps the spec's empty-object marker for each known key", () => {
|
||||
expect(
|
||||
approveSandboxPermissions({
|
||||
camera: {},
|
||||
microphone: true as unknown as Record<string, never>,
|
||||
}),
|
||||
).toEqual({ camera: {}, microphone: {} });
|
||||
approveSandboxPermissions({ camera: {}, clipboardWrite: {} }),
|
||||
).toEqual({ camera: {}, clipboardWrite: {} });
|
||||
});
|
||||
|
||||
it("drops a truthy non-marker value the proxy would have honored", () => {
|
||||
// The proxy's buildAllowAttribute() tests each key for truthiness, so the
|
||||
// string "false" — a plausible way for a server to mean "off" — would
|
||||
// otherwise switch the camera on.
|
||||
it("drops a truthy non-marker the proxy would have honored", () => {
|
||||
// buildAllowAttribute() tests each key for truthiness, so the string
|
||||
// "false" — a plausible way for a server to mean "off" — would otherwise
|
||||
// switch the camera on. `true` is rejected too: it is not a marker the
|
||||
// spec defines, and guessing in the granting direction is the wrong bet.
|
||||
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
expect(
|
||||
approveSandboxPermissions({
|
||||
camera: "false",
|
||||
microphone: true,
|
||||
geolocation: 1,
|
||||
microphone: null,
|
||||
} as unknown as McpUiResourcePermissions),
|
||||
clipboardWrite: [],
|
||||
}),
|
||||
).toBeUndefined();
|
||||
expect(warn).toHaveBeenCalledTimes(3);
|
||||
expect(warn).toHaveBeenCalledTimes(4);
|
||||
warn.mockRestore();
|
||||
});
|
||||
|
||||
|
|
@ -116,15 +114,17 @@ describe("approveSandboxPermissions", () => {
|
|||
camera: false,
|
||||
midi: {},
|
||||
clipboardWrite: {},
|
||||
} as unknown as McpUiResourcePermissions),
|
||||
}),
|
||||
).toEqual({ clipboardWrite: {} });
|
||||
// `false` is an explicit refusal, not a malformed value.
|
||||
expect(warn).not.toHaveBeenCalled();
|
||||
warn.mockRestore();
|
||||
});
|
||||
|
||||
it("returns undefined for no permissions at all", () => {
|
||||
it("returns undefined when there is nothing to grant", () => {
|
||||
expect(approveSandboxPermissions(undefined)).toBeUndefined();
|
||||
expect(approveSandboxPermissions(null)).toBeUndefined();
|
||||
expect(approveSandboxPermissions("camera")).toBeUndefined();
|
||||
expect(approveSandboxPermissions({})).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue