PRD: Encrypted at-rest egress secrets (SecretProvider interim slice) #458

Merged
didericis merged 7 commits from prd-secret-provider-encrypted-env into main 2026-07-23 17:48:26 -04:00
Collaborator

Part of #355.

PRD: https://gitea.dideric.is/didericis/bot-bottle/src/commit/46e595ff/docs/prds/prd-new-secret-provider-encrypted-env.md

Summary

PRD-only draft for the interim slice of #355: persist per-bottle egress auth tokens encrypted, so they survive an infra-container restart, without regressing to plaintext secrets at rest and without foreclosing the per-request-minting end state.

The Design section is intentionally a TODO@didericis has the encryption/key-custody flow in mind and will fill it in on this branch. Everything else (problem, evidence, goals, non-goals, open questions) is drafted.

Motivating failure

Orchestrator._tokens (orchestrator/service.py:74-79) is in-memory only and deliberately never written to the registry DB. It is now the only piece of gateway state that does not survive a restart — the registry rows live on the container-only DB volume, and the CA has been host-resident since #450 / 2cd44cf7.

So any infra recreation silently strips every already-running bottle of its upstream credentials:

  • Checking out a branch that touches bot_bottle/**/*.py changes source_hash(), _source_current() sees the mismatch, and ensure_running() force-removes and recreates the infra container.
  • /resolve still succeeds — the bottle is active with its policy blob intact, including api.anthropic.com with auth_scheme: Bearer / token_env: EGRESS_TOKEN_0.
  • tokens_for() returns {}, so decide() blocks with egress: route for 'api.anthropic.com' declared auth but env var 'EGRESS_TOKEN_0' is unset — an 89-byte 403 on every request, mid-session.

Confirmed live on the macOS backend on 2026-07-21: two bottles running since 20:29/20:30 kept valid registry rows and policy across the 23:05 recreation and took 89-byte 403s from then on, while a bottle launched after the recreation egressed fine. Recovery today is relaunching every affected bottle.

This is a re-attachment blocker distinct from #443/#445 and from #450 — the CA and the gateway address were both correct. It is specifically the credential wipe.

Scope

PRD only, no implementation. Status stays Draft (CI assigns the number and flips status when implementation ships with it).

Explicitly not in this PRD: the full SecretProvider ABC, the manifest { provider: <name> } surface, user-extensible provider discovery, and per-request minting — all still #355. Also not here: a guard that refuses infra recreation while bottles are active, which is complementary and worth doing separately.

Part of #355. PRD: https://gitea.dideric.is/didericis/bot-bottle/src/commit/46e595ff/docs/prds/prd-new-secret-provider-encrypted-env.md ## Summary PRD-only draft for the interim slice of #355: persist per-bottle egress auth tokens **encrypted**, so they survive an infra-container restart, without regressing to plaintext secrets at rest and without foreclosing the per-request-minting end state. **The Design section is intentionally a TODO** — @didericis has the encryption/key-custody flow in mind and will fill it in on this branch. Everything else (problem, evidence, goals, non-goals, open questions) is drafted. ## Motivating failure `Orchestrator._tokens` (`orchestrator/service.py:74-79`) is in-memory only and deliberately never written to the registry DB. It is now the *only* piece of gateway state that does not survive a restart — the registry rows live on the container-only DB volume, and the CA has been host-resident since #450 / `2cd44cf7`. So any infra recreation silently strips every already-running bottle of its upstream credentials: - Checking out a branch that touches `bot_bottle/**/*.py` changes `source_hash()`, `_source_current()` sees the mismatch, and `ensure_running()` force-removes and recreates the infra container. - `/resolve` still succeeds — the bottle is `active` with its policy blob intact, including `api.anthropic.com` with `auth_scheme: Bearer` / `token_env: EGRESS_TOKEN_0`. - `tokens_for()` returns `{}`, so `decide()` blocks with `egress: route for 'api.anthropic.com' declared auth but env var 'EGRESS_TOKEN_0' is unset` — an 89-byte `403` on every request, mid-session. Confirmed live on the macOS backend on 2026-07-21: two bottles running since 20:29/20:30 kept valid registry rows and policy across the 23:05 recreation and took 89-byte `403`s from then on, while a bottle launched after the recreation egressed fine. Recovery today is relaunching every affected bottle. This is a re-attachment blocker distinct from #443/#445 and from #450 — the CA and the gateway address were both correct. It is specifically the credential wipe. ## Scope PRD only, no implementation. Status stays `Draft` (CI assigns the number and flips status when implementation ships with it). Explicitly **not** in this PRD: the full `SecretProvider` ABC, the manifest `{ provider: <name> }` surface, user-extensible provider discovery, and per-request minting — all still #355. Also not here: a guard that refuses infra recreation while bottles are active, which is complementary and worth doing separately.
didericis reviewed 2026-07-21 19:33:38 -04:00
@@ -0,0 +116,4 @@
host-side and guest-side components see different filesystems — that
asymmetry is available as a place to split custody.
- The agent must never be able to reach the key material. It is a separate
container with no control-plane token, which is the existing boundary.
Owner

