Skip to content

feat: add Linux system-wide setup#376

Open
Sahilb315 wants to merge 19 commits into
mainfrom
cursor/linux-system-install
Open

feat: add Linux system-wide setup#376
Sahilb315 wants to merge 19 commits into
mainfrom
cursor/linux-system-install

Conversation

@Sahilb315

@Sahilb315 Sahilb315 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Linux system-wide setup so one install protects every user account on shared VMs and golden Docker images.

  • pmg setup install --system (Linux, root): package-manager shims in /usr/local/lib/pmg/bin, managed config at /etc/safedep/pmg/config.yml, login-shell PATH via /etc/profile.d/pmg.sh. pmg setup remove --system uninstalls; removal is best-effort so one failed step does not strand the rest.
  • The pmg binary is validated before install: root-owned, world-executable, not writable by group or others, and in a root-owned, non-world-writable directory. Group-writable parents are tolerated because Debian/Ubuntu ship /usr/local/bin as root:staff 2775.
  • Per-user paths (event logs, cloud-sync WAL, cache) resolve from root's passwd home when running as root, never from $HOME. Environments where sudo/su preserve the invoking user's HOME previously made any sudo pmg run create root-owned state inside that user's home, fail-closing every later non-root pmg/npm/pip run for them.
  • Event-log init failure stays fatal (no silent audit loss) and exits with a triaged remedy: restore ownership with chown when another account created files inside your own home, fix the environment when a leaked HOME/XDG_CONFIG_HOME points at another user's home (chown there would brick the other account), make the directory writable when PMG_CONFIG_DIR is explicit.
  • pmg setup doctor understands system installs: alias and shim checks, a resolution-based PATH check that accepts either shim directory, and an event-log writability probe with the same triaged fix.
  • Cloud events from sudo runs are attributed to the invoking user via SUDO_USER.
  • E2E job for system install: binary rejection cases, install artifacts, PATH activation, reason-checked managed-config refusal, a regression assertion that root runs leave user homes untouched, and non-root interception through system shims.
  • Docs: docs/system-install.md covers setup, Docker ENV PATH, limitations, user data directories, and troubleshooting.

Made with Cursor

@codecov-commenter

codecov-commenter commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.20390% with 248 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.74%. Comparing base (d3e656e) to head (297f516).

Files with missing lines Patch % Lines
cmd/setup/setup.go 16.09% 72 Missing and 1 partial ⚠️
cmd/setup/doctor.go 43.80% 64 Missing and 4 partials ⚠️
internal/shim/system.go 67.30% 25 Missing and 9 partials ⚠️
config/config.go 67.12% 15 Missing and 9 partials ⚠️
internal/shim/shim.go 51.61% 10 Missing and 5 partials ⚠️
main.go 0.00% 13 Missing ⚠️
internal/ui/info.go 0.00% 9 Missing ⚠️
cmd/setup/info.go 0.00% 5 Missing ⚠️
internal/audit/cloud_sink.go 54.54% 3 Missing and 2 partials ⚠️
internal/shim/file_owner_unix.go 60.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #376      +/-   ##
==========================================
- Coverage   55.78%   55.74%   -0.05%     
==========================================
  Files         217      219       +2     
  Lines       14781    15168     +387     
==========================================
+ Hits         8246     8455     +209     
- Misses       5790     5941     +151     
- Partials      745      772      +27     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sahilb315 and others added 3 commits July 11, 2026 02:19
Install shared shims, managed configuration, and login-shell PATH integration so golden images and multi-user hosts can protect package installs for every user.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Tighten shim detection, profile repair, and install ordering while
trimming over-specific doctor/info hints from the system-install path.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Sahilb315 Sahilb315 force-pushed the cursor/linux-system-install branch from 3635f35 to ffd0e7e Compare July 10, 2026 20:52
Sahilb315 and others added 2 commits July 13, 2026 15:37
Use UserBinDir for PATH checks and pass aliases as not required under
system install without treating that as active interception.

Co-authored-by: Cursor <cursoragent@cursor.com>
Prefix the warning with [pmg] and drop the redundant continuing clause.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Sahilb315 Sahilb315 marked this pull request as ready for review July 13, 2026 10:15
@safedep

safedep Bot commented Jul 13, 2026

Copy link
Copy Markdown

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

Package Details
Package Malware Vulnerability Risky License Report
icon buf.build/gen/go/safedep/api/protocolbuffers/go @ v1.36.11-20260713161921-716fa3011a21.1
go.mod
ok icon
ok icon
ok icon
🔗

