{"record":{"id":"4caf7cd1aaf8ef7c","repo":"chatboxai/chatbox","slug":"invalid-file-path-for-name-file-path","errorCode":null,"errorMessage":"invalid file path for \"${name}\": ${file.path}","messagePattern":"invalid file path for \"(.+?)\": (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/skills/builtin-sync.ts","lineNumber":180,"sourceCode":"  if (normalized === '.' || lower === 'skill.md' || lower === 'source.json') return null\n  if (normalized.startsWith('..') || path.isAbsolute(normalized)) return null\n\n  const target = path.resolve(skillDir, normalized)\n  const root = path.resolve(skillDir)\n  if (target !== root && !target.startsWith(root + path.sep)) return null\n  return { target, normalizedPath: normalized.split(path.sep).join('/') }\n}\n\nfunction prepareSnapshotFiles(name: string, skillDir: string, files: RemoteSkillFile[]): SnapshotFileTarget[] {\n  const targets: SnapshotFileTarget[] = []\n  const seenPaths = new Set<string>()\n  for (const file of files) {\n    if (!file || typeof file.path !== 'string' || typeof file.content !== 'string') {\n      throw new Error(`invalid file entry for \"${name}\"`)\n    }\n    const resolved = resolveSnapshotFilePath(skillDir, file.path)\n    if (!resolved) {\n      throw new Error(`invalid file path for \"${name}\": ${file.path}`)\n    }\n    if (seenPaths.has(resolved.normalizedPath)) {\n      throw new Error(`duplicate file path for \"${name}\": ${resolved.normalizedPath}`)\n    }\n    seenPaths.add(resolved.normalizedPath)\n    targets.push({ file, target: resolved.target, normalizedPath: resolved.normalizedPath })\n  }\n  return targets\n}\n\n/** 将 skill 内容写入快照目录的 SKILL.md（frontmatter + body）和附属文件，与 parser 的解析格式一致。 */\nfunction writeSnapshotSkill(\n  name: string,\n  metadata: SkillMetadata,\n  body: string,\n  files: RemoteSkillFile[] = [],\n  options: { replaceDir?: boolean } = {}\n): void {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/chatboxai/chatbox/blob/81571269addb6bafb589a920b2883f1e1e084fd1/src/main/skills/builtin-sync.ts#L162-L198","documentation":"Thrown when resolveSnapshotFilePath returns null for a file.path, i.e. the normalized path does not stay inside skillDir. resolveSnapshotFilePath resolves the target and checks target!==root && target.startsWith(root+sep) to reject path-traversal (.. segments, absolute paths) and writes outside the snapshot dir.","triggerScenarios":"file.path contains '..' that escapes skillDir, an absolute path (/etc/x), a drive/rooted path on Windows, or after normalization (path.resolve) the resolved target does not begin with root+path.sep. The skill name is interpolated for context.","commonSituations":"Malicious or buggy remote skill manifest pointing at parent directories; paths with leading slash treated as absolute; symlink-like '..' chains; Windows drive letters producing a target that starts with a different root than skillDir.","solutions":["Ensure all file.path values in the remote skill are relative and stay under the skill root (no leading '/', no '..').","Treat this as a security event: log and reject the entire skill rather than retrying, since path traversal is a likely attack.","Normalize paths server-side before publishing the skill manifest.","Add a unit test for resolveSnapshotFilePath covering '..' and absolute inputs."],"exampleFix":"// before\nconst resolved = resolveSnapshotFilePath(skillDir, file.path)\nif (!resolved) throw new Error(`invalid file path for \"${name}\": ${file.path}`)\n\n// after: also strip leading slashes/.. defensively before resolving\nconst safe = file.path.replace(/^\\/+/, '')\nif (safe.includes('..')) throw new Error(`invalid file path for \"${name}\": ${file.path}`)","handlingStrategy":"validation","validationCode":"const safe = file.path.replace(/^\\/+/, '')\nif (safe.includes('..') || path.isAbsolute(file.path)) throw new Error(`invalid file path for \"${name}\": ${file.path}`)","typeGuard":"function isInvalidFilePath(e: unknown): e is Error { return e instanceof Error && e.message.startsWith('invalid file path for') }","tryCatchPattern":"try { await syncSkill(name, files) } catch (e) { if (isInvalidFilePath(e)) { reportUnsafeSkill(name, e.message); return } throw e }","preventionTips":["Treat path-traversal rejections as security events: log and block the skill.","Normalize and validate paths server-side before publishing.","Reject absolute paths and '..' segments at ingest time."],"tags":["skills","builtin-sync","security","path-traversal","validation","typescript"],"backgroundTag":null,"analyzedSha":"81571269addb6bafb589a920b2883f1e1e084fd1","analyzedAt":"2026-08-12T21:51:44.981Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}