Flow:

  1. Each agent gets a ENV_VAR_SECRET when it's started up with a random key, which is given to the agent as an env var (not logged/not persisted)
  2. This key is also used to encrypt the env vars given to the gateway during provision time into a new bottled_agent_secrets table in the orchestrator db (columns are bottled_agent_id, value, and type, where type is set to injected_env_var)
  3. When a new orchestrator spins up and we're reattaching to agents, we prompt the agent to send a reprovision_gateway request to the orchestrator which sends the ENV_VAR_SECRET and injects the env vars back into the gateway

We'll also want a follow up step (later PR) where we clean up those rows after the bottled_agent gets torn down.

Flow: 1. Each agent gets a `ENV_VAR_SECRET` when it's started up with a random key, which is given to the agent as an env var (not logged/not persisted) 2. This key is also used to encrypt the env vars given to the gateway during provision time into a new `bottled_agent_secrets` table in the orchestrator db (columns are `bottled_agent_id`, `value`, and `type`, where `type` is set to `injected_env_var`) 3. When a new orchestrator spins up and we're reattaching to agents, we prompt the agent to send a `reprovision_gateway` request to the orchestrator which sends the `ENV_VAR_SECRET` and injects the env vars back into the gateway We'll also want a follow up step (later PR) where we clean up those rows after the `bottled_agent` gets torn down.
Author
Collaborator

On it. Here is the implementation shape I am working with:

Step 1 (agent startup): ENV_VAR_SECRET is generated host-side in provision_bottle using secrets.token_urlsafe(32). It is passed to the agent via the compose env as a bare name (so the value does not land in the compose file on disk) and passed alongside tokens to register_bottle.

Step 2 (encrypt at provision time): Orchestrator.launch_bottle accepts env_var_secret; when tokens are present it encrypts json.dumps(tokens) with HMAC-SHA256 in CTR mode (stdlib-only, nonce || ciphertext as base64url) and stores the ciphertext in the new bottled_agent_secrets table (bottled_agent_id, value, type=injected_env_var). The key never touches the DB.

Step 3 (reprovision on restart): New reprovision-gateway MCP tool in the supervise server. The agent calls it with its $ENV_VAR_SECRET. The supervise server attributes the call (source IP + identity token), then POSTs {env_var_secret} to POST /bottles/<id>/reprovision_gateway on the orchestrator (with the control-plane auth it already holds). The orchestrator decrypts the stored ciphertext and restores _tokens[bottle_id].

The prompting mechanism — what causes the agent to call this after an orchestrator restart — I am leaving as a follow-up. The tool is available; AGENTS.md or the system prompt can document when to call it.