View complete scan results →

This report is generated by SafeDep Github App

@Sahilb315 Sahilb315 requested a review from abhisek July 13, 2026 10:29
Comment thread docs/config.md
Comment thread docs/system-install.md
Sahilb315 and others added 4 commits July 13, 2026 17:02
Cover root system setup, PATH/profile.d, managed config, non-root
interception, and remove. Pin pnpm 11.10.0 on the package-manager e2e
job after an integrity crash on pnpm add.

Co-authored-by: Cursor <cursoragent@cursor.com>
Align package.json with the pnpm version we want in CI so action-setup
stops erroring on a version mismatch after the e2e integrity flake.

Co-authored-by: Cursor <cursoragent@cursor.com>
pnpm 11.x `pnpm init` still writes onFail:download; `pnpm add` then
fails after PMG analysis even on 11.10.0. Seed the temp package with
npm init instead.

Co-authored-by: Cursor <cursoragent@cursor.com>
The e2e integrity crash is avoided by npm init; the 11.10.0 bump is
no longer needed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Sahilb315 Sahilb315 requested a review from abhisek July 13, 2026 12:12
Comment thread main.go

@abhisek abhisek left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Deep-dive review of the system-install feature. Overall: the design is sound and rides well on existing infrastructure (managed/global config, PMG_SHIM_PATH stripping), the docs are genuinely good, and the new e2e-system-install CI job is a strong addition — it covers the flows unit tests can't reach, including non-root interception through the system shims and clean removal. Idempotent install with profile self-repair, usefulerror codes for the root/platform gates, and the install-order comment in installSystem are all appreciated.

Issues, in priority order (details inline):

  1. Event-logging fail-open is a global policy change — explicit --log failures should stay fatal, and lockdown (IsLocked()) should keep enforcement; soft-fail is right as the default. (main.go)
  2. validateSystemExecutable only checks o+x — a user-writable binary path baked into root-installed shims is a lateral-movement vector on multi-user machines. (internal/shim/system.go)
  3. remove --system is blocked by the same validation — uninstall should not require a valid executable. (cmd/setup/setup.go)
  4. PATH precedence isn't verified — nvm/pyenv-style rc prepends silently bypass system shims while doctor reports green on membership. (cmd/setup/doctor.go, docs)
  5. isInterceptionActive branches on a display string — message text is now load-bearing logic, also grepped by CI. (cmd/setup/doctor.go)
  6. Smaller items: install output should mention that per-user configs become ignored; writeSystemProfile reads the global bin dir instead of the manager's config.

Nits not worth inline threads: errIfSystemInstallAllowed reads as "error if allowed" — consider requireSystemInstallSupported; per-user Remove() now aborts before rc cleanup when shim-dir removal fails (consider attempting rc cleanup and joining errors); the pnpm-init CI flake fix is unrelated to this feature and could land separately.

I'd hold merge on 1 and 2; the rest is follow-up-able.


Generated by Claude Code

Comment thread main.go Outdated
Comment thread internal/shim/system.go
Comment thread cmd/setup/setup.go Outdated
Comment thread cmd/setup/doctor.go Outdated
Comment thread cmd/setup/doctor.go Outdated
Comment thread internal/ui/info.go
Comment thread internal/shim/system.go Outdated
Sahilb315 and others added 2 commits July 13, 2026 22:20
Require root-owned, non-group/other-writable pmg for --system install;
allow remove without that validation. Doctor checks npm resolution for
PATH precedence, uses ImpliesInterception instead of message matching,
and documents version-manager shadowing. Pass profile bin dir from the
shim manager and note that system config ignores per-user files.

Co-authored-by: Cursor <cursoragent@cursor.com>
Doctor now verifies every installed package manager against the shim
directory, and system-install validation only requires a safe parent
directory. Cloud sync records username/uid on invocation context for
multi-user hosts sharing one endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown

vet Summary Report

This report is generated by vet

Policy Checks

  • ✅ Vulnerability
  • ✅ Malware
  • ✅ License
  • ✅ Popularity
  • ✅ Maintenance
  • ✅ Security Posture
  • ✅ Threats

Malicious Package Analysis

Malicious package analysis was performed using SafeDep Cloud API

Malicious Package Analysis Report
Ecosystem Package Version Status Report
  • ℹ️ 0 packages have been actively analyzed for malicious behaviour.
  • ✅ No malicious packages found.

Note: Some of the package analysis jobs may still be running.Please check back later. Consider increasing the timeout for better coverage.

