{"record":{"id":"351d37f01a7b4949","repo":"can1357/oh-my-pi","slug":"unknown-security-resource-security-parts-join","errorCode":null,"errorMessage":"Unknown security resource: security://${parts.join(\"/\")}","messagePattern":"Unknown security resource: security://(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/internal-urls/security-protocol.ts","lineNumber":138,"sourceCode":"\t\tif (!(securityEnabledFromContext(context) ?? this.#enabled())) throw new SecurityDisabledError();\n\t\tconst parts = splitSecurityPath(url);\n\t\tconst store = await this.#store(context);\n\t\tif (parts.length === 0) {\n\t\t\treturn createSecurityResource({\n\t\t\t\turl: \"security://\",\n\t\t\t\tcontent: [\n\t\t\t\t\t\"# Security\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"OMP-owned software-security analysis resources. The namespace is read-only; use explicit security commands or tools for mutations.\",\n\t\t\t\t\t\"\",\n\t\t\t\t\t\"- `security://scans` — list scans\",\n\t\t\t\t\t\"\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\tcontentType: \"text/markdown\",\n\t\t\t\tisDirectory: true,\n\t\t\t});\n\t\t}\n\t\tif (parts[0] !== \"scans\") throw new Error(`Unknown security resource: security://${parts.join(\"/\")}`);\n\t\tif (parts.length === 1) {\n\t\t\treturn createSecurityResource({\n\t\t\t\turl: \"security://scans\",\n\t\t\t\tcontent: formatScans(await store.listScans()),\n\t\t\t\tcontentType: \"text/markdown\",\n\t\t\t\tisDirectory: true,\n\t\t\t});\n\t\t}\n\t\tconst scanId = parts[1];\n\t\tconst bundle = await store.getBundle(scanId);\n\t\tif (!bundle) throw new Error(`Unknown security scan: ${scanId}`);\n\t\tif (parts.length === 2) {\n\t\t\treturn createSecurityResource({\n\t\t\t\turl: `security://scans/${scanId}`,\n\t\t\t\tcontent: [\n\t\t\t\t\t`# Security scan ${scanId}`,\n\t\t\t\t\t\"\",\n\t\t\t\t\t`- Status: **${bundle.scan.status}**`,","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/internal-urls/security-protocol.ts#L120-L156","documentation":"After splitting a security:// URL into path segments, resolve() only recognizes \"scans\" as the first segment (the namespace root). Any other first segment — or a malformed deeper path — throws this \"Unknown security resource\" error echoing the reconstructed URL. It is a routing error: the URL does not match any resource in the security:// namespace.","triggerScenarios":"Resolving a URL like security://foo, security://scan/<id> (singular), security://scans/<id>/manifest/extra, or any typo'd top-level path where parts[0] !== \"scans\".","commonSituations":"Typos such as scan:// or security://scan/... instead of security://scans/...; hand-constructed URLs adding extra path segments under a resource; model-generated URLs inventing namespaces that don't exist; URL-encoded characters splitting into unexpected segments.","solutions":["Use the documented namespace root: security://scans (see the security:// index page which lists valid children).","Fix typos in the first path segment — only \"scans\" is valid.","Remove extra path segments: valid forms are security://, security://scans, security://scans/<id>, and security://scans/<id>/{manifest,findings,coverage,report,sarif,provenance} (plus .../findings/<findingId>).","Use the handler's complete() method to enumerate valid security:// URLs instead of hand-building them."],"exampleFix":"// before\nawait resolve(new URL(\"security://scan/abc123\"));\n// after\nawait resolve(new URL(\"security://scans/abc123\"));","handlingStrategy":"validation","validationCode":"const SECURITY_ROOTS = new Set([\"scans\"]);\nconst SECURITY_CHILDREN = new Set([\"manifest\", \"findings\", \"coverage\", \"report\", \"sarif\", \"provenance\"]);\n\nfunction validateSecurityUrl(parts: string[]): void {\n  if (parts.length === 0) return; // security:// index\n  if (!SECURITY_ROOTS.has(parts[0])) {\n    throw new Error(`Invalid security:// root \"${parts[0]}\"; expected \"scans\"`);\n  }\n  if (parts.length >= 3 && !SECURITY_CHILDREN.has(parts[2])) {\n    throw new Error(`Invalid security resource \"${parts[2]}\"; expected one of ${[...SECURITY_CHILDREN].join(\", \")}`);\n  }\n}","typeGuard":"function isScansPath(parts: string[]): parts is [\"scans\", ...string[]] {\n  return parts.length > 0 && parts[0] === \"scans\";\n}\n// use: if (!isScansPath(parts)) { /* route elsewhere or fail fast */ }","tryCatchPattern":"try {\n  return await handler.resolve(url, ctx);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Unknown security resource:\")) {\n    logger.warn(\"Unrecognized security:// URL\", { url: url.href });\n    return await handler.resolve(new URL(\"security://\"), ctx); // fall back to the index listing\n  }\n  throw err;\n}","preventionTips":["Only use \"scans\" as the first path segment under security://.","Build URLs from the completion() listing rather than string templates.","Remember the namespace is flat: one level of resources under scans/<id>, nothing deeper except findings/<findingId>.","Decode and filter empty segments before resolving user/model-provided URLs."],"tags":["internal-urls","routing","not-found"],"backgroundTag":"resource-not-found","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}