Security model¶

mpm runs other programs: that is its whole job. This page explains the trust model behind that, what is safe by default, and the guardrails around the most powerful configuration features.

The configuration file is trusted input¶

The single most important thing to understand: your mpm configuration file can make mpm run arbitrary commands. Treat it like your shell startup file (.bashrc, .zshrc) or a Makefile: anyone who can write it can run code as you the next time you invoke mpm.

This is not new to any one feature. The per-manager override mechanism alone already allows it. A [mpm.overrides.<id>] section can point a built-in manager at a different binary and prefix every call with sudo:

[mpm.overrides.brew]
cli_search_path = ["/tmp/somewhere"] # searched before $PATH
cli_names = ["not-really-brew"]      # the binary mpm executes
pre_cmds = ["sudo"]                  # prepended to every invocation

Defining a brand-new manager makes this capability explicit rather than more powerful: a definition spells out the commands to run, but choosing the binary (cli_names + cli_search_path) already permitted running anything. The ceiling is the same either way: configuration is code.

Where the configuration comes from¶

mpm reads configuration from two places, and layers them key by key (see Configuration):

  • A single file in the per-user application directory (~/.config/mpm/ on Linux, ~/Library/Application Support/mpm/ on macOS, the roaming app data folder on Windows).

  • The [tool.mpm] table of a pyproject.toml, searched from the working directory upward to the nearest VCS root. It wins over the per-user file where both set the same key.

The second place follows the directory you stand in. Cloning a repository you do not trust and running mpm outdated inside it reads the [tool.mpm] table of that repository, overrides and manager definitions included. A cloned file belongs to you, so it passes the permission check of the guardrails below. Pass --no-config to ignore every configuration file, or --config to read one file and nothing else.

No shell, no injection¶

Every command mpm runs is executed as an argument list, never through a shell (subprocess is called without shell=True anywhere). Placeholders like {package_id} and {query} in a manager definition are substituted into individual argument elements, not interpolated into a command string. A package name containing shell metacharacters (;, |, `) stays a single argument and is never interpreted by a shell.

The residual surface is ordinary argument injection (a package id that begins with - being read as a flag), which is inherent to wrapping any CLI and applies equally to the built-in managers.

Guardrails on manager definitions¶

Because a manager definition is the most capable configuration feature, it carries guardrails the plain overrides do not:

  1. Trusted local files only. mpm refuses to build a manager from a definition unless the config file (and its parent directory) is owned by you or root and is not group- or world-writable. This mirrors how ssh, git, and sudo reject unsafe-permission config. A world-writable directory is accepted only when it carries the sticky bit (like /tmp), which prevents others from replacing your file.

  2. No definitions from remote configs. mpm supports --config <url>, but it will not synthesize a manager from a definition fetched over the network. Overrides of built-in managers still load from a URL for backward compatibility.

  3. A warning for risky overrides from untrusted sources. When an override of pre_cmds, cli_names, cli_search_path (the fields that can redirect mpm to an arbitrary binary), or sudo (which runs a manager’s binary as root) is read from a remote URL or an unsafe-permission file, mpm prints a warning. The override still applies, but the heads-up is loud.

Note

The permission check is POSIX-only. On Windows the file-ownership model differs (ACLs rather than Unix mode bits), so the check is skipped. Keep your configuration directory restricted to your own account.

These guardrails govern definitions read from a configuration file. mpm also ships a few managers as bundled definitions: read-only *.toml files inside the installed package (like meta_package_manager/managers/gh_ext.toml). Those are trusted exactly like the Python modules beside them, installed from the same reviewed release, so they load without the trust and remote-source checks above. The trust gate exists for files an attacker might write, not for package data you could not alter without already controlling the installation.

Recommendations¶

  • Keep your configuration file in the default per-user directory and make sure it is not group- or world-writable.

  • Pass --no-config when you run mpm inside a repository you did not write: its pyproject.toml is read otherwise.

  • Be deliberate about --config: pointing it at a file you did not write (a downloaded snippet, a repository’s pyproject.toml) loads whatever that file says, including commands.

  • Prefer contributing a manager upstream over a private definition when the manager could be useful to others: an upstreamed manager is reviewed, tested, and shared, rather than living as executable configuration on one machine.

See also¶

  • Per-manager overrides — the [mpm.overrides.<id>] override and definition schema.

  • Cooldown — the release-age gate that mitigates a different threat: compromised upstream package releases.

  • Configuration — configuration-file discovery and precedence.