chatboxai/chatbox · error · Error

invalid file path for "${name}": ${file.path}

Error message

invalid file path for "${name}": ${file.path}

What it means

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.

Source

Thrown at src/main/skills/builtin-sync.ts:180

  if (normalized === '.' || lower === 'skill.md' || lower === 'source.json') return null
  if (normalized.startsWith('..') || path.isAbsolute(normalized)) return null

  const target = path.resolve(skillDir, normalized)
  const root = path.resolve(skillDir)
  if (target !== root && !target.startsWith(root + path.sep)) return null
  return { target, normalizedPath: normalized.split(path.sep).join('/') }
}

function prepareSnapshotFiles(name: string, skillDir: string, files: RemoteSkillFile[]): SnapshotFileTarget[] {
  const targets: SnapshotFileTarget[] = []
  const seenPaths = new Set<string>()
  for (const file of files) {
    if (!file || typeof file.path !== 'string' || typeof file.content !== 'string') {
      throw new Error(`invalid file entry for "${name}"`)
    }
    const resolved = resolveSnapshotFilePath(skillDir, file.path)
    if (!resolved) {
      throw new Error(`invalid file path for "${name}": ${file.path}`)
    }
    if (seenPaths.has(resolved.normalizedPath)) {
      throw new Error(`duplicate file path for "${name}": ${resolved.normalizedPath}`)
    }
    seenPaths.add(resolved.normalizedPath)
    targets.push({ file, target: resolved.target, normalizedPath: resolved.normalizedPath })
  }
  return targets
}

/** 将 skill 内容写入快照目录的 SKILL.md(frontmatter + body)和附属文件,与 parser 的解析格式一致。 */
function writeSnapshotSkill(
  name: string,
  metadata: SkillMetadata,
  body: string,
  files: RemoteSkillFile[] = [],
  options: { replaceDir?: boolean } = {}
): void {

View on GitHub (pinned to 81571269ad)

Solutions

  1. Ensure all file.path values in the remote skill are relative and stay under the skill root (no leading '/', no '..').
  2. Treat this as a security event: log and reject the entire skill rather than retrying, since path traversal is a likely attack.
  3. Normalize paths server-side before publishing the skill manifest.
  4. Add a unit test for resolveSnapshotFilePath covering '..' and absolute inputs.

Example fix

// before
const resolved = resolveSnapshotFilePath(skillDir, file.path)
if (!resolved) throw new Error(`invalid file path for "${name}": ${file.path}`)

// after: also strip leading slashes/.. defensively before resolving
const safe = file.path.replace(/^\/+/, '')
if (safe.includes('..')) throw new Error(`invalid file path for "${name}": ${file.path}`)
Defensive patterns

Strategy: validation

Validate before calling

const safe = file.path.replace(/^\/+/, '')
if (safe.includes('..') || path.isAbsolute(file.path)) throw new Error(`invalid file path for "${name}": ${file.path}`)

Type guard

function isInvalidFilePath(e: unknown): e is Error { return e instanceof Error && e.message.startsWith('invalid file path for') }

Try / catch

try { await syncSkill(name, files) } catch (e) { if (isInvalidFilePath(e)) { reportUnsafeSkill(name, e.message); return } throw e }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/4caf7cd1aaf8ef7c. Report an issue: GitHub.