Inject a secret into an execution unit¶
What this recipe does¶
Resolves a list of EnvironmentVariable records — some static, some
referencing secret refs — into a concrete env dict via SecretResolver,
then uses SecretRedactor to scrub the resolved values out of an audit
payload before it's persisted.
When to use¶
- An executable unit needs API keys, DB passwords, or tokens at run time, and you don't want those values baked into the manifest.
- You're writing audit records that include tool inputs and want automatic redaction of any value that came from a secret provider.
- You're swapping between local env-backed secrets for tests and a vault-backed provider in production.
When NOT to use¶
- The value is not secret — use
EnvironmentVariable(value=...)directly and skip the resolver. - You need dynamic per-request secrets rotated in-flight — build a
custom
SecretProviderimplementation instead ofEnvSecretProvider.
Recipe¶
"""23 — Secrets resolved through a real SecretResolver + in-process sandbox run.
What this shows
---------------
Two real primitives stitched together:
* :class:`SecretResolver` + :class:`EnvSecretProvider` turn a list of
:class:`EnvironmentVariable` records (some plain, some ``secret_ref``)
into a concrete env dict — the same path the runner uses when
building the env for a sandboxed unit.
* :class:`SandboxManager` runs a short ``python -c`` command inside
the ``local`` sandbox backend with that env. The env is scrubbed to
an allowlist and the secret arrives inside the subprocess.
* :class:`SecretRedactor` (via ``resolver.redactor()``) is the same
redactor the audit pipeline uses to strip secret values out of
payloads before they are persisted.
Run
---
uv run python examples/23_secrets_and_sandbox.py
"""
from __future__ import annotations
# Allow python examples/NN_name.py to find the sibling examples/_common.py helper.
import sys as _sys
from pathlib import Path as _Path
_sys.path.insert(0, str(_Path(__file__).resolve().parents[1]))
import os
import sys
from zeroth.integrations.execution import (
EnvironmentVariable,
SandboxBackendMode,
SandboxConfig,
SandboxManager,
)
from zeroth.platform.secrets import EnvSecretProvider, SecretResolver
def main() -> int:
# 1. Provider seeded with two secret refs. In production this is a
# vault-backed provider; here we inject a dict so the example is
# hermetic. No real secrets live in the file.
provider = EnvSecretProvider(
environment={
"secret/demo/api_key": "sk-demo-zeroth-23-example-1234",
"secret/demo/db_password": "p@ssw0rd-demo",
}
)
resolver = SecretResolver(provider)
# 2. EnvironmentVariable records mix plain values and secret refs.
# This is exactly the shape an ExecutableUnitManifest carries in
# its ``environment_variables`` field.
env_vars = [
EnvironmentVariable(name="DEMO_MODE", value="cookbook"),
EnvironmentVariable(name="DEMO_API_KEY", secret_ref="secret/demo/api_key"),
EnvironmentVariable(name="DEMO_DB_PASSWORD", secret_ref="secret/demo/db_password"),
]
resolved = resolver.resolve_environment_variables(env_vars)
print("resolved env (full):")
for key, value in resolved.items():
print(f" {key}={value}")
# 3. Run a real command inside SandboxManager's local backend. The
# allowlist keeps PATH; the overlay adds our demo env on top.
manager = SandboxManager(
base_env={
"HOME": os.environ.get("HOME", "/tmp"),
"PATH": os.environ.get("PATH", ""),
},
config=SandboxConfig(backend=SandboxBackendMode.LOCAL),
)
result = manager.run(
["python", "-c", "import os; print(os.environ.get('DEMO_API_KEY', 'missing'))"],
allowed_env_keys=["PATH"],
overlay_env=resolved,
cache_identity={"example": "secrets-sandbox"},
timeout_seconds=10,
)
print(f"\nsandbox returncode={result.returncode} stdout={result.stdout.strip()!r}")
assert result.returncode == 0
assert result.stdout.strip() == "sk-demo-zeroth-23-example-1234"
# 4. Redactor strips the resolved secret out of an audit payload
# before it reaches the audit store. This is the same redactor
# the framework uses automatically in production.
audit_payload = {
"tool": "http-post",
"headers": {"Authorization": f"Bearer {resolved['DEMO_API_KEY']}"},
"body": f"password={resolved['DEMO_DB_PASSWORD']}",
}
redacted = resolver.redactor().redact(audit_payload)
print("\nredacted audit payload (what the audit store sees):")
print(f" {redacted}")
assert "sk-demo-zeroth-23-example-1234" not in str(redacted)
assert "p@ssw0rd-demo" not in str(redacted)
print("\nsecrets-and-sandbox demo OK")
return 0
if __name__ == "__main__":
sys.exit(main())
How it works¶
SecretResolver.resolve_environment_variables walks the env var list,
fetches any secret_ref values in one batch via
SecretProvider.resolve_many, and raises KeyError if any are
missing. The resolver remembers every value it handed out; calling
resolver.redactor() returns a SecretRedactor seeded with those
values so recursive redact(payload) calls replace each one with a
[REDACTED:<ref>] marker.