anthropic-manager: seal the refresh token to the node key, do no crypto to open

The manager module drops its bespoke ECIES at-rest envelope and the node-private-key mount.
A module is never given a node's private key, so it cannot open an envelope -- the refresh
token is now delivered to it as cleartext by the host, unsealed from an ordinary sealed box.

  - sealedbox.ts: a dependency-free NaCl crypto_box_seal (node:crypto for X25519, transcribed
    XSalsa20-Poly1305 and BLAKE2b-24), byte-compatible with Go's box.SealAnonymous. It SEALS
    only -- opening is the host's job. Proven by a cross-language test in mesh-control.
  - adopt: reads the node's PUBLIC key from the delivered bound facts and seals the operator's
    refresh token to it, handing out only the box.
  - refresh: reads the refresh token as cleartext the host mounted, calls the vendor, re-seals
    a rotated token to the node's public key, submits only { access token, box }.
  - module.json: a model-access holder now -- binds the facts, binds the refresh token as a
    sealed secret; no keys dir, no MESH_NODE_SEALING_* mount.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-07 01:55:19 +02:00
parent c206e2e11e
commit 4c98bee043
9 changed files with 611 additions and 310 deletions
+11 -17
View File
@@ -4,6 +4,15 @@
"capabilities": [
"container-runtime"
],
"requires": [
"model-access"
],
"binds": {
"model-access": "/var/lib/mesh/anthropic-manager/model.json"
},
"secrets": {
"model-access": "/var/lib/mesh/anthropic-manager/refresh-token"
},
"own-secrets": {
"broker": "/var/lib/mesh/anthropic-manager/broker"
},
@@ -17,26 +26,12 @@
"path": "/var/lib/mesh/anthropic-manager",
"mode": "0700"
},
{
"id": "keys",
"type": "directory",
"path": "/var/lib/mesh/anthropic-manager/keys",
"mode": "0700"
},
{
"id": "out",
"type": "directory",
"path": "/var/lib/mesh/anthropic-manager/out",
"mode": "0700"
},
{
"id": "config",
"type": "file",
"path": "/var/lib/mesh/anthropic-manager/config.json",
"merge": "json",
"content": "{}",
"mode": "0600"
},
{
"id": "refresh",
"type": "container",
@@ -55,9 +50,8 @@
"env": {
"MESH_BROKER_FILE": "/run/secrets/broker",
"MESH_ANTHROPIC_LICENCE": "personal",
"MESH_ANTHROPIC_GRANT_FILE": "/run/state/grant.json",
"MESH_NODE_SEALING_PUBLIC_FILE": "/run/state/keys/sealing.pub",
"MESH_NODE_SEALING_PRIVATE_FILE": "/run/state/keys/sealing.priv",
"MESH_MODEL_ACCESS_SECRET_FILE": "/run/state/refresh-token",
"MESH_MODEL_ACCESS_BIND_FILE": "/run/state/model.json",
"MESH_ANTHROPIC_ACCESS_OUT": "/run/state/out/access-token",
"MESH_ANTHROPIC_GRANT_OUT": "/run/state/out/grant.json",
"MESH_ANTHROPIC_USAGE_OUT": "/run/state/out/usage.json",