Experimental secrets resolver¶
The module gemstone_utils.experimental.secrets_resolver resolves string references to secret values (environment, files, container secret mounts, literals, and optional plugins). It is intended for configuration bootstrap (for example Pydantic BeforeValidator), not as a full secrets manager.
Stability: Experimental. The API and behavior may change; see the Experimental components section in the repository README.
Schemes¶
Prefix |
Behavior |
|---|---|
|
Read |
|
Read UTF-8 file once, strip, cache. Path must be absolute (no |
|
Search |
|
Return the substring after the first colon unchanged (URLs, connection strings, etc.). |
Custom prefixes can be registered with register_backend(prefix, resolver, ...).
Values containing : must use one of the prefixes above (or a registered backend). Plain strings without : are returned unchanged.
Path security¶
file:¶
Paths must be absolute (e.g.
file:/app/secret/passphrase). Relative paths and~are rejected (no tilde expansion).By default, only paths under
/app/secretare allowed (common container mount). Callset_allowed_file_path_prefixes([...])at startup to replace that list entirely (include/app/secretagain if you still need it).Do not register bare
/etcor filesystem root (/) if you can avoid it — the setter logs a warning but does not block them. Prefer narrow trees such as/etc/yourapp/secrets/.Paths outside the allowlist raise
FilePathNotAllowed.
from gemstone_utils.experimental.secrets_resolver import set_allowed_file_path_prefixes
set_allowed_file_path_prefixes(["/etc/myapp/secrets"])
secret:¶
The name segment must start with a letter, end with a letter or digit, and contain only
[A-Za-z0-9_-](no trailing-or_).Secret mounts are read via fixed roots (
CREDENTIALS_DIRECTORY,/run/secrets,/var/run/secrets) and are not subject to thefile:allowlist.Names with dots or slashes (e.g. systemd-style dotted names) must use
file:under a narrow allowed prefix instead.
BackendNotImplemented¶
Subclass of RuntimeError. Raised when a reference names a removed or unregistered backend.
reason="removed"— prefix was removed (e.g.azexp:in v0.5.0).reason="unregistered"— unknown prefix; useliteral:...for opaque values orregister_backend.prefix— normalized backend name from the reference.
Encrypted wire values ($A256GCM$...)¶
If the resolved string looks like an encrypted field (is_encrypted_prefix), it is decrypted with decrypt_string after resolving a KeyContext via set_keyctx_resolver.
Segment 2 of the wire is a canonical UUID string (logical key id), same as
EncryptedStringcolumn ciphertext.secrets_resolver.set_keyctx_resolveris separate fromEncryptedString.set_keyctx_resolver. If you use both encrypted config values and encrypted columns, register both (often with the same underlying lookup).
API notes¶
set_keyctx_resolver(func: Callable[[str], KeyContext])— must be called before resolving encrypted secrets.set_allowed_file_path_prefixes(prefixes)— replace thefile:path allowlist (default/app/secretonly).allowed_file_path_prefixes() -> frozenset[str]— resolved allowlist prefixes for introspection.resolve_secret(value: str) -> str— dispatches on prefix or decrypts encrypted blobs.register_backend,unregister_backend,is_backend_registered,list_backends— pluggable backend registry (built-insenv,file,secret,literalare pre-registered).FilePathNotAllowed— raised when afile:path is outside the allowlist.
Operational caveats¶
env:scrubbing removes variables after first read; behavior is process-global.Caching applies to env, file, and secret paths; treat the process as holding secrets in memory.
For backend-specific details, see Secret resolver backends in the repository README.