Skip to content

test(installer): make npm resolution permission test root-safe#2253

Merged
ericksoa merged 7 commits intomainfrom
test/installer-npm-resolution-root-safe
Apr 22, 2026
Merged

test(installer): make npm resolution permission test root-safe#2253
ericksoa merged 7 commits intomainfrom
test/installer-npm-resolution-root-safe

Conversation

@cv
Copy link
Copy Markdown
Contributor

@cv cv commented Apr 22, 2026

Summary

This PR fixes the post-merge main-CI regression introduced after #485 by making the installer npm-resolution permission test match non-root installer semantics on root-backed runners. The production installer logic is unchanged; only the test harness is adjusted so the assertion no longer depends on root-only writability behavior.

Changes

  • update test/install-npm-resolution.test.ts so the permission-sensitive npm_link_targets_writable() assertion runs as nobody on Linux root runners
  • keep the temporary fixture directories traversable while making the lib path the actual unwritable target under test
  • preserve existing coverage for active-PATH npm preference, nvm fallback, and writable target detection without changing installer code

Type of Change

  • Code change (feature, bug fix, or refactor)
  • Code change with doc updates
  • Doc only (prose changes, no code sample modifications)
  • Doc only (includes code sample changes)

Verification

  • npx prek run --all-files passes
  • npm test passes
  • Tests added or updated for new or changed behavior
  • No secrets, API keys, or credentials committed
  • Docs updated for user-facing behavior changes
  • make docs builds without warnings (doc changes only)
  • Doc pages follow the style guide (doc changes only)
  • New doc pages include SPDX header and frontmatter (new pages only)

AI Disclosure

  • AI-assisted — tool: pi agent

Signed-off-by: Carlos Villela cvillela@nvidia.com

Summary by CodeRabbit

  • Tests
    • Installer tests now support executing provided snippets "raw" (no automatic sourcing) so wrappers can control execution.
    • Tests simulate privilege-drop execution (running as an unprivileged user) to validate installer behavior under lowered privileges.
    • Improved handling of writable/unwritable install targets with conditional permission adjustments when running as root.
    • No public interfaces were changed; updates are test-only.

@cv cv self-assigned this Apr 22, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough
📝 Walkthrough
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: making the npm resolution permission test root-safe by adjusting test harness logic for privilege-drop execution.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/installer-npm-resolution-root-safe

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach is correct — dropping to nobody is the right way to make permission tests meaningful on root-backed runners. The guard in nonRootSpawnOptions() is well-scoped and the fixture permissions are properly set up.

However, the WSL-E2E job is still failing on this exact test:

FAIL  test/install-npm-resolution.test.ts > reports npm link targets as unwritable
      when npm_prefix/lib exists but cannot create node_modules
AssertionError: expected null to be +0

result.status is null (signal kill, not clean exit), which suggests nobody can't access something outside the test fixture — likely the repo checkout directory used as cwd (the default when cwd is undefined). On the WSL runner, the repo root may not be world-traversable.

A couple of options:

  • Pass cwd: tmp and ensure the installer payload is accessible from there
  • Explicitly chmod a+rx the ancestor directories nobody needs to traverse
  • Skip the uid-drop path on WSL if the runner layout makes it impractical

The fix works on Linux (checks job) and macOS — just the WSL path needs attention before this can merge.

