A cross-repo trace showed nothing writes the provisioner's grant-request files, nothing reads its sealed credentials, and no consumer unseals — while the mesh already mints and delivers provider/consumer credentials asymmetrically with no shared key. The fix is to drop the symmetric seal and have providers consume the mesh-minted password, a breaking provider-contract change that wants an ADR. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
5.4 KiB
status, opened, located-in, fixed-by, amended-design
| status | opened | located-in | fixed-by | amended-design | ||
|---|---|---|---|---|---|---|
| open | 2026-09-04 |
|
A provider's provisioner seals with a key the mesh has no way to deliver — and does not need to
What was observed
Building the vertical slice for the module runtime (the module runs its own code as its own
process under its own account), a provider module — one that stands up a per-consumer
resource and hands back a credential — was assigned to a node and run as a broker-bound
runtime. The runtime hosts the module's provisioner (the sdk's runProvisioner), and the
harness opens by reading a seal key from $MESH_SEAL_KEY, failing immediately without
one. Every credential it produces for a consumer is sealed to that key with the sdk's
symmetric seal() (AES-256-GCM, mesh-sdk/src/primitives/index.ts) before being written.
Nothing in the mesh sets $MESH_SEAL_KEY. It is read in exactly two places in the sdk and
set nowhere — no manifest, no control-plane code, no host code. So a provider runtime, as
delivered, aborts at start-up. The slice proved the mechanism only by setting a lab-local key
in the manifest by hand.
What a trace of the credential path turned up
The seal key is not a missing delivery. The whole symmetric-seal provisioner is orphaned, and it duplicates — badly — a job the mesh already does.
runProvisionerreads request files named*.grant.json. Nothing writes those.- It writes sealed credential files named
<consumer>.<resource>.credential. Nothing reads those — not the host, not the control plane. The host reports applied-resource digests upward and never ships credentials; the control plane has no reference to that filename. - No consumer ever calls the symmetric
unseal(). Consumers receive plaintext.
Meanwhile the mesh already carries a provider→consumer credential across nodes, with no shared key anywhere:
- The control plane mints the password once (
secrets.Make) and seals it twice, asymmetrically —ForConsumerto the consumer node's X25519 public key,ForProviderto the provider node's (mesh-control/internal/secrets/seal.go,mesh-host/internal/identity/ sealing.go, NaCl box). - Each host opens its own copy with its own private key on the machine; the plaintext exists
only for the length of one function call (
mesh-host/internal/apply/apply.go, the${secret:name}substitution — ADR 0024's "the host is the only thing that ever holds both"). servescarries no credential and says so;receives/boundtell each side where its sealed secret is, never the value.
The two models also contradict each other. The sdk's seal() comment says the key is "a
per-node passphrase the host holds"; the host holds no such passphrase — it holds an X25519
private key, and the control plane's own code refuses a shared symmetric key on principle:
"a key both ends hold is a key the mesh would have to distribute, which is this problem again
one level down" (secrets/seal.go). A symmetric MESH_SEAL_KEY shared between a provider
node and a consumer node is exactly the thing the mesh was built not to have.
And the provisioner's model is wrong in a second way: its adapter generates its own
password (generatePassword()) and creates the resource with it — a different password from
the one the mesh mints and hands the consumer. Even with a seal key delivered, a consumer
would authenticate with the mesh's password against a resource created with the provisioner's.
Why it matters beyond this instance
The module-runtime model is what every provider now follows — a cache, an object store, a database. As written, each carries a provisioner that cannot start (no key), and that, if it did, would create resources with the wrong password and seal them for a reader that does not exist. The rule the design states — "a consumer receives a sealed credential and unseals it" — is enforced by nothing, because no consumer unseals and no shared key exists to unseal with.
The fix this points to
Not "deliver the seal key." Align the provider with the mesh's existing credential path and delete the symmetric seal:
- The provisioner should stop generating a password and stop sealing. It should read the
per-consumer password the mesh already mints and delivers to the provider (
ForProvider, unsealed onto the machine by the host), and create the resource with that password. - The consumer already receives the matching password as plaintext its own host wrote — no change needed there.
runProvisioner'ssealKey,seal(),writeSealedCredential, and the*.grant.json/*.credentialfile dance come out; what replaces them is a reconcile driven by the contributions file the mesh already writes to the provider'sreceivespath.
Open questions
- Does the mesh mint and deliver
ForProviderfor a served interface today (redis-cache, postgres-database), or only for the own-secret case the trace followed? Confirm the provider actually receives each consumer's password before reworking the adapter around it. - What is the adapter contract after the change —
create(consumer, password)rather thancreate(grant) -> Credential? That is a breaking change to the four providers (redis, postgres, minio, umami) and wants an ADR, since it changes what a provider module is. - Is any of the symmetric
seal()/unseal()primitive still used for anything legitimate, or does it leave with the provisioner?