One question for you: should POST /bottles/<id>/reprovision_gateway require the control-plane token (trusted-caller only, always reached via the supervise server), or should it eventually be callable directly by agents over an unauthenticated gateway-proxied path? Keeping it control-plane-only is simpler and matches the existing trust model; I will go that way unless you say otherwise.

On it. Here is the implementation shape I am working with: **Step 1 (agent startup):** `ENV_VAR_SECRET` is generated host-side in `provision_bottle` using `secrets.token_urlsafe(32)`. It is passed to the agent via the compose env as a bare name (so the value does not land in the compose file on disk) and passed alongside `tokens` to `register_bottle`. **Step 2 (encrypt at provision time):** `Orchestrator.launch_bottle` accepts `env_var_secret`; when tokens are present it encrypts `json.dumps(tokens)` with HMAC-SHA256 in CTR mode (stdlib-only, `nonce || ciphertext` as base64url) and stores the ciphertext in the new `bottled_agent_secrets` table (`bottled_agent_id`, `value`, `type=injected_env_var`). The key never touches the DB. **Step 3 (reprovision on restart):** New `reprovision-gateway` MCP tool in the supervise server. The agent calls it with its `$ENV_VAR_SECRET`. The supervise server attributes the call (source IP + identity token), then POSTs `{env_var_secret}` to `POST /bottles/<id>/reprovision_gateway` on the orchestrator (with the control-plane auth it already holds). The orchestrator decrypts the stored ciphertext and restores `_tokens[bottle_id]`. The *prompting* mechanism — what causes the agent to call this after an orchestrator restart — I am leaving as a follow-up. The tool is available; AGENTS.md or the system prompt can document when to call it. One question for you: should `POST /bottles/<id>/reprovision_gateway` require the control-plane token (trusted-caller only, always reached via the supervise server), or should it eventually be callable directly by agents over an unauthenticated gateway-proxied path? Keeping it control-plane-only is simpler and matches the existing trust model; I will go that way unless you say otherwise.
Owner

@didericis-claude instead of a json, I wanted each individual key to be it's own row in the table. Forgot to add a key column indicating that. That makes it clearer what env vars were provided (which should mirror what's in the manifest), and it's good to have the labels visible for audit purposes.

RE the prompting mechanism, by "prompt" I don't literally mean we need claude to call /reprovision_gateway, I mean there should be some way we can trigger the running agent that has the env var to call the api. A dumb idea would be to start up a small process in the agent container that polls a file and then fires the event when we exec in there and write to it, but there's probably a better way to do that. And we should do that in this pr/not as a follow up. We do NOT want an mcp server for reprovisioning/that's unecessary.

@didericis-claude instead of a json, I wanted each individual key to be it's own row in the table. Forgot to add a `key` column indicating that. That makes it clearer what env vars were provided (which should mirror what's in the manifest), and it's good to have the labels visible for audit purposes. RE the prompting mechanism, by "prompt" I don't literally mean we need claude to call `/reprovision_gateway`, I mean there should be some way we can trigger the running agent that has the env var to call the api. A dumb idea would be to start up a small process in the agent container that polls a file and then fires the event when we exec in there and write to it, but there's probably a better way to do that. And we should do that in this pr/not as a follow up. We do NOT want an mcp server for reprovisioning/that's unecessary.
Author
Collaborator

Got it on both points.

Schema: Will add a key column so each env var is its own row — (bottled_agent_id, key, value, type). Env var names stay plaintext for the audit trail; only the values are encrypted.

Triggering reprovision: How about this — during provisioning we write a one-shot shell script into the agent container at a fixed path (e.g., /usr/local/bin/bb-reprovision). The script reads $ENV_VAR_SECRET from its own env and POSTs to a new plain HTTP (non-MCP) endpoint POST /reprovision on the supervise server at $BOT_BOTTLE_SUPERVISE_URL, with the identity token as a header for attribution. The supervise server decrypts and calls the orchestrator.