ericksoa and others added 2 commits April 22, 2026 04:40
When dropping to nobody uid/gid on root-backed WSL runners, the repo
checkout directory is not traversable. Copy the installer payload into
the test's temp dir and use that as cwd so the spawned process can
access both.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/install-npm-resolution.test.ts`:
- Around line 62-70: The nonRootSpawnOptions function currently calls
execFileSync("id", ["-u","nobody"]) / ["-g","nobody"] and parses results which
can throw or produce invalid output on minimal images; change
nonRootSpawnOptions to guard these calls with a try/catch and validate the
parsed numbers (Number.isFinite and not NaN) before returning uid/gid, and if
any step fails (execFileSync throws, trim/parseInt yields NaN, or values are out
of range) return {} as a safe fallback so tests don't error on environment
differences.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f4dcb06-3414-4d52-8822-565d3075f38c

📥 Commits

Reviewing files that changed from the base of the PR and between 15e4154 and 24a6cb2.

📒 Files selected for processing (1)
  • test/install-npm-resolution.test.ts

Comment thread test/install-npm-resolution.test.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
test/install-npm-resolution.test.ts (1)

67-70: ⚠️ Potential issue | 🟠 Major

Harden nobody UID/GID discovery to avoid environment-driven test failures.

Line 68 and Line 69 can throw (or parse invalid values) on minimal/nonstandard Linux images, which turns this into setup fragility instead of behavior validation.

Suggested hardening
 function nonRootSpawnOptions(): { uid?: number; gid?: number } {
   if (typeof process.getuid !== "function" || process.getuid() !== 0 || process.platform !== "linux") {
     return {};
   }
 
-  return {
-    uid: Number.parseInt(execFileSync("id", ["-u", "nobody"], { encoding: "utf-8" }).trim(), 10),
-    gid: Number.parseInt(execFileSync("id", ["-g", "nobody"], { encoding: "utf-8" }).trim(), 10),
-  };
+  try {
+    const uid = Number.parseInt(execFileSync("id", ["-u", "nobody"], { encoding: "utf-8" }).trim(), 10);
+    const gid = Number.parseInt(execFileSync("id", ["-g", "nobody"], { encoding: "utf-8" }).trim(), 10);
+    if (!Number.isInteger(uid) || uid < 0 || !Number.isInteger(gid) || gid < 0) {
+      return {};
+    }
+    return { uid, gid };
+  } catch {
+    return {};
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/install-npm-resolution.test.ts` around lines 67 - 70, The current helper
that returns uid/gid by calling execFileSync("id", ["-u","nobody"]) and
execFileSync("id", ["-g","nobody"]) is brittle on minimal images; wrap each
execFileSync call in a try/catch, validate the trimmed output with a /^\d+$/
check and Number.parseInt before using it, and if validation fails (or exec
throws) fall back to a safe constant UID/GID (e.g. 65534) so tests don't depend
on environment; update the return object that sets uid/gid accordingly and
optionally include a debug/log message when falling back.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@test/install-npm-resolution.test.ts`:
- Around line 67-70: The current helper that returns uid/gid by calling
execFileSync("id", ["-u","nobody"]) and execFileSync("id", ["-g","nobody"]) is
brittle on minimal images; wrap each execFileSync call in a try/catch, validate
the trimmed output with a /^\d+$/ check and Number.parseInt before using it, and
if validation fails (or exec throws) fall back to a safe constant UID/GID (e.g.
65534) so tests don't depend on environment; update the return object that sets
uid/gid accordingly and optionally include a debug/log message when falling
back.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b02e3025-f9b1-499e-8614-e0d4bb1a7cb5

📥 Commits

Reviewing files that changed from the base of the PR and between 24a6cb2 and 7edfd88.

📒 Files selected for processing (1)
  • test/install-npm-resolution.test.ts

ericksoa and others added 3 commits April 22, 2026 05:30
WSL does not support setuid via Node's spawnSync uid/gid options
(EACCES even when running as root). Switch to `runuser -u nobody`
inside the bash command to drop privileges for the permission test.

Also copy the installer payload into the temp dir so nobody can read
it, and use tmp as cwd so nobody can traverse to it.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is green (pending WSL confirmation on latest commit). The final diff against main is clean.

Summary

The permission test (npm_link_targets_writable) was failing on root-backed CI runners because test -w always returns true for root. The fix drops to nobody before running the permission check so the assertion is meaningful.

What changed

  • isLinuxRoot() helper replaces nonRootSpawnOptions() — simpler, no execFileSync dependency.
  • rawSnippet mode on runInstallerFunction — lets callers control sourcing when they need to wrap the command (e.g., in su).
  • Root path uses su -s /bin/bash nobody -c '...' — drops privileges at the bash level. Node's uid/gid spawn options don't work on WSL (EACCES), and runuser isn't available on WSL's minimal image. su is universally available.
  • Installer payload copied to /tmp and cwd set to the temp dir so nobody can access both.
  • Non-root path is unchanged — same behavior as before.

Note

Commit history has debug commits from diagnosing the WSL issue — will clean up on squash merge.

@ericksoa ericksoa merged commit 7d4874a into main Apr 22, 2026
9 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