carlos / platform Public

Provisioning never mints the app-secrets keypair, so sealed secrets are silently unavailable on a fresh deployment Issue open

Migrated from github.com/carlosframework/platform issue #190, opened 2026-08-14. GitHub issues are closed for this repo; see AGENTS.md, "Work is tracked on amadan, not GitHub".

Still outstanding: Provisioning never mints the app-secrets keypair, and nothing warns that sealed secrets are therefore unavailable.

---

What happened

On carlos.exciting.io (a deployment stood up the normal way), carlos secrets set refuses:

$ carlos secrets set -app eleven "LCHAT_ADMIN_TOKEN=…"
no CARLOS_SECRETS_PUBKEY in env and this console does not serve one — set the
env var, or set CARLOS_SECRETS_PUBKEY in the console's environment

The refusal is clear and correct. The problem is upstream of it: nothing in provisioning ever mints or installs the keypair, so every deployment starts without sealed secrets and only finds out when someone reaches for them.

Grepping for it:

  • carlos bootstrap stages units, dirs, the service user, timers — not /etc/carlos/host.env, which infra/remote-host.md step 3 has the operator write by hand.
  • CARLOS_SECRETS_KEY appears nowhere under infra/ — no module, no template, no tofu variable.
  • carlos secrets genkey exists and is purely local. Nothing calls it, and no doc step says "run this once per deployment, put the private half here and the public half there".

So the two placements a working deployment needs — CARLOS_SECRETS_PUBKEY in the console's environment, CARLOS_SECRETS_KEY in every host's /etc/carlos/host.env — are known only to whoever has read appconfig_cli.go and internal/host/agent.go.

Why it bites harder than a missing feature

Plain carlos env writes to the bucket whether or not the key exists, and delivery on a box is gated on CARLOS_SECRETS_KEY (internal/host/agent.go configSyncLoop). A deployment in this state has a config surface that looks complete: env set succeeds, env list shows the value. Whether it reaches a box depends on a variable nobody was told to set. On this deployment delivery does work, so the key is evidently present on the host but not on the console — the two halves drifted, which is exactly what nothing-provisions-them predicts.

Meanwhile the only remaining place for a credential is carlos env, i.e. plaintext in the deployment bucket. That is the actual cost: not a missing convenience, but a live deployment where the sealed path is closed and the unsealed one is wide open.

What I think it should do

Mint at deployment-creation time, the way the release signing key already is — one keypair per deployment, private half to wherever host.env comes from, public half into the console's environment, both recorded as deployment facts rather than operator lore.

Failing that, two smaller things that would each have caught this:

  1. carlos bootstrap (and the tofu modules) emit the pair, or refuse to finish silently without one, naming carlos secrets genkey and both placements.
  2. Something reports the drift. carlos status -live, or a carlos doctor-style check, saying "console serves no secrets pubkey; sealed secrets unavailable" beats discovering it at the moment you need to store an API key.

Happy to send a PR for either if you tell me which shape you want.

Repro

carlos auth login -console https://carlos.exciting.io
carlos secrets set -app <any> FOO=bar     # refused, as above
carlos env set -app <any> FOO=bar         # succeeds, plaintext in the bucket