Skip to content

What this crate does not do

Everything here is a real constraint of rtb-credentials 0.6.3, not a gap waiting to be filled in a patch release. If you are looking for whether something is supported and cannot find it described elsewhere in these docs, look here first.

You cannot change the resolution order

The chain is envkeychainliteralfallback_env, fixed in the body of Resolver::resolve. There is no configuration key, builder method or feature that reorders, disables or extends it.

A tool that needs different precedence — keychain first, say, or an extra source such as a cloud secret manager — has to read the four fields of the CredentialRef itself and implement its own walk. The type is public and its fields are public, so this is possible, but it means not using Resolver.

Implementing CredentialStore does not help here. A custom store slots into the keychain leg only; it cannot add a fifth leg or move itself up the order.

A broken keychain is not routed around

Only a clean "no such entry" from the keychain leg falls through to the next source. Any other backend error — locked keyring, no D-Bus session, a permissions failure — aborts the whole resolution and is returned to the caller.

This means a configured fallback_env does not rescue a machine whose keychain is broken. From the operator's point of view the tool has a perfectly good environment-variable fallback in its config that never gets used, and the error names the keychain rather than explaining that a fallback existed.

Removing the keychain key from the config is the only way to skip a misbehaving backend.

A literal plus a fallback fails in CI rather than using the fallback

When literal is set and CI=true, resolution stops with LiteralRefusedInCi. It does not continue to fallback_env, even when that variable is set and readable.

anthropic:
  literal: sk-ant-...
  fallback_env: ANTHROPIC_API_KEY   # set in CI, never reached

The combination that looks like "use the inline secret locally and the environment variable in CI" does not behave that way. In CI it fails outright. Getting that behaviour means removing the literal key from the config used in CI, rather than relying on the fallback to take over.

An empty variable is a successful resolution

A variable that is set but empty satisfies std::env::var, and neither the resolver nor EnvStore checks for an empty value. The credential resolves to an empty SecretString, and the remaining legs are never tried.

There is no "non-empty" validation anywhere in the crate. Whether an empty secret is a problem is left to the code that uses it, which usually discovers it as an authentication failure from a remote API.

The keychain store requires Tokio specifically

KeyringStore wraps its blocking keyring calls in tokio::task::spawn_blocking, which panics when called outside a Tokio runtime. It cannot be used on async-std, smol or any other executor, and the failure is a panic rather than an error a caller can handle.

tokio is an unconditional dependency of the crate, declared with the full feature. There is no runtime-agnostic build and no feature that removes it.

MemoryStore, EnvStore and LiteralStore do no spawn_blocking and work on any executor.

There is no way to build without the keyring dependency

Every build compiles keyring and its macOS, Windows and Linux keyutils backends. The only feature the crate has, linux-persistent, adds a backend; nothing removes one.

A build that must provably not contain keychain code cannot be produced by turning a feature off. It has to avoid depending on rtb-credentials.

The Go sibling in the phpboyscout toolkit, go/credentials, provides a registration seam that lets a build omit keychain support entirely. This crate has no equivalent, so do not assume the two components are interchangeable on that point.

Secrets cannot be written back to config

CredentialRef does not implement Serialize, and cannot, because SecretString does not implement it either. Any struct containing a CredentialRef is therefore not serialisable.

This is deliberate — it makes "load config, change one setting, save the whole struct, silently write the resolved secret to disk" a compile error. The cost is real: a tool that legitimately wants to write config back has to split its credential fields out of the serialisable struct and handle them on a separate path.

KeychainRef is serialisable, since it holds identifiers rather than a secret.

There is no credential CLI in this crate

CredentialBearing exists so a tool can enumerate the credentials its config carries, and Resolver::probe exists so a status command can report which source would win without printing secrets.

Neither ships a command. credentials list, credentials add and credentials doctor live in the consuming CLI framework, not here. This crate provides only the trait and the probe.

No caching, no expiry, no rotation

resolve performs its lookups on every call. There is no memoisation, so a credential fetched in a loop hits the platform keychain every iteration.

There is equally no notion of a credential expiring, being refreshed, or being rotated. rtb-credentials retrieves a stored static secret. Short-lived tokens, OAuth refresh flows and anything that needs to re-fetch on a 401 are outside its model and have to be built above it.

No validation of what a secret contains

The crate never inspects a secret's value. There is no length check, no prefix check, no format validation and no attempt to confirm the credential works.

A Resolver that returns Ok means a source produced a string, not that the string is a valid API key.

Not a redaction library

SecretString redacts in its own Debug output, which stops a secret being printed by debug-formatting a struct that holds one. That is the extent of it.

Once expose_secret() has been called, the returned &str is an ordinary string and nothing prevents it being logged. Catching secrets that have already escaped into free-form text — a URL with credentials in it, an error message from a third-party library — is a different job, handled elsewhere in the phpboyscout toolkit by the redact component.