Skip to content

[security][low] /source path guard uses startsWith(cwd) without a separator — a sibling directory escapes the project root #401

Description

@ErykAzevedo

Version: 4.0.0-alpha.10 (confirmed on HEAD)
File: skill/scripts/live-server.mjs:848-849

const absPath = path.resolve(process.cwd(), filePath);
if (!absPath.startsWith(process.cwd())) { res.writeHead(403); res.end('Forbidden'); return; }

Issue. startsWith on path strings, without requiring a separator, accepts any sibling directory whose name starts with the project directory's name. With the live server running in /home/u/projeto:

Requested path startsWith('/home/u/projeto') Result
/home/u/projeto-backup/.env true 200 — served
/home/u/projetoEVIL/secret.txt true 200 — served

Impact. Low on its own: it needs the live server running and a caller willing to pass a path outside the root. But it composes with #304 — anyone who lifts the token from /live.js reaches these neighbouring directories too, not just the project. Sibling directories like myapp-backup/, myapp-old/ or myapp.bak/ are exactly where people keep copies with credentials in them.

Fix. The correct pattern is already used elsewhere in the same file, at lines 1069-1070:

const rel = path.relative(process.cwd(), full);
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return base;

Applying that same check to the /source handler fixes it with no new dependency and no behaviour change for legitimate paths.

(Downstream we also block dotfiles on that route, but that's a policy choice rather than a fix for this bug.)

Found while auditing the vendored skill in a project that keeps it under version control.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions