Files
mesh-catalog/modules/anthropic-manager/test/sealedbox.test.ts
T
jschoubben 4c98bee043 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
2026-09-07 01:55:19 +02:00

37 lines
1.7 KiB
TypeScript

import { test } from "node:test";
import assert from "node:assert/strict";
import { generateKeyPairSync } from "node:crypto";
import { seal } from "../sealedbox.ts";
// The definitive proof that this seal interoperates with Go's box.OpenAnonymous (the host's Unseal
// and mesh-control's secrets.Seal/Open) is a cross-language test in mesh-control
// (internal/secrets/sealedbox_xcheck_test.go), which opens a fixture this module's seal() produced.
// These tests hold the TypeScript side: the output has the crypto_box_seal shape, and it is
// randomised so a rotation that changed nothing looks nothing like one that changed everything.
/** A node public key as the mesh records it: raw 32-byte X25519, standard base64. */
function aNodePublicKey(): string {
const kp = generateKeyPairSync("x25519");
const x = (kp.publicKey.export({ format: "jwk" }) as { x: string }).x;
return Buffer.from(x, "base64url").toString("base64");
}
test("a seal has the crypto_box_seal shape: ephemeralPub(32) + tag(16) + ciphertext(len)", () => {
const pub = aNodePublicKey();
const msg = Buffer.from("rt-a-refresh-token", "utf8");
const blob = Buffer.from(seal(new Uint8Array(msg), pub), "base64");
// 32 (ephemeral public key) + 16 (Poly1305 tag) + message length.
assert.equal(blob.length, 32 + 16 + msg.length);
});
test("two seals of the same value differ — a fresh ephemeral key each time", () => {
const pub = aNodePublicKey();
const msg = new Uint8Array(Buffer.from("rt-a-refresh-token", "utf8"));
assert.notEqual(seal(msg, pub), seal(msg, pub));
});
test("a public key that is not 32 bytes is refused before anything is sealed", () => {
assert.throws(() => seal(new Uint8Array([1, 2, 3]), Buffer.from("short").toString("base64")));
});