No background process, no polling, no MCP. The host-side reattach logic (or a new reprovision-bottle CLI command) just runs docker exec <container> /usr/local/bin/bb-reprovision for each bottle that lost its tokens. One exec, done.

Does that work for you, or do you want the exec avoided entirely (e.g., trigger from a Unix socket or named pipe the container already has open)?

Got it on both points. **Schema:** Will add a `key` column so each env var is its own row — `(bottled_agent_id, key, value, type)`. Env var names stay plaintext for the audit trail; only the values are encrypted. **Triggering reprovision:** How about this — during provisioning we write a one-shot shell script into the agent container at a fixed path (e.g., `/usr/local/bin/bb-reprovision`). The script reads `$ENV_VAR_SECRET` from its own env and POSTs to a new plain HTTP (non-MCP) endpoint `POST /reprovision` on the supervise server at `$BOT_BOTTLE_SUPERVISE_URL`, with the identity token as a header for attribution. The supervise server decrypts and calls the orchestrator. No background process, no polling, no MCP. The host-side reattach logic (or a new `reprovision-bottle` CLI command) just runs `docker exec <container> /usr/local/bin/bb-reprovision` for each bottle that lost its tokens. One exec, done. Does that work for you, or do you want the exec avoided entirely (e.g., trigger from a Unix socket or named pipe the container already has open)?
Owner

@didericis-claude does that exec have access to the env var? Was thinking we wouldn't be able to get the env var from exec. If we can then we don't need the endpoint, we can simply call exec, read the env var into a variable, and then reprovision the gateway during gateway reboot.

@didericis-claude does that exec have access to the env var? Was thinking we wouldn't be able to get the env var from `exec`. If we can then we don't need the endpoint, we can simply call `exec`, read the env var into a variable, and then reprovision the gateway during gateway reboot.
Author
Collaborator

Yes — docker exec runs inside the container and inherits its full environment, so docker exec <container> printenv ENV_VAR_SECRET works fine.

And you are right: that means no endpoint in the container and no script to provision. The host-side reattachment path (triggered on infra restart or ensure_running) can do the whole thing itself:

  1. For each registered bottle that has a stored secret: docker exec <container> printenv ENV_VAR_SECRET
  2. POST /bottles/<id>/reprovision_gateway on the orchestrator control plane (trusted caller, control-plane token) with the result
  3. Orchestrator decrypts the stored ciphertext and restores _tokens

Clean — no sidecar, no supervise endpoint, no script. The supervise server stays out of it entirely. I will wire this into OrchestratorService.ensure_running (or a dedicated reprovision_all step that runs after the infra container is healthy) so it happens automatically on every gateway reboot.

Yes — `docker exec` runs inside the container and inherits its full environment, so `docker exec <container> printenv ENV_VAR_SECRET` works fine. And you are right: that means no endpoint in the container and no script to provision. The host-side reattachment path (triggered on infra restart or `ensure_running`) can do the whole thing itself: 1. For each registered bottle that has a stored secret: `docker exec <container> printenv ENV_VAR_SECRET` 2. `POST /bottles/<id>/reprovision_gateway` on the orchestrator control plane (trusted caller, control-plane token) with the result 3. Orchestrator decrypts the stored ciphertext and restores `_tokens` Clean — no sidecar, no supervise endpoint, no script. The supervise server stays out of it entirely. I will wire this into `OrchestratorService.ensure_running` (or a dedicated `reprovision_all` step that runs after the infra container is healthy) so it happens automatically on every gateway reboot.
Author
Collaborator

Done — pushed as 01cee05.

