{"record":{"id":"819c3ce228e9eecf","repo":"paperclipai/paperclip","slug":"wiki-path-must-stay-inside-agents-md-idea-md-raw","errorCode":null,"errorMessage":"Wiki path must stay inside AGENTS.md, IDEA.md, raw/, or wiki/: ${path}","messagePattern":"Wiki path must stay inside AGENTS\\.md, IDEA\\.md, raw/, or wiki/: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/plugin-llm-wiki/src/wiki/core.ts","lineNumber":1177,"sourceCode":"  if (\n    !trimmed ||\n    trimmed.includes(\"\\\\\") ||\n    trimmed.split(\"/\").some((segment) => segment === \"\" || segment === \".\" || segment === \"..\")\n  ) {\n    throw new Error(`Invalid wiki path: ${path}`);\n  }\n  if (\n    trimmed !== \".gitignore\" &&\n    trimmed !== \"WIKI.md\" &&\n    trimmed !== \"AGENTS.md\" &&\n    trimmed !== \"IDEA.md\" &&\n    trimmed !== \"index.md\" &&\n    trimmed !== \"log.md\" &&\n    !trimmed.startsWith(\"raw/\") &&\n    !trimmed.startsWith(\"wiki/\") &&\n    !(options.allowMetadata && trimmed.startsWith(\".paperclip/\"))\n  ) {\n    throw new Error(`Wiki path must stay inside AGENTS.md, IDEA.md, raw/, or wiki/: ${path}`);\n  }\n  return trimmed;\n}\n\nfunction assertPagePath(path: string): string {\n  const normalized = assertWikiPath(path);\n  if (normalized !== \"index.md\" && normalized !== \"log.md\" && normalized !== \"WIKI.md\" && normalized !== \"AGENTS.md\" && normalized !== \"IDEA.md\" && !normalized.startsWith(\"wiki/\")) {\n    throw new Error(`Wiki page writes must target AGENTS.md, IDEA.md, or wiki/: ${path}`);\n  }\n  if (!normalized.endsWith(\".md\")) {\n    throw new Error(`Wiki page path must be a markdown file: ${path}`);\n  }\n  return normalized;\n}\n\nfunction assertPageWriteAllowed(path: string, writer: WritePageInput[\"writer\"] = \"agent_tool\"): void {\n  if (writer !== \"board_ui\" && PROTECTED_WIKI_CONTROL_FILES.has(path)) {\n    throw new Error(`Refusing to overwrite protected wiki control file ${path}; board-managed edits must use the wiki UI.`);","sourceCodeStart":1159,"sourceCodeEnd":1195,"githubUrl":"https://github.com/paperclipai/paperclip/blob/67001ec6eb96ae601aa27bc91d9b2415d665334a/packages/plugins/plugin-llm-wiki/src/wiki/core.ts#L1159-L1195","documentation":"Thrown by assertWikiPath when a path passes the structural check but is not one of the allowed roots: .gitignore, WIKI.md, AGENTS.md, IDEA.md, index.md, log.md, a raw/ prefixed path, a wiki/ prefixed path, or (only with allowMetadata) a .paperclip/ prefixed path. Enforces the wiki's controlled directory layout.","triggerScenarios":"Submitting a path that lives outside the allowed roots, e.g. 'notes/foo.md', 'config.json', 'README.md', or '.paperclip/x' without allowMetadata.","commonSituations":"Assuming any markdown file is writable; writing to a top-level filename not in the allowlist; trying to use .paperclip/ metadata paths from a context that did not opt in.","solutions":["Place page content under wiki/ and raw assets under raw/.","Use only the recognized control files (AGENTS.md, IDEA.md, WIKI.md, index.md, log.md) for their intended purpose.","If you need .paperclip/ metadata access, ensure the call path passes { allowMetadata: true } to assertWikiPath (internal use only).","Reject unrecognized top-level filenames at the UI boundary."],"exampleFix":"// before\nawait writePage(ctx, { companyId, path: \"notes/meeting.md\" });\n\n// after\nawait writePage(ctx, { companyId, path: \"wiki/notes/meeting.md\" });","handlingStrategy":"validation","validationCode":"const ALLOWED_TOP = new Set([\".gitignore\", \"WIKI.md\", \"AGENTS.md\", \"IDEA.md\", \"index.md\", \"log.md\"]);\nfunction isAllowedWikiRoot(trimmed) {\n  return ALLOWED_TOP.has(trimmed) || trimmed.startsWith(\"raw/\") || trimmed.startsWith(\"wiki/\") || trimmed.startsWith(\".paperclip/\");\n}\nfunction sanitizeWikiRoot(path) {\n  const trimmed = String(path).trim().replace(/^\\/+/, \"\");\n  if (!isAllowedWikiRoot(trimmed)) throw new Error(`Wiki path must stay inside AGENTS.md, IDEA.md, raw/, or wiki/: ${path}`);\n  return trimmed;\n}","typeGuard":"function isAllowedWikiPath(path) {\n  const t = String(path ?? \"\").trim().replace(/^\\/+/, \"\");\n  return isAllowedWikiRoot(t);\n}","tryCatchPattern":"try {\n  await writePage(ctx, { companyId, path, content });\n} catch (err) {\n  if (/must stay inside/.test(err.message)) {\n    return res.status(400).json({ error: \"Move the file under wiki/ or raw/, or use a recognized control file.\" });\n  }\n  throw err;\n}","preventionTips":["Default new page paths to wiki/<name>.md.","Keep raw assets under raw/.","Reject unrecognized top-level filenames early."],"tags":["validation","security","wiki","path-traversal"],"backgroundTag":null,"analyzedSha":"67001ec6eb96ae601aa27bc91d9b2415d665334a","analyzedAt":"2026-08-12T12:05:45.408Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}