Minimal, pure-Python keyring backend for Azure DevOps Artifacts feeds.
Replaces the official artifacts-keyring (which wraps a ~100 MB .NET binary) with a
no-fuss, pure-Python implementation — no .NET required.
pip install artifacts-keyring-nofussOr for development:
pip install -e .When pip, uv, twine, etc. query the keyring for credentials to an Azure DevOps Artifacts feed, this backend:
- Discovers the Azure AD tenant by making an unauthenticated request to the feed
URL and parsing the
WWW-Authenticateheader. - Obtains a bearer token using one of the supported auth flows (see below).
- For user tokens (Azure CLI): exchanges the bearer token for a narrower
VssSessionTokenscoped tovso.packaging. - For service principal tokens (managed identity, SP, WIF): returns the Entra bearer token directly as Basic auth credentials.
- Returns the credentials to the caller.
| # | Flow | How it works |
|---|---|---|
| 1 | Azure CLI | Runs az account get-access-token. Most common for local dev. |
| 2 | Azure Identity | Uses DefaultAzureCredential from azure-identity. Handles managed identities (system + user-assigned), service principals (secret/cert), workload identity federation, and more. |
By default, providers are tried in the order above. To force a specific one:
# Environment variable
export ARTIFACTS_KEYRING_NOFUSS_PROVIDER=azure_cli # or: azure_identityOr in ~/.config/python_keyring/keyringrc.cfg:
[artifacts_keyring_nofuss]
provider = azure_cliSet AZURE_CLIENT_ID to the client ID of the user-assigned managed identity:
export AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxWhen unset, system-assigned managed identity is used.
Set the standard Azure Identity environment variables:
export AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export AZURE_CLIENT_SECRET=your-secretThis requires the azure-identity package (included as a dependency). The service principal must have
permissions on the Azure DevOps feed (e.g. Feed Reader).
pip install --index-url https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/ my-packageThe keyring backend is automatically discovered by pip. No extra flags needed.
- Install keyring with this backend:
pip install keyring artifacts-keyring-nofuss
# or
uv tool install keyring --with artifacts-keyring-nofuss- Configure uv to use keyring for authentication. Either add it to your project config:
# pyproject.toml or uv.toml
[tool.uv]
keyring-provider = "subprocess"Or set the environment variable:
export UV_KEYRING_PROVIDER=subprocess- Use uv as normal with your private feed. A username in the URL (e.g.
__token__@) is required to trigger keyring lookup:
uv pip install my-package --index-url https://__token__@pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/This also works with legacy subdomain-prefixed feed URLs:
uv pip install my-package --index-url https://__token__@myorg.pkgs.visualstudio.com/_packaging/{feed}/pypi/simple/For pyproject.toml index configuration:
[[tool.uv.index]]
url = "https://__token__@pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/"
name = "my-feed"Any URL whose host matches one of (including subdomain-prefixed variants):
pkgs.dev.azure.com(e.g.https://pkgs.dev.azure.com/myorg/…)pkgs.visualstudio.com(e.g.https://myorg.pkgs.visualstudio.com/…)pkgs.codedev.mspkgs.vsts.me
URLs with userinfo (e.g. https://__token__@host/…) and bare hostnames without
a scheme are also handled correctly.
Enable verbose debug output to see the full authentication flow:
ARTIFACTS_KEYRING_NOFUSS_DEBUG=1 pip install --index-url https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/ my-packageThis prints the provider chain, token exchange steps, and any errors to stderr.
This package handles authentication tokens. Key security properties:
- Endpoint validation: Discovery responses are validated against allowlists.
The
authorization_uriand VSTS authority must point to known hosts over HTTPS, with no non-default ports, userinfo, or deep paths. The authority must be a clean origin (https://hostorhttps://host/). This prevents bearer token exfiltration via DNS hijacking or rogue proxy responses. - Short-lived tokens: Bearer tokens are not persisted to disk. In-memory caching has a 50-minute TTL (tokens typically live 60–75 minutes).
- Narrow scope: User tokens (Azure CLI) are exchanged for session tokens scoped to
vso.packaging(read-only). Service principal tokens (MI/SP/WIF) are returned directly — scope is determined by the identity's Azure DevOps permissions. - No CWD config: Provider configuration is read only from
~/.config/or environment variables, never from the working directory.