What landed:

  • bot_bottle/orchestrator/secret_store.pynew_env_var_secret(), encrypt_value(), decrypt_value() (HMAC-SHA256 CTR, stdlib-only)
  • bottled_agent_secrets table (migrations v4+v5): (bottled_agent_id, key, value, type) — env-var name plaintext, value encrypted
  • Orchestrator.launch_bottle accepts env_var_secret and writes one row per token
  • POST /bottles/<id>/reprovision_gateway on the control plane
  • ENV_VAR_SECRET injected into agent container via compose subprocess env (bare name — value never hits the compose file on disk)
  • _reprovision_running_bottles runs inside launch_consolidated after ensure_running: for each registered bottle alive on the gateway network, docker exec … printenv ENV_VAR_SECRET + reprovision call. No sidecar, no polling.

All 111 existing unit tests pass.

Done — pushed as `01cee05`. **What landed:** - `bot_bottle/orchestrator/secret_store.py` — `new_env_var_secret()`, `encrypt_value()`, `decrypt_value()` (HMAC-SHA256 CTR, stdlib-only) - `bottled_agent_secrets` table (migrations v4+v5): `(bottled_agent_id, key, value, type)` — env-var name plaintext, value encrypted - `Orchestrator.launch_bottle` accepts `env_var_secret` and writes one row per token - `POST /bottles/<id>/reprovision_gateway` on the control plane - `ENV_VAR_SECRET` injected into agent container via compose subprocess env (bare name — value never hits the compose file on disk) - `_reprovision_running_bottles` runs inside `launch_consolidated` after `ensure_running`: for each registered bottle alive on the gateway network, `docker exec … printenv ENV_VAR_SECRET` + reprovision call. No sidecar, no polling. All 111 existing unit tests pass.
didericis force-pushed prd-secret-provider-encrypted-env from 350b1def0d to 3b6f68d26d 2026-07-21 22:57:33 -04:00 Compare
didericis-codex requested changes 2026-07-21 23:12:30 -04:00
didericis-codex left a comment
Collaborator

Found three merge-blocking issues (CI failures intentionally ignored):

  1. [P0] Restart recovery does not work on the default macOS backend. The generated key is returned in LaunchContext, but the macOS launch path never injects ctx.env_var_secret into the container or its exec environment, and only the Docker launcher invokes _reprovision_running_bottles. After the host launcher exits, the sole key is gone; an infra recreation therefore still leaves existing macOS bottles without credentials—the exact motivating failure for this PR. The Firecracker backend has the same incomplete plumbing. Relevant locations: bot_bottle/backend/macos_container/consolidated_launch.py:149-156, bot_bottle/backend/docker/consolidated_launch.py:91-169, and bot_bottle/backend/firecracker/consolidated_launch.py:72-84.

  2. [P1] Stored secrets are never removed with their bottle. teardown_bottle, reconcile, and failed-launch rollback remove the registry record and in-memory tokens but never call the newly added delete_agent_secrets. Since the secrets table has no cascading foreign key, ciphertext rows survive indefinitely, violating success criterion 5 and accumulating orphaned credentials. See bot_bottle/orchestrator/service.py:117-155.

  3. [P1] The encryption format provides no authentication. The stored blob is only nonce || CTR ciphertext; there is no MAC/tag. Database corruption or modification therefore changes decrypted credentials undetectably. A wrong key is also accepted whenever the resulting bytes happen to be valid UTF-8, after which reprovision_from_secret reports success and installs garbage tokens. Please use authenticated encryption, or derive separate encryption/MAC keys and verify an encrypt-then-MAC tag before decrypting. See bot_bottle/orchestrator/secret_store.py:53-91.

The focused unit tests pass, but they do not exercise the missing backend integration or lifecycle cleanup.

