{"record":{"id":"c1d2b9cb778d31ab","repo":"vercel/ai","slug":"invalid-cline-history-file-name-historyfilename","errorCode":null,"errorMessage":"Invalid Cline history file name: ${historyFileName}","messagePattern":"Invalid Cline history file name: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/harness-cline/src/cline-resume-state.ts","lineNumber":14,"sourceCode":"import { createHash } from 'node:crypto';\nimport path from 'node:path';\nimport {\n  safeParseJSON,\n  type Experimental_SandboxSession,\n} from '@ai-sdk/provider-utils';\nimport type { AgentMessage } from '@cline/agents';\nimport { z } from 'zod/v4';\n\nconst CLINE_HISTORY_FILE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\\.json$/;\n\nexport function safeClineHistoryFileName(historyFileName: string): string {\n  if (!CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName)) {\n    throw new Error(`Invalid Cline history file name: ${historyFileName}`);\n  }\n  return historyFileName;\n}\n\nconst clineHistoryFileNameSchema = z\n  .string()\n  .refine(\n    historyFileName => CLINE_HISTORY_FILE_NAME_PATTERN.test(historyFileName),\n    'Cline historyFileName must be a safe .json basename.',\n  );\n\n/**\n * Schema for the adapter-specific portion of lifecycle state `data` produced\n * by the Cline harness's resumable lifecycle methods. Carries the basename of\n * the serialized conversation history. The actual history bytes live in a\n * private, session-scoped directory under sandbox HOME so they survive\n * cross-process resume without appearing in the agent workspace.\n */","sourceCodeStart":1,"sourceCodeEnd":32,"githubUrl":"https://github.com/vercel/ai/blob/69428b1f8b037e4d118fb4853428d5c4e620493c/packages/harness-cline/src/cline-resume-state.ts#L1-L32","documentation":"safeClineHistoryFileName validates a Cline session history file name against the strict pattern /^[A-Za-z0-9][A-Za-z0-9._-]*\\.json$/ before it is ever used to build a filesystem path. Any name containing path separators, traversal sequences ('..'), leading special characters, or a non-.json extension is rejected to prevent path traversal and arbitrary file access when resuming Cline sessions.","triggerScenarios":"Calling cline-resume-state helpers (filePath/restored) with a history file name containing '/' or '\\\\', starting with '.' or '-', containing other special characters, or not ending in '.json'; loading persisted state written by an older or foreign tool with a different naming scheme.","commonSituations":"Restoring sessions from hand-edited or third-party state files; names captured from untrusted input (URLs, CLI args) that include path components; migrated history from a previous Cline version using different naming; typos like 'session json' (space) or 'session.JSON' (uppercase extension).","solutions":["Sanitize the name to the allowed character set and re-add the .json extension before passing it in.","Strip any directory components — pass only the base file name, never a full path.","Ensure the extension is lowercase '.json' and the first character is alphanumeric.","If loading legacy state, rename/normalize history files to the expected pattern before resuming."],"exampleFix":"// before\nsafeClineHistoryFileName('../etc/passwd'); // Invalid Cline history file name\nsafeClineHistoryFileName('sub/dir/session.json'); // rejected\n\n// after: validate/sanitize first\nfunction toHistoryFileName(raw) {\n  const base = raw.split(/[\\\\/]/).pop() ?? '';\n  const safe = base.replace(/[^A-Za-z0-9._-]/g, '-');\n  return safe.endsWith('.json') ? safe : `${safe}.json`;\n}\nsafeClineHistoryFileName(toHistoryFileName('sub/dir/session.json')); // 'session.json'","handlingStrategy":"validation","validationCode":"const SAFE_HISTORY_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*\\.json$/;\nfunction isValidHistoryFileName(name) {\n  return typeof name === 'string' && SAFE_HISTORY_NAME.test(name);\n}\n// call before: if (!isValidHistoryFileName(raw)) throw new Error('bad history file name');","typeGuard":"function isValidClineHistoryFileName(value: unknown): value is string {\n  return typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9._-]*\\.json$/.test(value);\n}","tryCatchPattern":"try {\n  const name = safeClineHistoryFileName(rawName);\n  return restored(name);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid Cline history file name:')) {\n    // sanitize: strip directories and illegal chars, re-add .json, then retry\n    const base = rawName.split(/[\\\\/]/).pop() ?? '';\n    const fixed = base.replace(/[^A-Za-z0-9._-]/g, '-').replace(/(\\.json)?$/i, '.json');\n    return restored(safeClineHistoryFileName(fixed));\n  }\n  throw e;\n}","preventionTips":["Pass only base file names — never full paths or URLs — into resume helpers.","Validate history names with the same regex at your API boundary before storage.","Normalize legacy/foreign state files to the expected naming scheme on migration.","Treat history file names from untrusted sources (URLs, CLI args) as hostile input."],"tags":["validation","security","path-traversal"],"backgroundTag":"invalid-file-name","analyzedSha":"69428b1f8b037e4d118fb4853428d5c4e620493c","analyzedAt":"2026-08-30T12:32:21.016Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}