PyPI
Configure pip, uv, and Poetry to install through your firewall. Index setup, authentication, CI, publishing with twine, and troubleshooting.
A PyPI firewall implements the simple repository API (PEP 503 HTML and PEP 691 JSON). Anything that speaks it can install through the firewall: pip, uv, and Poetry.
The examples use <namespace-id> and <firewall-id> placeholders. Replace them with IDs, not with namespace or firewall names. The firewall's Setup tab shows a ready-to-copy pip snippet with both values already filled in. This page also covers uv and Poetry, whose configuration is not generated in the dashboard.
Index endpoint
https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/simple/The namespace ID and firewall ID are part of the URL. Use HTTP basic authentication. Set the username to the literal value __token__ and the password to your Bytesafe access token.
Configure the project
Commit a pip.conf (pip.ini on Windows) or set the index per project:
[global]
index-url = https://__token__:${BYTESAFE_TOKEN}@eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/simple/Or without a config file:
pip install --index-url "https://__token__:${BYTESAFE_TOKEN}@eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/simple/" requestsUse index-url, not extra-index-url. An extra index keeps pypi.org as a parallel source, and pip is free to install from it, bypassing the firewall entirely.
uv reads the index from pyproject.toml:
[[tool.uv.index]]
name = "firewall"
url = "https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/simple/"
default = trueWith credentials in the environment:
export UV_INDEX_FIREWALL_USERNAME=__token__
export UV_INDEX_FIREWALL_PASSWORD=$BYTESAFE_TOKENThe pip-compatible interface also accepts the flag directly:
uv pip install --index-url "https://__token__:${BYTESAFE_TOKEN}@eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/simple/" requestsPoint Poetry's source at the firewall and store the credential outside the repository:
[[tool.poetry.source]]
name = "firewall"
url = "https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/simple/"
priority = "primary"poetry config http-basic.firewall __token__ $BYTESAFE_TOKENEach developer exports a personal access token:
export BYTESAFE_TOKEN=<your token>Verify
pip install requestsuv add requestspoetry add requestsA successful install confirms the firewall is serving the package, and every request through that index is evaluated against your rules.
Do not expect a log entry from this. A request is logged only when it matches a
rule with log: true or a logging exception, so an install that breaks no rule
leaves no trace. An empty log is not evidence that the client bypassed the
firewall. To see the requests while verifying setup, add a log-only rule that
matches all packages. See What is logged when.
CI
Use a Service Access Token (SAT) instead of a PAT. Set it as a secret in your CI system and export it as BYTESAFE_TOKEN before install steps.
To correlate all requests from one pipeline run, append an interaction ID to the token:
export BYTESAFE_TOKEN="${BYTESAFE_TOKEN}::${CI_PIPELINE_ID}"The ID shows up on each log entry generated by a matching logging rule or exception from that run.
Publishing
A firewall can also receive uploads. First set a publish target: the upstream that should receive published packages. Without one the firewall is install-only and rejects uploads. Then upload with twine against the firewall's legacy endpoint:
twine upload \
--repository-url https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/pypi/<firewall-id>/legacy/ \
-u __token__ -p $BYTESAFE_TOKEN \
dist/*Upload rules run against the publish before it is forwarded. A blocked upload fails with:
requests@2.32.0 upload blocked. See firewall log for request <request-id>What a blocked install looks like
Rules on the versions phase filter releases out of the simple index. The effect depends on how the dependency is declared:
- A range like
requests>=2.31resolves to the newest version that passed the rules. The install succeeds, possibly with an older version than the public latest. - An exact pin to a filtered version fails with pip's
Could not find a version that satisfies the requirementerror, because that release no longer exists as far as the client can see.
Rules on the download phase block the file fetch. The firewall returns a 404 with:
requests@2.32.0 is blocked. Check firewall log entries for request <request-id> for detailsTake the request ID to the logs to see which rule fired. See Investigate a blocked install.
Yanked releases
Releases yanked by their publisher (PEP 592) match the deprecated selector function, so one rule keeps yanked releases out of your builds.
Install scripts in sdists
The install-scripts selector function matches source distributions that run package code at build time: a root setup.py, or an in-tree PEP 517 build backend (backend-path in pyproject.toml). Wheels install without executing package code, so they never match. This asymmetry is visible during rollout: for a release shipping both formats, pip normally picks the wheel and installs fine, while a client forced to build from source (--no-binary, or a platform with no matching wheel) hits the sdist and the rule. See Block install scripts.
Troubleshooting
BYTESAFE_TOKEN is unset, expired, or revoked, or the URL lost its credential part. Confirm the variable is set in the shell that runs the install, that the URL still carries __token__:<token>@ before the host, and that the token is still listed and unexpired in the dashboard. The token travels in the password field; the username is not checked, so __token__ is convention, not a requirement.
Most likely a versions rule filtered it. Check the firewall logs before assuming an upstream problem.
If a rule expected to log an install but no matching entry appears, check for extra-index-url entries and overriding config with pip config list, and check the PIP_INDEX_URL environment variable. Any extra index pointing at pypi.org is an open bypass.
Credentials embedded in the index URL are parsed as a URL, so characters like @ or / in a password would need percent-encoding. Bytesafe tokens (pat-/sat- plus a UUID) contain none of these, but an appended interaction ID should stick to URL-safe characters.