credbroker
Resolve secrets for agent skills without leaking them to the model.
credbroker is a standalone, pip-installable credential resolver. It reads a secret in-process, walks three tiers, and never lets a cleartext value cross a process boundary to the LLM. The core is stdlib-only, with no third-party dependency.
Install
python -m pip install credbroker # stdlib-only core
python -m pip install 'credbroker[crypto]' # + encrypted-at-rest vault
Use
credbroker is a plain Python library. It works in any program, agent, or skill — no framework and no agent-ready-repo install required.
Resolve a namespace's credentials in one call:
from credbroker import load_credentials
# Keys are used verbatim. The namespace is upper-cased to compose the
# env / dotfile name: here, JIRA_BASE_URL and JIRA_API_TOKEN.
creds = load_credentials("jira", required_keys=["BASE_URL", "API_TOKEN"])
connect(creds.BASE_URL, token=creds.API_TOKEN) # attribute access returns the value
A typical agent skill resolves its namespace once, up front, and fails loud if a secret is missing — so the agent surfaces a setup prompt instead of firing a half-filled request:
from credbroker import load_credentials, CredentialsMissingError
def jira_session():
try:
creds = load_credentials("jira", required_keys=["BASE_URL", "API_TOKEN"])
except CredentialsMissingError as exc:
raise SystemExit(str(exc)) # clear setup guidance, no broken call
return Session(creds.BASE_URL, token=creds.API_TOKEN)
The returned object is immutable, and its repr lists key names only. A stray print(creds) can't echo token bytes.
How it resolves
credbroker walks three tiers and returns the first hit:
- Environment variable —
JIRA_API_TOKEN. Good for CI and ephemeral shells. - OS keyring — the platform's native secret store. macOS uses the Keychain. Windows uses Credential Manager. The backend is chosen at import time from
sys.platform. - Dotfile floor — a
0600dotfile, or an encrypted-at-rest vault with the[crypto]extra (Argon2id, then AES-256-GCM).
Linux and other platforms have no keyring tier. Resolution skips straight from the environment variable to the dotfile floor. Without [crypto], that floor is the plaintext 0600 dotfile.
SSO web-session cookies
Some enterprise instances sit behind corporate SSO and block personal API tokens outright. For those, credbroker resolves a captured web session instead of a token. The companion sso-broker engine drives a one-time browser login and stores the cookie jar; your skill resolves it in-process:
from credbroker import load_sso_cookies, SsoSessionUnavailableError
try:
jar_path = load_sso_cookies("corp") # returns a path, never the bytes
except SsoSessionUnavailableError as exc:
raise SystemExit(str(exc)) # "...run 'sso-broker register corp'"
Same discipline as the token path: the secret never crosses the model boundary. load_sso_cookies hands back the path to a 0600 cookie jar — not the cookie values — and fails closed (it never silently falls back to a token) when the session is missing or expired, surfacing a remediation that tells the user to re-register.
Re-establishing an expired session
A captured session expires long before the corporate SSO session behind it does, so a check-style verb can usually recover on its own:
try:
jar_path = load_sso_cookies("corp")
except SsoSessionUnavailableError:
refresh_sso_session("corp") # headless; no browser is shown
jar_path = load_sso_cookies("corp") # exactly one retry
refresh_sso_session takes only a profile, and that is the control rather than a convenience: the signature is structurally incapable of forwarding a sign-in destination, so an automated caller cannot choose where the browser goes. It runs headless — if the stored browser profile cannot complete the identity-provider flow unaided it raises SsoInteractionRequiredError instead of putting a login page in front of whoever is at the machine. register_sso_session(...) performs a first capture and is the only function that accepts a destination; reach it from an operator-typed action.
Recover on SsoSessionUnavailableError (and its SsoProfileNotRegisteredError subclass) and nothing else. A timeout, a missing engine, or an internal broker failure raises SsoBrokerUnavailableError — none of them means the session is gone, and re-authenticating for them would open a browser while the stored session is perfectly valid.
One condition is recoverable but not by re-authenticating: SsoStoreContendedError means another process holds that profile's store lock. Back off and retry the same call — the session is fine and the lock frees on its own. It subclasses SsoError directly rather than SsoSessionUnavailableError, so an auto-recovery handler will not mistake a busy store for an expired session.
derive_sso_destination(base_url, strategies=()) asks the resource server where it sends users to sign in (RFC 9728 protected resource metadata, then OIDC discovery, then an opt-in vendor probe), so a first capture can compare that against its configured destination before opening anything. It is defence in depth, not a control: the derivation target usually lives in the same config file as the value being attested.
The confinement helpers that keep a captured jar from over-reaching ship alongside it: filter_jar_to_domains reduces the engine's deliberately broad capture down to the domains you declare, domain_in_cookie_domains / require_host_in_cookie_domains enforce a label-boundary host match (so evil-corp.example.com never matches corp.example.com), validate_https_url / validate_root_relative_endpoint guard the connection config, and validate_sso_profile confines the profile name that becomes a filename and a keychain entry. See the SSO cookie-auth design for the full design.
HTTP access resolution
resolve_http_access selects credentials for a single HTTPS request without
requiring the caller to know which provider is present. AgentBundle 0.51.0 calls
it from agentbundle.catalogue_fetch.open_fetch_session; use it the same way in
any tool that fetches from a URL that may be open or protected.
import os
from credbroker import resolve_http_access, HttpAccessError
try:
access = resolve_http_access(
"https://registry.example.test/channel.json",
env=os.environ,
)
except HttpAccessError as exc:
raise SystemExit(f"access denied: {exc.code} from {exc.provider}")
# access is one of:
# BearerHttpAccess — set Authorization: Bearer <token>
# JfrogCliHttpAccess — delegate to `jf api`
# NetrcHttpAccess — use Basic auth from .netrc
# AnonymousHttpAccess — no credentials
env is a required keyword-only argument and is the only credential source;
os.environ is never read directly. Pass a specific dict in tests to control
which providers are active without touching the process environment.
Providers are checked in priority order:
| Priority | Provider | Active when |
|---|---|---|
| 1 | Bearer token | AGENTBUNDLE_HTTP_BEARER_TOKEN is set and non-empty |
| 2 | JFrog CLI 2.105.0+ | A configured JFrog CLI profile matches the target origin |
| 3 | Exact-machine .netrc |
The file has a record for the target host (or host:port) |
| 4 | Anonymous | None of the above are present |
The chain stops at the first available provider. When a provider is present but
broken (for example, invalid_bearer — a bearer value containing whitespace or
a non-ASCII character; or netrc_incomplete — a .netrc record with no
password), HttpAccessError is raised immediately — no silent fallback to the
next provider. An expired token passes resolution and fails later at fetch time.
To inject a specific environment dict in a test:
access = resolve_http_access(url, env={"AGENTBUNDLE_HTTP_BEARER_TOKEN": token})
Learn more
For local development, install from a repo clone: python -m pip install -e ./packages/credbroker.
See the full contract and the broker design for the rationale.
Metadata
Release files for credbroker 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| credbroker-0.7.0.tar.gz | 62.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| credbroker-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 124.4 kB
Release files / credbroker-0.7.0.tar.gz
| Download URL | credbroker-0.7.0.tar.gz |
|---|---|
| Size | 62.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7fd0e805e18f59ceadde89ebcb119964ac4301950a48585a72882166fc8b9423
|
|
BLAKE2b-256 checksum How to use checksums |
5d563525b6fdfec2313d6bc197f4aee857c398e0dae5fcc5b02ba3e35da7d166
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / credbroker-0.7.0-py3-none-any.whl
| Download URL | credbroker-0.7.0-py3-none-any.whl |
|---|---|
| Size | 62.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1a3bda81cc00346c7d014f8fc96236eee2fc2aeca38937e93b0144db653c9490
|
|
BLAKE2b-256 checksum How to use checksums |
20fa358250a75c0f02f699a0c5bcf6b3a362f8b26bbb35b47a0916f330ed5ee3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log