Changed Packages

Changed Packages

  • ✅ [Go] buf.build/gen/go/safedep/api/protocolbuffers/go@1.36.11-20260713161921-716fa3011a21.1

- shim: make system executable resolution injectable so tests pass under
  umask 002; skip the root-owner test when running as root
- doctor: treat resolution into either the system or per-user shim dir as
  intercepted, and collapse the shim-in-PATH check to a single call site
- setup: make remove (both --system and per-user) best-effort with
  errors.Join so one failed step no longer strands the other artifact
- shim: allow a group-writable install parent dir (Debian/Ubuntu ship
  /usr/local/bin as root:staff 2775) while still rejecting world-writable
  and non-root-owned parents
- audit: attribute cloud events to SUDO_USER when running under sudo
- docs: drop the soft-fail event-logging claim (hard-fail is retained)
The GitHub ubuntu-latest runner ships /usr/local/bin world-writable so
tooling can install without sudo. System install correctly refuses a
world-writable dir for the shared binary (any local user could replace
it and hijack every user's npm/pip). No FHS-compliant distro or Docker
image ships it world-writable — it is always root:root 0755 or
root:staff 2775 — so this normalizes only the anomalous CI runner back
to standard perms and still exercises the real /usr/local/bin path.
A pmg run as root with a preserved HOME (GitHub runners, sudo -E, su
without -) creates the invoking user's ~/.config/safedep as root-owned,
and event-log init then fail-closes every later non-root command.
Make that state self-solvable:

- event-log init permission errors exit with a usefulerror naming the
  likely cause and the chown fix instead of a bare fatal
- pmg setup doctor probes event-log dir writability and reports the
  same fix via a new per-result Fix override
- document the mechanism and remedy in system-install.md, along with
  the binary ownership requirements for --system
- consolidate this branch's doctor tests into doctor_test.go
Path resolution trusted HOME (and XDG_*), which sudo and su can preserve
from the invoking user (GitHub runners, sudo -E, su without -). Any pmg
run as root then created root-owned ~/.config/safedep inside that user's
home, and event-log init fail-closed every later non-root pmg/npm/pip
run for them. System install made sudo pmg the documented flow, turning
this latent bug into the happy path.

When euid is 0, configDir and cacheDir now resolve from root's passwd
home instead of the environment, so root state lands under /root and
user homes are never touched. PMG_CONFIG_DIR/PMG_CACHE_DIR still win,
non-root resolution is unchanged, and Windows is unaffected (no euid).
Event-log init stays fatal on failure; sudo-run package events are
attributed via SUDO_USER and synced by the exit auto-sync as usual.

E2E: GitHub runners preserve HOME under sudo, so assert that no sudo
pmg run leaks state into the runner's home, and that the managed-config
refusal fails for the documented reason rather than a permission brick.
The chown hint is only correct when another account created files
inside the current user's own home. When a leaked HOME or
XDG_CONFIG_HOME points at another user's home (e.g. sudo -u on GitHub
runners), following it would chown that user's directory and brick
their pmg instead. Classify the failure against the passwd home,
which the leaked environment cannot influence, and prescribe:

- dir inside own home: restore ownership with chown
- dir outside own home: fix the leaked environment, never chown
- explicit PMG_CONFIG_DIR: make it writable

Used by both the fatal event-log error and the doctor check, and the
docs troubleshooting now carries the same two-case triage.
GitHub runners export XDG_CONFIG_HOME=/home/runner/.config and it leaks
through sudo -u, so the pmgtest pmg resolved the runner user's config
dir and fail-closed on its runner-owned log file (run 29289868727 shows
the triaged error catching exactly this). Set it inside the login shell
so it wins regardless of how the leak is delivered.

The remedy now returns a full-help and doctor-table pair from a single
triage, and drops the do-not-chown tail from the leak message.
Belongs with the previous commit; it was left unstaged and e0580b1
does not compile without it.
Running as uid 0 without a resolvable root passwd entry (scratch
containers, minimal chroots) panicked at startup on every command,
because the euid-based path resolution had no fallback. Fall back to
env-derived resolution there: without a passwd database there is no
user switching, so the cross-user poisoning that branch prevents
cannot occur.

Also restore the underlying cause in the generic event-log init error
(minimal output hid it after the usefulerror change), and document
that root's per-user data lives under /root regardless of a preserved
HOME.
@Sahilb315 Sahilb315 requested a review from abhisek July 13, 2026 23:22
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.

3 participants