Files
Ignacy Łątka 5bca403c29 fix(source-resolver): reject paths outside projectRoot + non-source extensions
readSourceFragment() previously accepted any `file` field on the
SourceLocation it was given:

  - if absolute, it read it directly (anywhere on disk);
  - if relative, it joined with projectRoot but never verified the
    resolved path stayed inside (so "../../../etc/passwd" worked).

The `file` field originates in a React fiber's _debugSource.fileName,
which a malicious app — or a cross-origin browser tab leveraging Vuln 1
to call debugger-evaluate — can mutate to any string. Combined with the
no-auth HTTP surface, this was an arbitrary file-read primitive
(~/.gitconfig, ~/.aws/credentials, /etc/passwd, ...).

Adds two gates inside readSourceFragment:

  1. Containment: path.resolve the absolute candidate, then
     path.relative(projectRoot, absPath) — null if it starts with `..`
     or is itself absolute (escaped projectRoot).
  2. Extension allowlist: only .js/.jsx/.ts/.tsx/.mjs/.cjs/.json. Even
     .env files inside the project are out of scope here.

Both checks return null silently; the fiber-source-fragment path is
opportunistic (the inspector never required it to succeed) so attackers
get no signal about whether a file existed.
2026-05-07 15:27:39 +02:00
..