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.
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:
[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.
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.
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
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.confin the job from a secret, or exportPIP_INDEX_URLfor 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
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
pip config unset --site global.index-url
Use the same level you configured (--site, --user, or
--global). Decisions already recorded stay in the dashboard.