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:
@@ -0,0 +1,32 @@
|
||||
// Reading the manager node's PUBLIC sealing key out of the bound facts the mesh delivers, and
|
||||
// writing a sealed refresh token in the wire shape mesh-control reads.
|
||||
//
|
||||
// **The public key is delivered, not derived.** The manager module holds no node key of its own
|
||||
// (novox/hq ADR 0050) — it is deliberately never given one. To seal a refresh token to this node it
|
||||
// needs the node's PUBLIC sealing key, and mesh-control puts that in the manager holder's bound facts
|
||||
// (`serves.manager_public_key`), safe to disclose because it is public. Both adoption and every
|
||||
// rotation read it from there.
|
||||
|
||||
import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
|
||||
import { dirname } from "node:path";
|
||||
|
||||
/** The manager node's public sealing key, from the bound facts file the mesh delivers. */
|
||||
export function managerPublicKey(boundFile: string): string {
|
||||
const raw = JSON.parse(readFileSync(boundFile, "utf8")) as { serves?: Record<string, unknown> };
|
||||
const key = raw.serves?.["manager_public_key"];
|
||||
if (typeof key !== "string" || key === "") {
|
||||
throw new Error(
|
||||
"the bound facts carry no manager_public_key — this node is not the licence's manager, or " +
|
||||
"the manager holder has not been delivered yet",
|
||||
);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
/** Write a sealed refresh token in the {sealed, manager_key} wire shape mesh-control reads. */
|
||||
export function writeSealedGrant(path: string, sealed: string, managerKey: string): void {
|
||||
mkdirSync(dirname(path), { recursive: true });
|
||||
const tmp = `${path}.tmp`;
|
||||
writeFileSync(tmp, JSON.stringify({ sealed, manager_key: managerKey }), { mode: 0o600 });
|
||||
renameSync(tmp, path);
|
||||
}
|
||||
Reference in New Issue
Block a user