mirror of
https://github.com/safedep/pmg.git
synced 2026-08-03 07:24:09 +02:00
* 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.
254 lines
10 KiB
YAML
254 lines
10 KiB
YAML
# PMG configuration template. Customize this file as needed.
|
|
# https://github.com/safedep/pmg
|
|
|
|
# UI verbosity level. Valid values: silent, normal, verbose. Default is normal.
|
|
# silent: PMG is hidden from the user except for errors and malicious package detection
|
|
# normal: Show minimal status updates
|
|
# verbose: Show verbose status updates and detailed information
|
|
verbosity: normal
|
|
|
|
# Enable paranoid mode. In paranoid mode, PMG will treat suspicious packages
|
|
# as malicious packages
|
|
paranoid: false
|
|
|
|
# Disable anonymous telemetry. Default is false.
|
|
disable_telemetry: false
|
|
|
|
# Skip event logging. Default is false.
|
|
# When skip_event_logging is false, all events will be logged to file. These events are useful for audit
|
|
# trail and incident response on systems using PMG. Set this config to true to disable event logging.
|
|
skip_event_logging: false
|
|
|
|
# Event log retention days. Default is 7.
|
|
# This is the number of days to retain event logs.
|
|
event_log_retention_days: 7
|
|
|
|
# Optional message appended to every block output, regardless of which control
|
|
# blocked the installation (malware analysis, dependency cooldown, etc.).
|
|
# Useful for org deployments to point developers at internal policy docs or a
|
|
# security contact. Example:
|
|
# advisory_message: "Blocked by ACME security policy. Questions? #security-help"
|
|
advisory_message: ""
|
|
|
|
# Proxy configuration.
|
|
# PMG intercepts package manager requests through a local proxy and analyzes
|
|
# packages in real-time as they are downloaded.
|
|
proxy:
|
|
# When true, only install commands are proxied. Other commands
|
|
# (e.g., npm ls, pip list) bypass the proxy and execute directly.
|
|
install_only: false
|
|
|
|
# Per-package-manager commands to skip proxying (only applies when install_only is true).
|
|
# Example:
|
|
# skip_commands:
|
|
# pip: ["list", "show"]
|
|
skip_commands:
|
|
npm: []
|
|
|
|
# Persistent proxy server (`pmg proxy start`) settings.
|
|
server:
|
|
# Host the persistent proxy binds to. Defaults to 127.0.0.1 (loopback),
|
|
# which keeps the MITM proxy private to the host (the right choice for CI
|
|
# and local use). Set to 0.0.0.0 or a specific interface ONLY for a
|
|
# deliberately hosted deployment: a non-loopback bind exposes the proxy,
|
|
# and every client must trust the PMG CA. The --host flag overrides this.
|
|
listen_host: 127.0.0.1
|
|
|
|
# Port the persistent proxy binds to. 0 means a random free port. The
|
|
# --port flag overrides this.
|
|
listen_port: 0
|
|
|
|
# Trusted packages are packages that are trusted by the user and will be ignored by the security guardrails.
|
|
# This is useful for packages that are known to be safe and are used in the application.
|
|
# Example:
|
|
# - purl: pkg:npm/@safedep/pmg
|
|
# reason: "PMG is a trusted package for PMG"
|
|
#
|
|
# When a package is specified without an explicit version, all versions of the package will be trusted.
|
|
# This feature should be used with caution and should be used for minimal set of packages.
|
|
#
|
|
# When a package is specified with an explicit version, only that version will be trusted.
|
|
#
|
|
# The purl is the package identifier and the reason is the reason for trusting the package.
|
|
# PURL specification: https://github.com/package-url/purl-spec
|
|
trusted_packages:
|
|
- purl: pkg:npm/@safedep/pmg
|
|
reason: "PMG is a trusted package for PMG"
|
|
|
|
# Sandbox configuration (EXPERIMENTAL)
|
|
# When enabled, package managers run in sandbox environments with restricted
|
|
# filesystem, network, and process execution access. This provides defense-in-depth
|
|
# protection against malicious install scripts and supply chain attacks.
|
|
#
|
|
# When sandboxing is enabled for a package manager, policy violations will block execution
|
|
# (no "warn-only" mode). Sandboxing itself can be disabled globally or per package manager.
|
|
#
|
|
# Currently supported platforms:
|
|
# - macOS (using Seatbelt sandbox-exec)
|
|
# - Linux (using Bubblewrap with namespace isolation)
|
|
# - Windows (planned)
|
|
#
|
|
# Platform-specific limitations:
|
|
# - Linux: Filesystem permissions use coarse-grained bind mounts. Glob patterns (e.g., *.txt)
|
|
# are expanded at policy translation time, but entire directories may be mounted rather than
|
|
# individual matching files. This is less precise than macOS regex-based filtering.
|
|
# - macOS: Network filtering is limited (all-or-nothing for most policies).
|
|
sandbox:
|
|
# Enable sandbox mode (opt-in, default: false for backward compatibility)
|
|
enabled: false
|
|
|
|
# Controls scope of sandbox enforcement:
|
|
# - true: sandbox applies to all package manager commands
|
|
# - false (default): sandbox only applies to install commands, others run unrestricted
|
|
# Requires 'enabled: true' and per-PM policies to be active. May break workflows expecting unrestricted commands.
|
|
enforce_always: false
|
|
|
|
# Policy templates define policy profiles by name and path.
|
|
# They can be used to override a built-in profile or create a custom profile.
|
|
# Note: Custom profiles loaded via policy_templates can inherit from built-in
|
|
# profiles using the 'inherits' field in the YAML (e.g., inherits: npm-restrictive).
|
|
# Inheritance allows you to extend a base profile with additional permissions.
|
|
policy_templates:
|
|
# Name for the template. Can be used to override a built-in profile or create a custom profile.
|
|
# Path is the path to the template file.
|
|
# Relative path can be used to reference a template file in the config directory (example: ./npm-restrictive.yml)
|
|
npm-restrictive-override:
|
|
path: ./profiles/npm-restrictive.yml
|
|
|
|
# Per-package-manager sandbox policies
|
|
# Each package manager can have its own policy to account for unique security characteristics
|
|
policies:
|
|
# npm ecosystem. npm-restrictive is the shared base profile; each package
|
|
# manager maps to a leaf profile that re-allows only its own environment
|
|
# variables.
|
|
npm:
|
|
enabled: true
|
|
profile: npm # Built-in profile, template name, or path to custom YAML
|
|
|
|
pnpm:
|
|
enabled: true
|
|
profile: pnpm
|
|
|
|
npx:
|
|
enabled: true
|
|
profile: npx
|
|
|
|
pnpx:
|
|
enabled: true
|
|
profile: npx
|
|
|
|
yarn:
|
|
enabled: true
|
|
profile: yarn
|
|
|
|
bun:
|
|
enabled: true
|
|
profile: bun
|
|
|
|
# PyPI ecosystem. pypi-restrictive is the shared base profile; each
|
|
# package manager maps to a leaf profile that re-allows only its own
|
|
# environment variables.
|
|
pip:
|
|
enabled: true
|
|
profile: pip
|
|
|
|
pip3:
|
|
enabled: true
|
|
profile: pip
|
|
|
|
pipx:
|
|
enabled: true
|
|
profile: pipx
|
|
|
|
poetry:
|
|
enabled: true
|
|
profile: poetry
|
|
|
|
uv:
|
|
enabled: true
|
|
profile: uv
|
|
|
|
uvx:
|
|
enabled: true
|
|
profile: uvx
|
|
|
|
# Go ecosystem (experimental). The go command is opt-in: it runs only when
|
|
# invoked explicitly as `pmg go ...`.
|
|
go:
|
|
enabled: true
|
|
profile: go
|
|
|
|
# Dependency cooldown blocks installation of package versions published within
|
|
# a configurable time window.
|
|
dependency_cooldown:
|
|
enabled: true
|
|
days: 5
|
|
|
|
# Per-control skip list of packages exempt from the cooldown window.
|
|
# Packages here are STILL malware-scanned — only the cooldown wait is waived.
|
|
# Use it for first-party / internal packages that must be installed immediately
|
|
# on release (e.g. to sanity-test a freshly published version).
|
|
#
|
|
# To bypass every PMG control (malware analysis, cooldown, and any future
|
|
# controls) for a package, add it to the top-level trusted_packages list
|
|
# above instead. Trusted packages are automatically cooldown-exempt; you do
|
|
# not need to repeat them here.
|
|
#
|
|
# A PURL without a version skips cooldown for ALL versions of the package; a
|
|
# PURL with a version skips cooldown for that version only. Example:
|
|
# skip:
|
|
# - purl: pkg:npm/my-internal-sdk # all versions
|
|
# reason: "First-party SDK; sanity-tested immediately on release"
|
|
# - purl: pkg:npm/another-internal-pkg@1.2.3 # only 1.2.3
|
|
# reason: "Pin a specific just-published build"
|
|
skip: []
|
|
|
|
# Persistent analysis cache (opt-in). Caching is analyzer-specific, so config is
|
|
# nested per analyzer; today only the Malysis (malware) analyzer has a cache.
|
|
#
|
|
# By default PMG re-screens the whole resolved graph against the analysis
|
|
# backend on every install. When enabled, clean (ALLOW) verdicts are cached on
|
|
# disk and reused across runs, so repeat installs of an unchanged graph are
|
|
# fast. Only ALLOW verdicts are cached — suspicious, malicious, and
|
|
# tenant-excluded verdicts are always re-evaluated.
|
|
#
|
|
# Trade-off: a version that was clean when first screened but is later flagged
|
|
# malicious is served from cache until its entry expires; `ttl` bounds that
|
|
# window. Keep `ttl` short if you prefer freshness over speed.
|
|
analysis_cache:
|
|
# Malysis is SafeDep's threat intelligence feed.
|
|
# Set cache TTL to be lower than dependency cooldown period to have a compensating
|
|
# control in place for newly published packages whose verdict is cached.
|
|
malysis:
|
|
enabled: false
|
|
ttl: 24h
|
|
|
|
# Cloud sync configuration.
|
|
# When enabled, PMG audit events are synced to SafeDep Cloud for centralized visibility.
|
|
# Requires SAFEDEP_API_KEY and SAFEDEP_TENANT_ID environment variables for authentication.
|
|
cloud:
|
|
enabled: false
|
|
# Endpoint ID is not required. By default, it falls back to the machine's hostname.
|
|
# Set it only if you want to explicitly override the identifier for this endpoint.
|
|
endpoint_id: ""
|
|
|
|
# Auto sync drains the local cloud-sync WAL to SafeDep Cloud opportunistically
|
|
# at the end of each PMG invocation, gated by a per-host cooldown so it does
|
|
# not fire on every command. The drain runs as a short-lived detached child
|
|
# so the user-facing CLI returns immediately.
|
|
#
|
|
# Disable this in ephemeral environments (CI runners, throwaway VMs) where
|
|
# the detached child may be torn down before it finishes draining; in those
|
|
# environments, prefer an explicit `pmg cloud sync` at job-end.
|
|
auto_sync:
|
|
enabled: true
|
|
|
|
# Minimum gap between sync attempts. The lastrun timestamp is updated on
|
|
# every attempt (success or failure), so a failing cloud endpoint will not
|
|
# cause every PMG invocation to retry. Reduce this if you want a more
|
|
# aggressive retry cadence.
|
|
min_interval: 15m
|
|
|
|
# Hard timeout applied to a single background sync attempt.
|
|
timeout: 5m
|