Found three merge-blocking issues (CI failures intentionally ignored): 1. **[P0] Restart recovery does not work on the default macOS backend.** The generated key is returned in `LaunchContext`, but the macOS launch path never injects `ctx.env_var_secret` into the container or its exec environment, and only the Docker launcher invokes `_reprovision_running_bottles`. After the host launcher exits, the sole key is gone; an infra recreation therefore still leaves existing macOS bottles without credentials—the exact motivating failure for this PR. The Firecracker backend has the same incomplete plumbing. Relevant locations: `bot_bottle/backend/macos_container/consolidated_launch.py:149-156`, `bot_bottle/backend/docker/consolidated_launch.py:91-169`, and `bot_bottle/backend/firecracker/consolidated_launch.py:72-84`. 2. **[P1] Stored secrets are never removed with their bottle.** `teardown_bottle`, `reconcile`, and failed-launch rollback remove the registry record and in-memory tokens but never call the newly added `delete_agent_secrets`. Since the secrets table has no cascading foreign key, ciphertext rows survive indefinitely, violating success criterion 5 and accumulating orphaned credentials. See `bot_bottle/orchestrator/service.py:117-155`. 3. **[P1] The encryption format provides no authentication.** The stored blob is only `nonce || CTR ciphertext`; there is no MAC/tag. Database corruption or modification therefore changes decrypted credentials undetectably. A wrong key is also accepted whenever the resulting bytes happen to be valid UTF-8, after which `reprovision_from_secret` reports success and installs garbage tokens. Please use authenticated encryption, or derive separate encryption/MAC keys and verify an encrypt-then-MAC tag before decrypting. See `bot_bottle/orchestrator/secret_store.py:53-91`. The focused unit tests pass, but they do not exercise the missing backend integration or lifecycle cleanup.
Owner

@didericis-codex

finding 1: fix it for both macos and firecracker backends

finding 2: we actually want teardown_bottle and reconcile to maintain records of agents, and simply update an events log with statuses. See conversation here. Cleaning up secrets was explicitly called out in here as a later step. Logic should be to remove the secrets once the bottled agent gets a status of removed. But we're ok to defer that.

finding 3: too complicated, do not implement this

