Canonicalize PACKAGE_ROOT so it matches realpath'd allowedPath checks#1905
Canonicalize PACKAGE_ROOT so it matches realpath'd allowedPath checks#1905kriszyp wants to merge 1 commit into
Conversation
PACKAGE_ROOT was derived from a raw directory walk (no realpathSync), while security/jsLoader.ts's checkAllowedModulePath always realpathSync()s the module path it's validating before the startsWith(allowedPath) comparison. When the checkout root is reached through a symlink (e.g. --preserve-symlinks, a symlinked worktree root), that asymmetry makes a legitimately in-scope module (like node_modules/mqtt) look like it's outside the allowed path — this is what was tripping unitTests/components/globalIsolation.test.js. componentLoader.ts already realpathSync()s its allowedPath sources; PACKAGE_ROOT is now canonical too, closing the gap for any consumer (tests included) that uses it as an allowedPath prefix. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request canonicalizes PACKAGE_ROOT in utility/packageUtils.js using realpathSync to resolve symlinks, ensuring correct path comparisons when the checkout is accessed via a symlink (e.g., under --preserve-symlinks). It also updates the unit tests to verify this behavior. The reviewer suggested using assert.strictEqual instead of assert.equal in the tests to comply with the repository's style guide.
| // realpathSync'd: PACKAGE_ROOT is canonicalized (see the symlink test below), so compare | ||
| // against the canonical form rather than the raw join, which would only coincidentally | ||
| // match unless this suite itself is invoked with --preserve-symlinks. | ||
| assert.equal(packageUtils.PACKAGE_ROOT, realpathSync(join(__dirname, '../..'))); |
There was a problem hiding this comment.
According to the repository style guide, strict assertion methods like assert.strictEqual should be used explicitly where strict semantics are needed (such as comparing string paths). Please use assert.strictEqual instead of assert.equal here and at line 58.
| assert.equal(packageUtils.PACKAGE_ROOT, realpathSync(join(__dirname, '../..'))); | |
| assert.strictEqual(packageUtils.PACKAGE_ROOT, realpathSync(join(__dirname, '../..'))); |
References
- Use assert.strictEqual/assert.deepStrictEqual explicitly where strict semantics are needed. (link)
- Use the bare
node:assertmodule instead ofnode:assert/strictfor test assertions to comply with linting rules, while still utilizing strict assertion methods likeassert.strictEqualfrom the bare module.
|
Reviewed; no blockers found. |
Ethan-Arrowood
left a comment
There was a problem hiding this comment.
LGTM. Canonicalizing PACKAGE_ROOT so it matches the realpath'd allowedPath prefix checks is the correct fix — checking both sides in canonical form is the sound containment form and doesn't open the inverse (a symlink escaping the boundary still resolves outside it). It's a single realpathSync at module load (cold path, PACKAGE_ROOT is a const), and the child-process test reproducing the mismatch under --preserve-symlinks is a good regression guard.
sent with Claude Opus 4.8
What / why
unitTests/components/globalIsolation.test.jswas reported to intermittently rejectnode_modules/mqttas "outside of allowed path" specifically when the checkout root is reached through a symlink (observed with dev-agent worktrees under.claude/worktrees/...).Root cause:
PACKAGE_ROOT(utility/packageUtils.js) is derived from a raw directory walk (__dirname→dirname()up to the nearestpackage.json) with norealpathSync. Meanwhilesecurity/jsLoader.ts'scheckAllowedModulePathalwaysrealpathSync()s the module path it's validating before doingpath.startsWith(allowedPath). Whenever the process resolves symlinks inconsistently for these two paths (confirmed reproducible under--preserve-symlinks, which some environments set viaNODE_OPTIONS),PACKAGE_ROOTkeeps a symlinked prefix while the module path gets canonicalized — sostartsWithnever matches, even though the module is legitimately in scope.components/componentLoader.tsalready callsrealpathSync(componentDirectory)for itsallowedPathsources — this closes the same gap forPACKAGE_ROOT, which several test fixtures (and any other consumer treating it as an allow-list prefix) rely on being canonical.Fix
utility/packageUtils.js: wrapPACKAGE_ROOT's computed value inrealpathSync().Test plan
unitTests/utility/packageUtils.test.js: spawns a childnode --preserve-symlinksprocess that requiresutility/packageUtils.jsthrough a freshly created symlink to the repo root, and assertsPACKAGE_ROOTstill equals the canonical (non-symlinked) path. Verified this test fails without the fix and passes with it.PACKAGE_ROOTequality assertion to compare againstrealpathSync(...)rather than a rawjoin(...), so it stays correct if the suite itself is ever invoked with--preserve-symlinks(Gemini review leg caught this).npm run build+npm run test:unit:main(3784 passing, 0 failing) andunitTests/components/globalIsolation.test.js+unitTests/security/**all green.npm run format:write/oxlintclean on both changed files.Review
Cross-model review (quick mode — diff <200 lines): the
reviewer/codex-reviewersubagents aren't installed in this dev-agent profile, so I ran the Gemini leg (agy) directly on the diff. It flagged one real issue (the pre-existing equality assertion would break under--preserve-symlinks), which is fixed above; its other two suggestions (Windows symlink type,returnvsthis.skip()) match the established pattern already used inunitTests/agent/fsTools.test.js, so left as-is for consistency.Refs finding from dispatch
finding-fix-harper-i7a1(queued fromfix-harper-1880).🤖 Generated with Claude Code