⚠️ This is an unsupported Microsoft sample. Unlikeartifacts-keyring, this project is a best-effort alternative focused on convenience (more auth auto-detection, reuse of existingazCLI logins) and debuggability (pure Python — no opaque .NET binary). It is not covered by any Microsoft support program — use at your own risk.
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.
uv tool install keyring --with artifacts-keyring-nofussOr with pipx:
pipx install keyring
pipx inject keyring artifacts-keyring-nofussBoth install the package in an isolated environment. The keyring CLI is
placed on your PATH and works automatically with both pip
(--keyring-provider=subprocess) and uv (keyring-provider = "subprocess").
The package ships a requirements-lock.txt with SHA-256 hashes for all
runtime dependencies — covered by the package's own
PyPI attestation. To install with
hash-verified, pinned dependencies:
# Extract the lockfile from the attested package on PyPI
pip download --no-deps --only-binary=:all: artifacts-keyring-nofuss -d /tmp/aknf
unzip -p /tmp/aknf/artifacts_keyring_nofuss-*.whl \
artifacts_keyring_nofuss/requirements-lock.txt > /tmp/requirements-lock.txt
# Install with pinned + hash-checked deps
uv tool install keyring --with artifacts-keyring-nofuss \
--with-requirements /tmp/requirements-lock.txtThe lockfile is maintained by Dependabot and regenerated on each release.
pip install artifacts-keyring-nofusspip install -e ".[dev]"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 | Environment variable | Reads a bearer token from ARTIFACTS_KEYRING_NOFUSS_TOKEN (or VSS_NUGET_ACCESSTOKEN as fallback). Also supports ARTIFACTS_KEYRING_NOFUSS_TOKEN_FILE pointing to a file, and auto-detects Docker BuildKit secrets at /run/secrets/. Best for CI and Docker builds. |
| 2 | Azure CLI | Runs az account get-access-token. Most common for local dev. |
| 3 | ADO auth helper | Calls ~/ado-auth-helper (created by the ado-codespaces-auth VS Code extension). Enables seamless auth in GitHub Codespaces. |
| 4 | Workload Identity | Exchanges a federated token via AZURE_CLIENT_ID + AZURE_FEDERATED_TOKEN_FILE + AZURE_TENANT_ID. Best for GitHub Actions with azure/login@v2. |
| 5 | 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: env_var, ado_auth_helper, workload_identity, 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).
For CI pipelines and Docker builds, pass a pre-minted bearer token:
export ARTIFACTS_KEYRING_NOFUSS_TOKEN=<bearer-token>For backward compatibility with existing artifacts-keyring CI configs,
VSS_NUGET_ACCESSTOKEN is also accepted as a fallback.
Set ARTIFACTS_KEYRING_NOFUSS_TOKEN_FILE to a path containing the bearer token.
This follows the Docker _FILE convention used by official images (postgres, mysql, etc.):
export ARTIFACTS_KEYRING_NOFUSS_TOKEN_FILE=/run/secrets/my_tokenWhen building Docker images with BuildKit, secrets are mounted as files under
/run/secrets/ only for the duration of the build step — they are never persisted
in image layers.
The env_var provider automatically checks these well-known BuildKit secret paths:
/run/secrets/ARTIFACTS_KEYRING_NOFUSS_TOKEN/run/secrets/ado_token
This means you can use BuildKit secrets with no extra env vars inside the container:
# Dockerfile
RUN pip install artifacts-keyring-nofuss
RUN --mount=type=secret,id=ARTIFACTS_KEYRING_NOFUSS_TOKEN \
PIP_KEYRING_PROVIDER=import pip install \
--index-url https://__token__@pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/ \
my-private-packageBuild with:
# Mint a short-lived ADO bearer token and pass it as a BuildKit secret
export ADO_TOKEN=$(az account get-access-token \
--resource 499b84ac-1321-427f-aa17-267ca6975798 --query accessToken -o tsv)
DOCKER_BUILDKIT=1 docker buildx build \
--secret id=ARTIFACTS_KEYRING_NOFUSS_TOKEN,env=ADO_TOKEN \
-t my-image .The token is available at /run/secrets/ARTIFACTS_KEYRING_NOFUSS_TOKEN only during
that RUN step and is never baked into the image.
Priority order: ARTIFACTS_KEYRING_NOFUSS_TOKEN_FILE → ARTIFACTS_KEYRING_NOFUSS_TOKEN →
VSS_NUGET_ACCESSTOKEN → BuildKit secret paths.
When using azure/login@v2 in GitHub Actions, the action automatically sets
AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_FEDERATED_TOKEN_FILE.
The workload identity provider detects these and exchanges the federated token
for a bearer — no extra configuration needed.
Add the artifacts-helper
devcontainer feature to your .devcontainer/devcontainer.json:
{
"features": {
"ghcr.io/microsoft/codespace-features/artifacts-helper:3": {}
}
}This installs the ado-codespaces-auth VS Code extension, which creates
~/ado-auth-helper. The ado_auth_helper provider calls it automatically —
no az login needed. Sign in via the "Click to authenticate" prompt in the
VS Code status bar on first use.
When installed as a standalone tool (recommended), configure pip to use the subprocess keyring provider:
# Global config (recommended — add to ~/.config/pip/pip.conf):
# [global]
# keyring-provider = subprocess
# Or per-command:
pip install --keyring-provider=subprocess \
--index-url https://pkgs.dev.azure.com/{org}/_packaging/{feed}/pypi/simple/ \
my-packageWhen installed in the same environment as pip, no extra flags are needed — the backend is discovered automatically via entry points.
- Install as a standalone tool (if not done already):
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.