chatboxai/chatbox · error · Error

invalid file path for

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.

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.

Appendix: 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)