mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
main
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
94781d6bda |
Remove guard mode: proxy interception is now the only flow (#386)
* refactor: remove guard mode execution paths and guard-only packages Guard (non-proxy) mode is removed; all package-manager commands now always run the proxy flow. Removes the guard engine, the common flow, the extractor package, the npm/pypi dependency resolvers and the PackageResolver plumbing that only guard mode consumed. The guard package retains only PackageManagerGuardInteraction, which the proxy flow and confirmation interceptors reuse for user prompts. Proxy behavior is unchanged. * refactor: remove proxy opt-out surfaces, guard references in config, action and docs Removes Config.ProxyMode, ProxyConfig.Enabled, IsProxyModeEnabled, the proxy_mode legacy fallback, PMG_PROXY_ENABLED handling and the --proxy-mode / --include-dev-dependencies flags. Proxy interception can no longer be disabled. Also removes the proxy-mode input from the GitHub Action, the proxy-mode doctor check and setup info row, updates the E2E workflow to stop passing --proxy-mode=false, and sweeps guard-mode wording from docs and the config template. The legacy proxy_install_only flat key and PMG_PROXY_INSTALL_ONLY env var remain supported. audit.FlowTypeGuard is kept so previously recorded audit events still translate for cloud sync. * feat: fail loudly when a removed proxy opt-out is still configured A leftover proxy.enabled: false / proxy_mode: false config key or PMG_PROXY_ENABLED=false / PMG_PROXY_MODE=false env var previously meant guard mode; silently ignoring it would switch those users to proxy interception without notice. PMG now exits with an actionable error naming the exact source. Precedence mirrors the old resolution order: env (ignored under lockdown) > proxy.enabled > legacy proxy_mode. The pmg config subtree is exempt so the config file can still be fixed with pmg config edit/set. The GitHub Action's proxy-mode input is kept as a tombstone that fails the action when set to false and warns otherwise. * refactor: extract flows.RunProxy and address review findings Collapses the identical parse-then-run body duplicated across the 12 package manager commands into flows.RunProxy. Documents the cache-hit / offline analysis trade-off versus the removed guard manifest path, fixes a stale non-proxy label in the E2E workflow and a stale guard reference in the uvx parser comment. * fix(config): mirror old proxy opt-out precedence exactly PMG_PROXY_MODE only ever took effect through the legacy fallback, which was gated on the presence of a proxy: key in the config file (even a null one). Promoting it to the top env tier caused two inversions: a stale PMG_PROXY_MODE=false hard-failed configs that resolved to proxy mode, and PMG_PROXY_MODE=true silently overrode an explicit proxy.enabled: false file opt-out. The check now resolves in the old order: PMG_PROXY_ENABLED > proxy: section (presence gates the legacy tier) > PMG_PROXY_MODE > flat proxy_mode. parseOptOutBool also accepts numeric values (0 = false) to match viper's WeaklyTypedInput/cast.ToBool coercion, so proxy.enabled: 0 and proxy_mode: 0 are detected as opt-outs. * refactor: move package manager interaction out of guard * refactor: trim package manager interaction * fix(config): normalize config keys viper-style in proxy opt-out check Viper resolved config file keys case-insensitively and expanded dotted keys, so spellings like Proxy:, Enabled:, a literal proxy.enabled key or Proxy_Mode selected guard mode before the removal. The opt-out check now lowercases keys recursively and nests dotted keys before matching, so those existing opt-outs fail loudly instead of being silently ignored. * refactor: remove inert transitive controls, dead parser state and guard audit variant transitive / transitive_depth lost their only consumers with the dependency resolvers; remove the config fields, flags, template and doc entries, and the report/audit plumbing that misreported transitive analysis as enabled. Remove write-only parser state (PackageInstallTarget.Extras, ParsedCommand.ManifestFiles, ShouldExtractFromManifest); IsManifestInstall stays as it feeds sandbox gating via IsInstallationCommand. Remove audit.FlowTypeGuard and its cloud mapping; guard events recorded by pre-removal versions in an unsynced WAL translate to UNSPECIFIED. * fix: address review findings on the opt-out wiring and cleanups Move the removed-opt-out rejection from the CLI PersistentPreRun into proxyFlow.Run: the check now fires exactly for package-manager runs, so non-install commands (pmg setup remove, doctor, config, version) stay usable to fix or remove an opted-out installation, and future commands inherit or avoid the check by construction instead of by exemption list. Also: make the e2e malicious-package assertion actually fail the job when an install is not blocked, route pmg go through flows.RunProxy, and drop the dead extras return from pypiParsePackageInfo (extras are still stripped from package names). * fix(config): make the removed opt-out check faithful to the old resolution The gate that silenced the legacy proxy_mode surfaces matched the raw proxy key case-sensitively in the old code, while values resolved viper-style (case-insensitive, dotted keys); applying each semantic where the old code did fixes both divergences: a case-variant Proxy: section no longer hides a flat proxy_mode: false opt-out, and a dotted proxy.enabled: false overridden by proxy_mode: true no longer errors. Replace the generic key-tree normalization with two targeted lookups (the check only ever resolves proxy.enabled and proxy_mode), which also makes colliding spellings resolve deterministically. Coerce legacy-tier values cast.ToBool-style so PMG_PROXY_MODE=off style opt-outs are detected, log the config read error instead of swallowing it, and shorten the error to a one-line statement with the specific remedy in the help text. Add lockdown coverage (env inert both directions) and a repeated-run determinism test. * fix(config): fall back to defaults for unrecognized proxy opt-out values The old loader swallowed viper errors and ran on defaults, so values like proxy.enabled: yes or PMG_PROXY_ENABLED=banana silently discarded the whole config and defaulted to proxy. Treat them the same way now: unrecognized values mean the default (proxy on) instead of a hard error, and the doc comment no longer claims the old loader failed loudly. Only values that actually meant guard mode fail. Also check the removed opt-out before the CA trust check in pmg go, restoring the old error precedence: a config problem must not steer the user into an unnecessary OS trust store change. * fix(e2e): PMG_PROXY_MODE assertion must match the legacy gate semantics The runner's setup step writes the template config, which has a proxy: section — and with one present the legacy PMG_PROXY_MODE was always inert, so expecting a loud failure there asserts pre-fidelity-fix behavior. Assert both sides instead: inert (command succeeds) with the standard config, loud failure against an empty config dir where the legacy fallback actually applied. * refactor(config): collapse parseOptOutBool to ParseBool over the string form YAML hands us typed values (bool, int), so route them through fmt.Sprintf %v and strconv.ParseBool instead of a per-type switch. Identical behavior for every recognized value; numbers other than 0/1 now read as no opinion instead of cast.ToBool's nonzero-true, which no real config relies on. |
||
|
|
dc0e3202cc |
feat: Linux system-wide setup (pmg setup install --system) (#377)
* feat: add Linux system-wide setup
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>
* chore: keep local design documents untracked
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: harden and simplify Linux system install
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>
* fix: clarify system-install doctor alias and shim path checks
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>
* fix: tighten event-log soft-fail warning prefix
Prefix the warning with [pmg] and drop the redundant continuing clause.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: add Linux system-install e2e and pin pnpm for add flake
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>
* chore: bump packageManager to pnpm 11.10.0 for e2e
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>
* ci: use npm init for pnpm e2e to avoid integrity crash
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>
* chore: revert packageManager pin to pnpm 11.1.3
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>
* fix: harden system-install review findings
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>
* fix: harden doctor PATH checks and attribute cloud events by OS user
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>
* fix: address system-install review findings
- 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)
* ci: normalize /usr/local/bin perms before system-install e2e
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.
* fix: actionable remedy for root-created per-user config dir
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
* fix: resolve per-user paths from root's own home when running as root
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.
* fix: triage the unwritable config dir remedy by cause
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.
* ci: pin XDG_CONFIG_HOME for the cross-user e2e step; terse doctor fix
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.
* fix: adapt event-log error to the two-value remedy signature
Belongs with the previous commit; it was left unstaged and
|
||
|
|
d360e75897 |
feat: Add malysis cache implementation with proxy flow integration (#346)
* feat: Add malysis cache implementation with proxy flow integration * fix: Code review fixes |
||
|
|
327c9c7068 |
feat(cooldown): respect trusted_packages in dependency cooldown (#342)
* feat(cooldown): respect trusted_packages in dependency cooldown Trusted packages are now treated as a superset waiver that bypasses every PMG control (malware analysis, cooldown, and any future controls). A globally trusted package is automatically exempt from the cooldown window and no longer needs a duplicate entry in dependency_cooldown.skip. The skip list remains the narrower, cooldown-only waiver for packages that must bypass the cooldown wait but still be malware-scanned. * refactor(cooldown): tag skip reason and audit-log skipped packages Address review feedback on #342: - Restore cooldownSkip to a pure single-list function (SRP); the merge into trusted_packages now happens in a separate mergeCooldownSkip step, driven by the exported CooldownSkip wrapper. - Extend CooldownSkipInfo with a CooldownSkipReason (TrustedPackage / CooldownSkipList) on both SkipAll and per-version entries, so callers can tell apart the broad waiver from the cooldown-only one. When both lists match the same package, trusted_packages wins. - Add audit.LogCooldownSkipped and emit it from the npm and PyPI interceptors on the SkipAll path, alongside the existing info log, carrying the source list as the reason. * refactor(cooldown): inline list merge, audit per-version exemptions Address further review feedback: - Drop the separate mergeCooldownSkip helper; cooldownSkip now writes into a shared *CooldownSkipInfo and is called twice from CooldownSkip (cooldown skip list first, trusted_packages on top so trusted entries override the reason on overlap). - Audit log every exemption, not just SkipAll: a new auditCooldownSkip helper in proxy/interceptors/cooldown.go emits one event per match (package-wide or per-version), each tagged with its source list. LogCooldownSkipped gains a version argument for the per-version case. - Cover the trusted_packages reason path in TestCooldownSkip. * fix(cooldown): avoid double-auditing trusted package exemptions auditCooldownSkip now only emits EventTypeCooldownSkipped for entries that came from dependency_cooldown.skip. Trusted-package exemptions already get an EventTypeInstallTrustedAllowed event at tarball-download time (proxy/interceptors/base_registry.go), so emitting a cooldown event for them too would double-count the same waiver. * emit trusted and cooldown skip events to cloud * fix tests * refactor(cooldown): return value from collectCooldownSkip, short-circuit on trusted SkipAll Address PR review feedback: - Rename cooldownSkip to collectCooldownSkip and return CooldownSkipInfo instead of mutating an input pointer. - Add mergeCooldownSkip to combine per-list results with trusted_packages taking precedence on overlap. - CooldownSkip now consults trusted_packages first and returns immediately on a package-wide trusted exemption (DC skip list cannot add anything). - Extend tests to cover disjoint pinned entries across both lists and the case where DC version-less subsumes a trusted pinned entry. * fix(audit): address cooldown review feedback * fix(cooldown): audit cooldown skips at download time with concrete version Backend rejects PackageVersion messages without a version, and audit logs should reflect the runtime fact (a specific version was skipped) rather than the config rule. Move the audit emission from metadata-request handling to download-request handling, where the concrete version is known, and require version in LogCooldownSkipped. * chore(audit): drop dead scope assignment in LogCooldownSkipped * refactor(cooldown): move skip-list logic into cooldown handlers Registry interceptors no longer compute CooldownSkip or branch on SkipAll; they just call HandleMetadataRequest. The npm and pypi cooldown handlers own the skip lookup, the package-wide exemption short-circuit, and (for pypi) the canonical-name denormalization. Also align LogCooldownSkipped with other LogXxx signatures by taking *packagev1.PackageVersion. * fix: Simplify audit logging for dependency cooldown skip * refactor: Simplify cooldown handling and maintain separation of concepts for trusted and DC skip packages * fix: Code review fixes * fix: Emit cooldown skipped audit event ONLY when an in-window version is skipped --------- Co-authored-by: Abhisek Datta <abhisek.datta@gmail.com> |
||
|
|
db1a5e58b3 | fix: bump ptyx to fix debug log staircasing in raw mode (#312) | ||
|
|
1c25395d74 |
feat: migrate pmg to nx based release automation (#293)
* feat: Migrate release system to Nx with platform-specific npm packages * add go.work.sum * fix: CI deprecations, stale action pins, and signal propagation * fix: update e2e workflows to pnpm 11 and latest action SHAs * fix: update pmg-e2e.yml to Node 24 with Go and pnpm caching * fix: resolve E2E failures, remove goreleaser-test, update action SHAs * fix: restore goreleaser-test (required check) * fix: escape pnpm workspace detection for yarn/pnpx tests, update action versions |