{"record":{"id":"0963d1c38ec78e08","repo":"paperclipai/paperclip","slug":"unsafe-label-result","errorCode":null,"errorMessage":"Unsafe ${label}: ${result}","messagePattern":"Unsafe (.+?): (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs","lineNumber":45,"sourceCode":"  \"case.json\",\n  \"config.json\",\n]);\n\nfunction json(value) {\n  return `${JSON.stringify(value, null, 2)}\\n`;\n}\n\nasync function loadObject(path) {\n  const value = JSON.parse(await readFile(path, \"utf8\"));\n  if (value === null || Array.isArray(value) || typeof value !== \"object\") {\n    throw new Error(`Expected a JSON object: ${path}`);\n  }\n  return value;\n}\n\nfunction safeId(value, label = \"identifier\") {\n  const result = String(value ?? \"\");\n  if (!SAFE_ID.test(result)) throw new Error(`Unsafe ${label}: ${result}`);\n  return result;\n}\n\nfunction inside(root, candidate, label) {\n  const rel = relative(resolve(root), resolve(candidate));\n  if (!rel || rel === \"..\" || rel.startsWith(`..${sep}`)) {\n    throw new Error(`${label} escapes its declared root`);\n  }\n  return resolve(candidate);\n}\n\nexport function credentialForConfig(config) {\n  if (config.provider === \"opencode\") return \"OPENROUTER_API_KEY\";\n  if (config.provider === \"claude_managed\") return \"ANTHROPIC_API_KEY\";\n  if (config.provider === \"aws_agentcore\") return \"AWS_AGENTCORE_OIDC\";\n  if (config.provider === \"codex\" || config.provider === undefined) {\n    return \"OPENAI_API_KEY\";\n  }","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/packages/paperclip-runner/scripts/runner-protocol-eval-campaign.mjs#L27-L63","documentation":"safeId validates that a string value (IDs, filenames, labels coming from config or JSON) matches the SAFE_ID pattern before it is used. Values containing path separators, spaces, or other unsafe characters are rejected with the label naming what field failed. It prevents untrusted roster/campaign content from injecting unsafe identifiers or paths.","triggerScenarios":"Calling safeId(value, label) where String(value ?? '') does not match SAFE_ID — e.g. roster.id = 'my roster/v2', a campaign ID with spaces or dots, or an empty string.","commonSituations":"Hand-edited campaign/roster JSON with IDs containing slashes, spaces, or uppercase-unfriendly characters; a filename with '..' passed as a roster reference; copy-pasted IDs with trailing whitespace or special characters.","solutions":["Rename the offending identifier to a slug matching SAFE_ID (typically letters, digits, hyphens/underscores) in the campaign or roster JSON.","Trim whitespace from the ID in the source file.","Check the SAFE_ID regex in the script and conform the value to it exactly."],"exampleFix":"// before\n\"id\": \"Team Roster/v2\"\n// after\n\"id\": \"team-roster-v2\"","handlingStrategy":"validation","validationCode":"const SAFE_ID = /^[a-z0-9][a-z0-9-_]*$/i;\nif (!SAFE_ID.test(String(id ?? \"\"))) throw new Error(`ID must match ${SAFE_ID}: ${id}`);","typeGuard":"const isSafeId = (v) => typeof v === \"string\" && /^[A-Za-z0-9][A-Za-z0-9_-]*$/.test(v);","tryCatchPattern":"try {\n  id = safeId(rawId, \"roster ID\");\n} catch (err) {\n  if (err.message.startsWith(\"Unsafe\")) console.error(`Rename identifier to slug form: ${err.message}`);\n  throw err;\n}","preventionTips":["Adopt slug-only naming (letters, digits, hyphens) for all IDs and roster filenames.","Lint campaign/roster JSON IDs against SAFE_ID in CI.","Avoid generating IDs from user text without slugification."],"tags":["validation","identifier","security"],"backgroundTag":"invalid-identifier-format","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"}