{"record":{"id":"bedf3a66aa65748b","repo":"paperclipai/paperclip","slug":"runner-protocol-eval-history-prefix-must-be-a-safe","errorCode":null,"errorMessage":"RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX must be a safe non-empty key prefix","messagePattern":"RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX must be a safe non-empty key prefix","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs","lineNumber":82,"sourceCode":"\nexport function validateProtocolEvalHistoryDestination({\n  bucket,\n  prefix,\n  publicBaseUrl,\n}) {\n  if (!/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bucket)) {\n    throw new Error(\n      \"RUNNER_PROTOCOL_EVAL_HISTORY_S3_BUCKET is not a valid bucket name\",\n    );\n  }\n  const normalizedPrefix = String(prefix ?? \"\").replace(/^\\/+|\\/+$/g, \"\");\n  if (\n    !normalizedPrefix ||\n    normalizedPrefix\n      .split(\"/\")\n      .some((segment) => !segment || segment === \".\" || segment === \"..\")\n  ) {\n    throw new Error(\n      \"RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX must be a safe non-empty key prefix\",\n    );\n  }\n  const url = new URL(publicBaseUrl);\n  if (\n    url.protocol !== \"https:\" ||\n    url.username ||\n    url.password ||\n    url.search ||\n    url.hash\n  ) {\n    throw new Error(\n      \"RUNNER_PROTOCOL_EVAL_HISTORY_PUBLIC_BASE_URL must be a credential-free HTTPS URL\",\n    );\n  }\n  return {\n    bucket,\n    prefix: normalizedPrefix,","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/publish-runner-protocol-eval-history.mjs#L64-L100","documentation":"Thrown by validateProtocolEvalHistoryDestination when the prefix fails safety checks: after stripping leading/trailing slashes it must be non-empty and every '/'-separated segment must be non-empty and not '.' or '..'. This prevents path-traversal-style or empty S3 key prefixes in RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX.","triggerScenarios":"Running the publish script with RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX unset/empty, set to '/' or '//', or containing segments like '.' or '..'.","commonSituations":"Leaving the env var empty assuming a default exists; copying a file-system relative path like './out' or '..' into the prefix; trailing-slash-only value.","solutions":["Set RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX to a concrete non-empty key prefix, e.g. 'eval-history/protocol'.","Remove '.' or '..' segments from the prefix.","Strip redundant leading/trailing slashes (they are trimmed, but interior empty segments are rejected)."],"exampleFix":"// before\nRUNNER_PROTOCOL_EVAL_HISTORY_PREFIX=../public\n// after\nRUNNER_PROTOCOL_EVAL_HISTORY_PREFIX=public/eval-history","handlingStrategy":"validation","validationCode":"const p = (process.env.RUNNER_PROTOCOL_EVAL_HISTORY_PREFIX ?? '').replace(/^\\/+|\\/+$/g, '');\nif (!p || p.split('/').some(s => !s || s === '.' || s === '..')) throw new Error('prefix must be safe and non-empty');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use concrete path-like prefixes with no '.' or '..' segments.","Don't leave the prefix env var empty — there is no default.","Normalize prefixes once in a shared config module."],"tags":["config","env-var","path-safety"],"backgroundTag":"invalid-env-var-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}