---
title: What this crate does not do
description: Deliberate omissions, unsupported combinations and known rough edges in rtb-credentials, stated explicitly.
tags:
- explanation
- limitations
---
# 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 `env` → `keychain` → `literal` → `fallback_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.
```yaml
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`](https://gitlab.com/phpboyscout/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.