{"record":{"id":"0771fd893b0205fd","repo":"different-ai/openwork","slug":"invalid-resource-csp","errorCode":"invalid_resource_csp","errorMessage":"MCP App CSP domain lists must contain at most 16 origins.","messagePattern":"MCP App CSP domain lists must contain at most 16 origins\\.","errorType":"error_code","errorClass":"McpAppHostError","httpStatus":null,"severity":"error","filePath":"apps/server/src/mcp-app-host.ts","lineNumber":114,"sourceCode":"  try {\n    const url = new URL(value);\n    if (url.username || url.password || url.pathname !== \"/\" || url.search || url.hash) return null;\n    if (url.protocol === \"https:\") return url.origin;\n    if (url.protocol === \"http:\" && isLoopbackHostname(url.hostname)) return url.origin;\n  } catch {\n    return null;\n  }\n  return null;\n}\n\nfunction isLoopbackHostname(hostname: string): boolean {\n  return hostname === \"127.0.0.1\" || hostname === \"localhost\" || hostname === \"[::1]\";\n}\n\nfunction domainList(value: unknown): string[] {\n  if (!Array.isArray(value) || value.length > 16) {\n    if (value === undefined) return [];\n    throw new McpAppHostError(\"invalid_resource_csp\", \"MCP App CSP domain lists must contain at most 16 origins.\");\n  }\n  const domains = value.map(safeDomain);\n  if (domains.some((domain) => domain === null)) {\n    throw new McpAppHostError(\"invalid_resource_csp\", \"MCP App CSP domains must be HTTPS origins (or loopback HTTP origins).\");\n  }\n  return Array.from(new Set(domains as string[]));\n}\n\nfunction resourcePresentationMeta(value: unknown): { csp: McpAppCsp; prefersBorder: boolean } {\n  const meta = isRecord(value) ? value : {};\n  const ui = isRecord(meta.ui) ? meta.ui : {};\n  const csp = isRecord(ui.csp) ? ui.csp : {};\n  const permissions = isRecord(ui.permissions) ? ui.permissions : {};\n  if (Object.keys(permissions).length > 0 || ui.domain !== undefined) {\n    throw new McpAppHostError(\n      \"unsupported_resource_permissions\",\n      \"This OpenWork host slice does not grant device permissions or dedicated sandbox origins.\",\n    );","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/mcp-app-host.ts#L96-L132","documentation":"domainList parses a CSP domain array from an MCP App's resource _meta and enforces a hard cap of 16 origins. If the value is not an array or exceeds 16 entries, the host throws invalid_resource_csp to bound the size of content-security-policy allow-lists it will render.","triggerScenarios":"An MCP App resource declares ui.csp with a domains array (or non-array value) longer than 16 origins, or the field is present but not an array (e.g. a string).","commonSituations":"Server author allow-listing many CDN/API origins in one CSP entry; auto-generated CSP including every vendor domain; passing a comma-joined string instead of an array.","solutions":["Trim the domains array in the app's _meta.ui.csp to 16 or fewer HTTPS origins.","Consolidate origins (use a shared CDN domain or wildcard-free minimal set).","Ensure ui.csp.domains is a JSON array of strings, not a string.","Split UI across multiple resources if more than 16 origins are truly required."],"exampleFix":"// before\n'\"csp\": { \"connectDomains\": [\"https://a.com\",\"https://b.com\", /* 20 more */] }'\n// after\n'\"csp\": { \"connectDomains\": [\"https://api.example.com\", \"https://cdn.example.com\"] }'","handlingStrategy":"validation","validationCode":"const domains = meta?.ui?.csp?.connectDomains\nif (!Array.isArray(domains)) throw new Error('csp domains must be an array')\nif (domains.length > 16) throw new Error(`max 16 origins, got ${domains.length}`)","typeGuard":"function isCspDomainList(v: unknown): v is `https://${string}`[] {\n  return Array.isArray(v) && v.length <= 16 && v.every((d): d is `https://${string}` => typeof d === 'string' && d.startsWith('https://'))\n}","tryCatchPattern":"try {\n  const { csp } = resourcePresentationMeta(resource._meta)\n} catch (e) {\n  if (e instanceof McpAppHostError && e.code === 'invalid_resource_csp') {\n    renderWithDefaultCsp(resource); warn('app CSP rejected, using defaults')\n  } else throw e\n}","preventionTips":["Keep CSP domain lists small — under 16 origins per list","Consolidate third-party origins onto one CDN","Always emit arrays, never comma-joined strings, for csp domain fields","Lint tool _meta CSP shape in CI"],"tags":["mcp","mcp-apps","csp","validation"],"backgroundTag":"invalid-resource-csp","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}