@didericis-codex finding 1: fix it for both macos and firecracker backends finding 2: we actually want `teardown_bottle` and `reconcile` to maintain records of agents, and simply update an events log with statuses. See conversation [here](https://gitea.dideric.is/didericis/bot-bottle-platform/pulls/7#issuecomment-4931). Cleaning up secrets was explicitly called out in [here](https://gitea.dideric.is/didericis/bot-bottle/pulls/458#issuecomment-4974) as a later step. Logic should be to remove the secrets once the bottled agent gets a status of `removed`. But we're ok to defer that. finding 3: too complicated, do not implement this
Collaborator

Acknowledged.

  1. Finding 1 remains blocking: this should be completed for both macOS-container and Firecracker, including durable key delivery and an automatic backend-appropriate reprovision path after infra restart.

  2. I withdraw finding 2 as a blocker given the intended retained-agent/event-log lifecycle and the explicit decision to defer deletion until an agent reaches removed. The current PRD success criterion saying teardown/reap destroys stored secrets should be adjusted or marked as follow-up so the shipped behavior and requirements do not contradict each other.

  3. Agreed that ciphertext authentication is unnecessary complexity for this interim threat model. The requirement is confidentiality when the registry DB is disclosed without the per-bottle key; database corruption or an incorrect recovery key simply yields unusable upstream credentials and fails closed. Keep the existing nonce || ciphertext format without an authentication tag.

I’ll re-review the updated head once those changes are pushed.

Acknowledged. 1. Finding 1 remains blocking: this should be completed for both macOS-container and Firecracker, including durable key delivery and an automatic backend-appropriate reprovision path after infra restart. 2. I withdraw finding 2 as a blocker given the intended retained-agent/event-log lifecycle and the explicit decision to defer deletion until an agent reaches `removed`. The current PRD success criterion saying teardown/reap destroys stored secrets should be adjusted or marked as follow-up so the shipped behavior and requirements do not contradict each other. 3. Agreed that ciphertext authentication is unnecessary complexity for this interim threat model. The requirement is confidentiality when the registry DB is disclosed without the per-bottle key; database corruption or an incorrect recovery key simply yields unusable upstream credentials and fails closed. Keep the existing `nonce || ciphertext` format without an authentication tag. I’ll re-review the updated head once those changes are pushed.
didericis-codex approved these changes 2026-07-22 14:36:28 -04:00
didericis-codex left a comment
Collaborator

Re-reviewed at 74f79cd6. The prior backend-recovery blocker is resolved for Docker, macOS-container, and Firecracker; the deferred secret-deletion criterion is now aligned with the intended retained-record lifecycle. Focused recovery/orchestrator tests pass, and I found no new actionable issues.

Re-reviewed at `74f79cd6`. The prior backend-recovery blocker is resolved for Docker, macOS-container, and Firecracker; the deferred secret-deletion criterion is now aligned with the intended retained-record lifecycle. Focused recovery/orchestrator tests pass, and I found no new actionable issues.
didericis-codex approved these changes 2026-07-22 14:36:59 -04:00
didericis added 7 commits 2026-07-23 17:39:24 -04:00
The orchestrator holds each bottle's egress auth tokens in process memory
only, so recreating the infra container strips every already-running
bottle of its upstream credentials. The registry row and the gateway CA
both survive; the tokens do not, so /resolve serves an intact policy with
an empty token map and the addon fails closed on `token_env unset`.

Drafts the interim slice of #355: persist the tokens encrypted so they
survive a restart, without regressing to plaintext at rest and without
foreclosing the per-request minting end state. Design section is left for
the author to fill in.
Implements the interim secret-provider design (PRD prd-new-secret-provider):
each agent receives a random ENV_VAR_SECRET injected into its container env
at launch. The host uses this key to encrypt each egress auth token value
(HMAC-SHA256 CTR mode, stdlib-only) and store it in a new
bottled_agent_secrets table (one row per env var, key column plaintext for
auditing). The key never touches the DB.

On infra container restart the in-memory token map is lost. launch_consolidated
now calls _reprovision_running_bottles after ensure_running: for each
registered bottle still alive on the gateway network it execs
`printenv ENV_VAR_SECRET` into the agent container and posts the result to the
new POST /bottles/<id>/reprovision_gateway control-plane endpoint, which
decrypts the stored rows and restores _tokens — no manual intervention needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add unit coverage for the encrypted at-rest egress secrets: the
secret_store round-trip (new_env_var_secret/encrypt_value/decrypt_value)
and the registry/service wiring that injects ENV_VAR_SECRET.

Recovered from the bot-bottle-claude-agent-1 VM after the CI runner's
disk filled and forced its rootfs read-only; committed work was already
on origin, these were the agent's uncommitted changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
refactor(secrets): keep ciphertext format minimal
tracker-policy-pr / check-pr (pull_request) Successful in 15s
test / integration-docker (pull_request) Successful in 35s
test / unit (pull_request) Successful in 46s
test / integration-firecracker (pull_request) Successful in 4m17s
test / coverage (pull_request) Successful in 16s
test / publish-infra (pull_request) Has been skipped
test / integration-docker (push) Successful in 14s
prd-number / assign-numbers (push) Failing after 31s
test / unit (push) Successful in 41s
Update Quality Badges / update-badges (push) Successful in 44s
lint / lint (push) Successful in 1m0s
test / integration-firecracker (push) Successful in 4m59s
test / coverage (push) Successful in 24s
test / publish-infra (push) Successful in 1m49s
26d0f5e3b2
didericis force-pushed prd-secret-provider-encrypted-env from 74f79cd690 to 26d0f5e3b2 2026-07-23 17:39:24 -04:00 Compare
didericis merged commit 26d0f5e3b2 into main 2026-07-23 17:48:26 -04:00
didericis deleted branch prd-secret-provider-encrypted-env 2026-07-23 17:48:32 -04:00
Sign in to join this conversation.