PyPI integration

Point pip at your mShield index and every wheel and sdist download becomes a policy decision. Setup is one index-url line — or none, in egress-interception mode.

What this integration does

pip resolves and downloads through mShield instead of talking to PyPI directly. Every artifact download — wheel or sdist — is evaluated against your policies before it reaches the machine. Resolution behaves normally; a package that fails policy stops with an HTTP 403 naming the check that refused it.

The integration is one index-url line in a pip.conf. Nothing is installed on the developer's machine, and in egress-interception mode there is no client change at all.

For SaaS and on-prem. This guide uses client configuration. DNS mode is an alternative available only on-prem; see deployment models.

Point pip at your mShield index

Your deployment provides the hostname and the credentials to use. Replace pypi.example.mshield.dev below with your own; the commands are otherwise exactly what you will run.

Project scope — recommended for a pilot

Writes pip.conf inside the project's virtual environment, so only work done in that environment goes through mShield. An activated venv is a precondition — without one there is no project-level pip configuration to write.

Terminal
cd /path/to/your/project
python3 -m venv .venv
source .venv/bin/activate
pip config set --site global.index-url https://token:<your-token>@pypi.example.mshield.dev/simple/
chmod 600 "$VIRTUAL_ENV/pip.conf"

Or write the file directly:

pip.conf
[global]
index-url = https://token:<your-token>@pypi.example.mshield.dev/simple/

User scope

Writes $HOME/.config/pip/pip.conf (or the platform equivalent), covering every environment for that account that does not carry its own configuration.

Terminal
pip config set --user global.index-url https://token:<your-token>@pypi.example.mshield.dev/simple/

Machine scope

Writes /etc/pip.conf, covering accounts without their own user-level or venv configuration. Needs administrator rights; typical for a build image or shared runner.

Terminal
sudo pip config set --global global.index-url https://token:<your-token>@pypi.example.mshield.dev/simple/
sudo chmod 644 /etc/pip.conf

Merging into an existing pip.conf. It is an INI file. If a [global] section already exists, set or replace only index-url inside it and leave the rest of that section — and sections such as [install] or [freeze] — as they are. pip config set edits in place and does not rewrite unrelated keys.

Keep extra-index-url out unless you deliberately want pip to fall back past mShield. An extra index is a second, ungated path to the same packages, and it defeats the gate quietly.

On tokens. pip carries the token inside the index URL, so anyone who can read pip.conf can use it. Keep the file out of version control and rotate the token if it is shared or committed.

Verify it took effect

Terminal
source .venv/bin/activate
pip config list
pip index versions pip

pip config list should print global.index-url pointing at your mShield host. Deactivate the virtual environment and run it again: the line should be gone — that is the proof the change is confined to this project. pip index versions pip then resolves through mShield.

For user scope, check outside any virtual environment; an activated venv with its own configuration still overrides it. For machine scope, use pip config debug, which lists which file supplied which value, and confirm on an account that has no configuration of its own.

CI runners

  • Write pip.conf in the job from a secret, or export PIP_INDEX_URL for the step that installs. Both are discarded with the workspace.
  • Bake it into the build image at machine scope so every job is covered without editing pipelines.

Watch for tools that bypass pip's configuration: poetry, uv, and pipenv each have their own source or index settings that must point at the same endpoint, or that project keeps installing straight from PyPI.

What a blocked install looks like

Example
ERROR: HTTP error 403 while getting https://pypi.example.mshield.dev/simple/urllib3/urllib3-1.26.4-py2.py3-none-any.whl
ERROR: Could not install requirement urllib3==1.26.4
       403 urllib3 1.26.4 was refused by policy "Production dependencies"
       | More details: https://mshield.example.com/...

pip fails the install the way it fails any rejected download, and the refusal names the package and the rule behind it.

The refusal also carries a link to a page that explains the block in full — what was refused, why, the version that would satisfy the policy, and a way to request an exclusion. It needs no login, and it is scoped to that single refusal. See what the developer opens.

If the block is wrong for a case your team has accepted, the developer requests an exclusion from that page and gets back a link to follow the decision; an operator grants or refuses it, and a granted exclusion is package-scoped, time-boxed, and recorded like any other decision.

Rolling back

Terminal
pip config unset --site global.index-url

Use the same level you configured (--site, --user, or --global). Decisions already recorded stay in the dashboard.

Discuss a pilot

See whether package policy fits your build.

Tell us your team, ecosystem, and current dependency-policy challenge. Pilot scope and commercial terms are agreed before provisioning.

Email about a pilot