{"record":{"id":"e373aab77a81791e","repo":"paperclipai/paperclip","slug":"refusing-non-allowlisted-public-protocol-eval-path","errorCode":null,"errorMessage":"Refusing non-allowlisted public protocol eval path ${file}","messagePattern":"Refusing non-allowlisted public protocol eval path (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs","lineNumber":157,"sourceCode":"\nexport async function validatePublicProtocolEvalReport(\n  reportRoot,\n  { viewerRoot } = {},\n) {\n  const root = resolve(reportRoot);\n  const files = await relativeFiles(root);\n  const hasChat = files.some((file) =>\n    /^attempts\\/[^/]+\\/index\\.html$/.test(file),\n  );\n  const viewer = hasChat ? await trustedViewerFiles(viewerRoot) : null;\n  if (!files.includes(\"index.html\") || !files.includes(\"campaign.json\")) {\n    throw new Error(\n      \"Public protocol eval report requires index.html and campaign.json\",\n    );\n  }\n  for (const file of files) {\n    if (!isPublicProtocolEvalPath(file)) {\n      throw new Error(\n        `Refusing non-allowlisted public protocol eval path ${file}`,\n      );\n    }\n    const absolute = resolve(root, ...file.split(\"/\"));\n    const metadata = await stat(absolute);\n    if (metadata.size === 0 || metadata.size > 12 * 1024 * 1024) {\n      throw new Error(\n        `Public protocol eval file exceeds its size boundary: ${file}`,\n      );\n    }\n    if (file.startsWith(\"viewer/\")) {\n      const expected = viewer?.files.get(file);\n      if (!expected || !expected.equals(await readFile(absolute)))\n        throw new Error(\n          `Public viewer asset differs from trusted build: ${file}`,\n        );\n      continue;\n    }","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs#L139-L175","documentation":"Every collected file path must match the allowlist defined by isPublicProtocolEvalPath (e.g. index.html, campaign.json, attempts/*/index.html, viewer/ assets). Any file outside that allowlist aborts publication naming the offending relative path, preventing unintended files from being exposed publicly.","triggerScenarios":"Publishing a report root containing extra files such as .DS_Store, editor backups (file~), raw JSON dumps, logs, or nested files not covered by the allowlist pattern.","commonSituations":"macOS .DS_Store or Thumbs.db inside the report dir; copying extra assets into the report folder; a newer generator emitting files the publisher doesn't allowlist; temp files left by a failed build.","solutions":["Inspect the named file; if it is not meant to be public, delete it from the report directory and re-run.","If the file is legitimately part of the report, update isPublicProtocolEvalPath in the script to allowlist its pattern, then re-run.","Regenerate the report into a clean directory so only generator output (no OS/editor droppings) is present."],"exampleFix":"# before\npublic-report/.DS_Store   -> Refusing non-allowlisted path\n# after\n$ find public-report -name .DS_Store -delete\n$ node scripts/publish-runner-protocol-eval-history.mjs ...","handlingStrategy":"validation","validationCode":"import { readdirSync } from 'node:fs';\nconst allowed = /^(index\\.html|campaign\\.json|attempts\\/[^/]+\\/index\\.html|viewer\\/[^/]+)$/;\nconst offenders = [];\n(function walk(dir, rel = '') {\n  for (const e of readdirSync(dir, { withFileTypes: true })) {\n    const p = rel ? `${rel}/${e.name}` : e.name;\n    if (e.isDirectory()) walk(`${dir}/${e.name}`, p);\n    else if (!allowed.test(p)) offenders.push(p);\n  }\n})(reportRoot);\nif (offenders.length) throw new Error(`non-allowlisted: ${offenders.join(', ')}`);","typeGuard":null,"tryCatchPattern":"try {\n  await publishReport(root);\n} catch (err) {\n  if (String(err.message).startsWith('Refusing non-allowlisted public protocol eval path')) {\n    console.error('Delete the stray file or extend the allowlist:', err.message);\n  } else throw err;\n}","preventionTips":["Generate reports into a clean directory to avoid .DS_Store, editor backups, and temp files.","Keep generator output and the publisher allowlist in sync when adding new file kinds.","Lint the report directory contents in CI before publication."],"tags":["validation","allowlist","publish-script","security"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}