Skip to content

[fix] Let open() propagate errors instead of swallowing them - #98

Merged
kmturley merged 2 commits into
mainfrom
fix/open-error-propagation
Jul 31, 2026
Merged

[fix] Let open() propagate errors instead of swallowing them#98
kmturley merged 2 commits into
mainfrom
fix/open-error-propagation

Conversation

@kmturley

Copy link
Copy Markdown
Member

Summary

  • open() was the only mutating method on ManagerLocal that caught its own errors and downgraded them to a false return — every other method (install, uninstall, installDependency, ...) throws on failure. Since this.log() is a no-op unless debug mode is explicitly enabled, a caller in the default configuration got false with zero information about why opening a package failed. Flagged in an internal spec-compliance audit.
  • Removed the try/catch so failures (a missing file, a fileOpen() error, ...) propagate as thrown Errors like everywhere else in the class.
  • Also adds the first test coverage open() has ever had: package not found, version not found, not installed, and a regression test that specifically asserts a fileOpen() failure now throws instead of returning false.

Test plan

  • npm run check passes (format, lint, build, tests — 202/202)
  • New tests cover open()'s error paths plus the happy path, with fileOpen() mocked so nothing actually launches during the test run

🤖 Generated with Claude Code

open() was the only mutating method on ManagerLocal that caught its
own errors and downgraded them to a `false` return - every other
method (install, uninstall, installDependency, ...) throws on
failure. Since logging is a no-op unless debug mode is explicitly
enabled, a caller in the default configuration got `false` with zero
information about why opening a package failed. Flagged in an
internal spec-compliance audit.

Removed the try/catch so failures (a missing file, a fileOpen()
error, ...) propagate as thrown Errors like everywhere else in the
class. Also adds the first test coverage open() has ever had: not
found, version not found, not installed, and a regression test that
specifically asserts a fileOpen() failure now throws instead of
returning false.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread src/classes/ManagerLocal.ts Fixed
Comment thread src/classes/ManagerLocal.ts Fixed
… Windows

GitHub Advanced Security (CodeQL) flagged js/shell-command-constructed-from-input
on this PR: fileOpen()'s Windows branch called
execFileSync('cmd.exe', ['/c', 'start', '""', filePath]) with a filePath
that (via ManagerLocal.open(), which this PR touches) ultimately comes
from a package's `open` field in registry metadata - untrusted,
community-submitted content.

execFileSync itself never invokes a shell, but the target of that
specific call is cmd.exe, which *is* a command interpreter: it
re-parses its /c command line using cmd's own grammar, where &, |, ^
etc are metacharacters, regardless of how Node quoted the argv it was
given. A filePath containing one of those characters could still be
reinterpreted as a second command.

Both fileOpen() and dirOpen() (same pattern, same false safety
comment, though CodeQL only traced a reachable untrusted-input path to
fileOpen) now use explorer.exe on Windows instead of cmd.exe /c start.
explorer.exe treats its argument as a literal path with no shell
reinterpretation. Its exit code is unreliable (frequently non-zero
even on success), so both now use spawn() and don't wait on/check the
result, rather than execFileSync() which would throw on that.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@kmturley
kmturley merged commit acbdd0a into main Jul 31, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants