anthropic-bed: prove the host unseals the refresh token, no node-key stub

The bed follows the reworked flow: the manager module seals the refresh token to the node's
PUBLIC key, the HOST unseals it and mounts the cleartext at the manager's bound path, and the
refresh reads that cleartext -- no fake node key pair is mounted any more, the host uses its
own real sealing key.

  - the manager is a model-access holder deployed first, so its bound facts (carrying the node
    public key) are delivered; the consumer is added only once an access token exists to seal.
  - adopt reads the node public key from the bound facts; the test asserts the host mounts the
    cleartext refresh token for the manager, and that it reaches nowhere on the consuming node.
  - the refresh_grant assertion reads { sealed, manager_key }.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-07 01:55:28 +02:00
parent 6be5072565
commit 71bea08f3b
2 changed files with 160 additions and 139 deletions
+151 -132
View File
@@ -1,28 +1,40 @@
/**
* The mesh plays out the model-access refreshable-grant flow for Anthropic end to end, with the
* vendor's OAuth endpoint STUBBED (novox/hq ADR 0050, Phase C). It proves the one property the
* carve-out rests on, and the reviewer will scrutinise it: the refresh token is opened ONLY on the
* manager node, the control plane is handed only the ACCESS token in the clear (plus an opaque
* re-sealed refresh envelope), and a consuming node writes an access-token-only credential and is
* never delivered a refresh token — nowhere on the machine, nowhere in the control plane's database.
* vendor's OAuth endpoint STUBBED (novox/hq ADR 0050). It proves the one property the carve-out rests
* on, and the reviewer will scrutinise it: the refresh token is delivered ONLY to the manager node —
* as an ordinary sealed credential the HOST unseals — the control plane is handed only the ACCESS
* token in the clear (plus an opaque re-sealed refresh box), and a consuming node writes an
* access-token-only credential and is never delivered a refresh token — nowhere on the machine,
* nowhere in the control plane's database.
*
* The flow, driven deterministically (the runtime images are the real ones the host pulled; the
* OAuth endpoint is a tiny node stub the test runs from the same image, so no vendor is reached):
* 1. adopt: the manager runtime seals a refresh token at rest to a node key pair (the seal runs on
* the manager node; the plaintext never leaves it). The sealed envelope is stored via
* `licence set-grant` — the control plane stores ciphertext it cannot open.
* 2. refresh: the manager runtime OPENS the envelope with the node's own key, calls the stub token
* endpoint, and writes out ONLY { access token, re-sealed refresh envelope }.
* 3. submit: `licence submit-refresh` hands the control plane those two things — never the refresh
* **What changed from the earlier cut, and why this is simpler.** The refresh token no longer rides a
* bespoke at-rest envelope the module opens with a node private key the mesh must somehow place — a
* module is never given a node's private key, so that path could not exist. It rides the ORDINARY
* sealed-delivery path instead: mesh-control (via the manager module at adoption) seals it to the
* manager node's PUBLIC key, and the HOST unseals it with that node's real private key and mounts the
* cleartext at the manager module's bound secret path — exactly as a consumer's db password arrives.
* So there is no fake node key pair mounted here any more; the host's own real sealing key does the
* unsealing, and the manager module does no crypto beyond re-sealing a rotated token to the same
* public key.
*
* The flow, driven deterministically (the runtime images are the real ones the host pulled; the OAuth
* endpoint is a tiny node stub run from the same image, so no vendor is reached):
* 1. deploy the manager and adopt: the manager holder is delivered its bound facts (carrying the
* node's PUBLIC sealing key). The manager runtime seals the operator's refresh token to that key
* and hands the box to `licence set-grant` — the control plane stores ciphertext it cannot open.
* 2. the host unseals: a push delivers the sealed refresh token to the manager, and the host mounts
* the cleartext at the manager module's secret path — the one place a refresh token is readable.
* 3. refresh: the manager runtime reads that cleartext, calls the stub token endpoint, re-seals a
* rotated refresh token to the node's public key, and writes out ONLY { access token, sealed box }.
* 4. submit: `licence submit-refresh` hands the control plane those two things — never the refresh
* token in the clear — and it seals the access token to the consumer holder.
* 4. deliver: a push delivers the sealed access token to the consumer; the consumer runtime writes
* 5. deliver: a push delivers the sealed access token to the consumer; the consumer runtime writes
* ~/.claude/.credentials.json, access-token-only.
*
* STUBBED, and flagged in the report: (a) the vendor OAuth endpoint (a node stub); (b) the manager
* node's private sealing key, mounted as a test key pair — production needs a host capability to
* place the node private key where a manager module reads it, which mesh-host does not have today;
* (c) the submit transport (the test invokes `mesh-control licence submit-refresh` on the manager's
* output, standing in for the authenticated cross-node call a manager node would make).
* STUBBED, and flagged in the report: (a) the vendor OAuth endpoint (a node stub); (b) the submit
* transport (the test invokes `mesh-control licence submit-refresh` on the manager's output, standing
* in for the authenticated cross-node call a manager node would make). The node-private-key stub of
* the earlier cut is GONE — the host uses its own real key.
*
* MESH_LAB_HOST_BINARY=.../mesh-host MESH_LAB_BUNDLE=.../examples/substrate-first-node.lock
* Build both runtime images into the local daemon first:
@@ -33,7 +45,6 @@
import { test, before, after } from "node:test";
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { generateKeyPairSync } from "node:crypto";
import { loadScenario } from "../../src/declaration/parse.ts";
import { raise } from "../../src/lifecycle/raise.ts";
import { destroy, exec } from "../../src/lifecycle/operate.ts";
@@ -56,10 +67,10 @@ const SCENARIO = "anthropic-bed";
const MACHINE = "anchor";
// The fake tokens the flow moves. The whole test is: the second reaches the consumer, the first never
// does.
const REFRESH_TOKEN = "rt-lab-refresh-must-never-be-delivered";
// does — except on the manager node, which is allowed to read it.
const REFRESH_TOKEN = "rt-lab-refresh-only-the-manager-may-read";
const ACCESS_TOKEN = "at-lab-access-token-minted-by-the-stub";
const ROTATED_REFRESH = "rt-lab-rotated-still-must-never-be-delivered";
const ROTATED_REFRESH = "rt-lab-rotated-still-only-the-manager";
let instanceId = "";
let stocked: string[] = [];
@@ -113,16 +124,6 @@ function tokenFrom(said: string): string {
return found;
}
/** A node key pair as the mesh records it: raw 32-byte X25519 keys, standard base64. */
function nodeKeyPair(): { pub: string; priv: string } {
const kp = generateKeyPairSync("x25519");
const std = (b64url: string) => Buffer.from(b64url, "base64url").toString("base64");
return {
pub: std((kp.publicKey.export({ format: "jwk" }) as { x: string }).x),
priv: std((kp.privateKey.export({ format: "jwk" }) as { d: string }).d),
};
}
/** The local image id the host pulled for a runtime, so the test can drive it deterministically. */
async function imageId(substr: string): Promise<string> {
const out = (await must(`docker images --no-trunc --format '{{.ID}} {{.Repository}}' | grep ${quote(substr)} | head -1`)).trim();
@@ -191,23 +192,29 @@ test("model access refreshes on the manager node and delivers only the access to
const managerImage = pinned("mesh-runtime-anthropic-manager");
const consumerImage = pinned("mesh-runtime-anthropic-consumer");
// --- the licence, its manager, and the consumer holder ------------------------------------------
// --- the licence, and the manager as its holder ------------------------------------------------
// The manager module is a HOLDER, named the licence's manager. It is delivered the refresh token;
// its bound facts carry the node's PUBLIC sealing key, which is what adoption seals to. The consumer
// holder is added later — only once an access token exists to seal to it — because a holder with no
// credential yet cannot have a declaration built for it.
await mesh(`licence add anthropic personal --serves '{"model":"a-model"}'`);
await mesh(`licence manager personal ${MACHINE}`);
await mesh(`licence use personal ${MACHINE} anthropic-consumer`);
await mesh(`licence manager personal ${MACHINE} anthropic-manager`);
await mesh(`licence use personal ${MACHINE} anthropic-manager`);
// --- both runtimes are placed so the host pulls their images (which the test then drives) --------
// Inline manifests, env pointed at the stub, mirroring the committed module.json. The scheduled
// containers install as present state (ADR 0053); the test drives the entrypoints directly for a
// deterministic flow rather than waiting on cron.
// --- the manager module, deployed so the host delivers its bound facts --------------------------
// Inline manifest mirroring the committed module.json: model-access holder, refresh token bound as a
// sealed secret, no node-key mount. The scheduled container installs as present state (ADR 0053);
// the test drives adopt/refresh directly for a deterministic flow rather than waiting on cron.
const managerManifest = JSON.stringify({
module: "anthropic-manager",
version: "1",
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" },
emits: ["module.anthropic-manager.usage.read"],
resources: [
{ id: "mesh-state", type: "directory", 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: "refresh", type: "container", name: "mesh-anthropic-manager-refresh",
@@ -218,9 +225,8 @@ test("model access refreshes on the manager node and delivers only the access to
MESH_ANTHROPIC_LICENCE: "personal",
MESH_ANTHROPIC_TOKEN_ENDPOINT: "http://127.0.0.1:9099/token",
MESH_ANTHROPIC_USAGE_ENDPOINT: "http://127.0.0.1:9099/usage",
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",
@@ -228,6 +234,96 @@ test("model access refreshes on the manager node and delivers only the access to
},
],
});
await must(`printf %s ${quote(managerManifest)} > /tmp/anthropic-manager.json && docker cp /tmp/anthropic-manager.json mesh-control:/anthropic-manager.json`);
await mesh(`module add /anthropic-manager.json`);
await mesh(`module issue anthropic-manager --node ${MACHINE}`);
await mesh(`assign ${MACHINE} anthropic-manager`);
await mesh(`push ${MACHINE}`);
await settled();
// The image the host pulled for the manager runtime is now local; drive it directly.
const managerId = await imageId("anthropic-manager");
// The host delivered the manager's bound facts, carrying the node's PUBLIC sealing key.
const facts = await must(`cat /var/lib/mesh/anthropic-manager/model.json`);
assert.match(facts, /"manager_public_key"\s*:/, `the manager was not delivered its node public key:\n${facts}`);
// --- the OAuth stub: a tiny node server run from the manager image itself -----------------------
const stub = [
"const http=require('http');",
"http.createServer((req,res)=>{let b='';req.on('data',c=>b+=c);req.on('end',()=>{",
" if(req.url.startsWith('/token')){res.setHeader('content-type','application/json');",
` res.end(JSON.stringify({access_token:${JSON.stringify(ACCESS_TOKEN)},refresh_token:${JSON.stringify(ROTATED_REFRESH)},expires_in:3600,refresh_token_expires_in:2592000,subscription_type:'pro'}));return;}`,
" if(req.url.startsWith('/usage')){res.setHeader('content-type','application/json');",
" res.end(JSON.stringify({five_hour:{utilization:12,resets_at:'2026-01-01T00:00:00Z'},seven_day:{utilization:3}}));return;}",
" res.statusCode=404;res.end('no');});}).listen(9099,'127.0.0.1',()=>console.log('stub up'));",
].join("\n");
await must(`printf %s ${quote(stub)} > /var/lib/mesh/anthropic-manager/stub.js`);
await must(`docker rm -f oauth-stub 2>/dev/null; docker run -d --name oauth-stub --network host --entrypoint node -v /var/lib/mesh/anthropic-manager/stub.js:/run/stub.js:ro ${managerId} /run/stub.js`);
await must(`for i in $(seq 1 20); do curl -s -X POST http://127.0.0.1:9099/token >/dev/null && break; sleep 1; done`);
// --- 1. adopt: seal the operator's refresh token to THIS node's public key, on the manager node --
// adopt reads the node public key from the delivered bound facts (never a private key), seals, and
// hands out only the box.
await must(`printf %s ${quote(REFRESH_TOKEN)} > /var/lib/mesh/anthropic-manager/adopt-token`);
await must(
`docker run --rm --network host -v /var/lib/mesh/anthropic-manager:/run/state ` +
`-e MESH_ANTHROPIC_ADOPT_TOKEN_FILE=/run/state/adopt-token ` +
`-e MESH_MODEL_ACCESS_BIND_FILE=/run/state/model.json ` +
`-e MESH_ANTHROPIC_GRANT_OUT=/run/state/out/grant.json ` +
`${managerId} run /app/modules/anthropic-manager/dist/adopt/index.js`,
);
const sealedGrant = await must(`cat /var/lib/mesh/anthropic-manager/out/grant.json`);
assert.doesNotMatch(sealedGrant, new RegExp(REFRESH_TOKEN),
`the adopted box holds the refresh token in the clear:\n${sealedGrant}`);
assert.match(sealedGrant, /"sealed"\s*:/, `the adopted grant is not a sealed box:\n${sealedGrant}`);
// Store the sealed box in the control plane — which never sees the refresh token.
await must(`docker cp /var/lib/mesh/anthropic-manager/out/grant.json mesh-control:/grant.json`);
await mesh(`licence set-grant personal --file /grant.json`);
// --- 2. the host unseals: a push mounts the cleartext refresh token at the manager's secret path --
await mesh(`push ${MACHINE}`);
await settled();
let mounted = false;
for (let i = 0; i < 20 && !mounted; i++) {
mounted = (await on(`test -s /var/lib/mesh/anthropic-manager/refresh-token`)).ok;
if (!mounted) await new Promise((r) => setTimeout(r, 3000));
}
assert.ok(mounted, "the host never unsealed and mounted the refresh token for the manager");
const mountedToken = (await must(`cat /var/lib/mesh/anthropic-manager/refresh-token`)).trim();
assert.equal(mountedToken, REFRESH_TOKEN, "the host mounted the wrong cleartext refresh token");
// --- 3. refresh: read the cleartext, call the stub, re-seal a rotated token, write out ----------
await must(
`docker run --rm --network host -v /var/lib/mesh/anthropic-manager:/run/state ` +
`-e MESH_ANTHROPIC_LICENCE=personal ` +
`-e MESH_ANTHROPIC_TOKEN_ENDPOINT=http://127.0.0.1:9099/token ` +
`-e MESH_ANTHROPIC_USAGE_ENDPOINT=http://127.0.0.1:9099/usage ` +
`-e MESH_MODEL_ACCESS_SECRET_FILE=/run/state/refresh-token ` +
`-e MESH_MODEL_ACCESS_BIND_FILE=/run/state/model.json ` +
`-e MESH_ANTHROPIC_ACCESS_OUT=/run/state/out/access-token ` +
`-e MESH_ANTHROPIC_GRANT_OUT=/run/state/out/new-grant.json ` +
`-e MESH_ANTHROPIC_USAGE_OUT=/run/state/out/usage.json ` +
`${managerId} run /app/modules/anthropic-manager/dist/refresh/index.js`,
);
const producedAccess = (await must(`cat /var/lib/mesh/anthropic-manager/out/access-token`)).trim();
assert.equal(producedAccess, ACCESS_TOKEN, "the manager did not mint the stub's access token");
const newBox = await must(`cat /var/lib/mesh/anthropic-manager/out/new-grant.json`);
assert.doesNotMatch(newBox, new RegExp(ROTATED_REFRESH),
`the re-sealed box holds the rotated refresh token in the clear:\n${newBox}`);
const usage = await must(`cat /var/lib/mesh/anthropic-manager/out/usage.json`);
assert.match(usage, /"sessionPct":12/, `the licence-grain usage reading is wrong:\n${usage}`);
// --- 4. submit: the control plane is handed only the access token + opaque box -------------------
// The consumer is put on the licence now — an access token exists to seal to it.
await mesh(`licence use personal ${MACHINE} anthropic-consumer`);
await must(`docker cp /var/lib/mesh/anthropic-manager/out/access-token mesh-control:/access-token`);
await must(`docker cp /var/lib/mesh/anthropic-manager/out/new-grant.json mesh-control:/new-grant.json`);
const submitted = await mesh(`licence submit-refresh personal --access-file /access-token --grant-file /new-grant.json`);
assert.match(submitted, /sealed to 1 holder/, submitted);
// --- 5. deliver: deploy the consumer and push; it gets the sealed access token -------------------
const consumerManifest = JSON.stringify({
module: "anthropic-consumer",
version: "1",
@@ -254,93 +350,15 @@ test("model access refreshes on the manager node and delivers only the access to
},
],
});
for (const [name, body] of [["anthropic-manager", managerManifest], ["anthropic-consumer", consumerManifest]] as const) {
await must(`printf %s ${quote(body)} > /tmp/${name}.json && docker cp /tmp/${name}.json mesh-control:/${name}.json`);
await mesh(`module add /${name}.json`);
await mesh(`module issue ${name} --node ${MACHINE}`);
await mesh(`assign ${MACHINE} ${name}`);
}
await must(`printf %s ${quote(consumerManifest)} > /tmp/anthropic-consumer.json && docker cp /tmp/anthropic-consumer.json mesh-control:/anthropic-consumer.json`);
await mesh(`module add /anthropic-consumer.json`);
await mesh(`module issue anthropic-consumer --node ${MACHINE}`);
await mesh(`assign ${MACHINE} anthropic-consumer`);
await mesh(`push ${MACHINE}`);
await settled();
// The images the host pulled to run the scheduled containers are now local; drive them directly.
const managerId = await imageId("anthropic-manager");
const consumerId = await imageId("anthropic-consumer");
// --- the stubbed node private key, mounted (FLAGGED) --------------------------------------------
// Production needs a host capability to place the node private key where the manager reads it;
// mesh-host has none today. Here the test mounts a generated key pair as that key.
const keys = nodeKeyPair();
await must(`printf %s ${quote(keys.pub)} > /var/lib/mesh/anthropic-manager/keys/sealing.pub`);
await must(`printf %s ${quote(keys.priv)} > /var/lib/mesh/anthropic-manager/keys/sealing.priv`);
// --- the OAuth stub: a tiny node server run from the manager image itself -----------------------
const stub = [
"const http=require('http');",
"http.createServer((req,res)=>{let b='';req.on('data',c=>b+=c);req.on('end',()=>{",
" if(req.url.startsWith('/token')){res.setHeader('content-type','application/json');",
` res.end(JSON.stringify({access_token:${JSON.stringify(ACCESS_TOKEN)},refresh_token:${JSON.stringify(ROTATED_REFRESH)},expires_in:3600,refresh_token_expires_in:2592000,subscription_type:'pro'}));return;}`,
" if(req.url.startsWith('/usage')){res.setHeader('content-type','application/json');",
" res.end(JSON.stringify({five_hour:{utilization:12,resets_at:'2026-01-01T00:00:00Z'},seven_day:{utilization:3}}));return;}",
" res.statusCode=404;res.end('no');});}).listen(9099,'127.0.0.1',()=>console.log('stub up'));",
].join("\n");
await must(`printf %s ${quote(stub)} > /var/lib/mesh/anthropic-manager/stub.js`);
await must(`docker rm -f oauth-stub 2>/dev/null; docker run -d --name oauth-stub --network host --entrypoint node -v /var/lib/mesh/anthropic-manager/stub.js:/run/stub.js:ro ${managerId} /run/stub.js`);
// Give it a moment to bind.
await must(`for i in $(seq 1 20); do curl -s -X POST http://127.0.0.1:9099/token >/dev/null && break; sleep 1; done`);
// --- 1. adopt: seal the refresh token at rest, on the manager node ------------------------------
await must(`printf %s ${quote(REFRESH_TOKEN)} > /var/lib/mesh/anthropic-manager/refresh-token`);
await must(
`docker run --rm --network host -v /var/lib/mesh/anthropic-manager:/run/state ` +
`-e MESH_ANTHROPIC_REFRESH_TOKEN_FILE=/run/state/refresh-token ` +
`-e MESH_NODE_SEALING_PUBLIC_FILE=/run/state/keys/sealing.pub ` +
`-e MESH_ANTHROPIC_GRANT_OUT=/run/state/grant.json ` +
`${managerId} run /app/modules/anthropic-manager/dist/adopt/index.js`,
);
const envelope = await must(`cat /var/lib/mesh/anthropic-manager/grant.json`);
assert.doesNotMatch(envelope, new RegExp(REFRESH_TOKEN),
`the adopted envelope holds the refresh token in the clear:\n${envelope}`);
// Store the sealed envelope in the control plane — which never sees the refresh token.
await must(`docker cp /var/lib/mesh/anthropic-manager/grant.json mesh-control:/grant.json`);
await mesh(`licence set-grant personal --file /grant.json`);
// --- 2. refresh: open on the manager node, call the stub, write access token + re-sealed refresh -
await must(
`docker run --rm --network host -v /var/lib/mesh/anthropic-manager:/run/state ` +
`-e MESH_ANTHROPIC_LICENCE=personal ` +
`-e MESH_ANTHROPIC_TOKEN_ENDPOINT=http://127.0.0.1:9099/token ` +
`-e MESH_ANTHROPIC_USAGE_ENDPOINT=http://127.0.0.1:9099/usage ` +
`-e MESH_ANTHROPIC_GRANT_FILE=/run/state/grant.json ` +
`-e MESH_NODE_SEALING_PUBLIC_FILE=/run/state/keys/sealing.pub ` +
`-e MESH_NODE_SEALING_PRIVATE_FILE=/run/state/keys/sealing.priv ` +
`-e MESH_ANTHROPIC_ACCESS_OUT=/run/state/out/access-token ` +
`-e MESH_ANTHROPIC_GRANT_OUT=/run/state/out/grant.json ` +
`-e MESH_ANTHROPIC_USAGE_OUT=/run/state/out/usage.json ` +
`${managerId} run /app/modules/anthropic-manager/dist/refresh/index.js`,
);
const producedAccess = (await must(`cat /var/lib/mesh/anthropic-manager/out/access-token`)).trim();
assert.equal(producedAccess, ACCESS_TOKEN, "the manager did not mint the stub's access token");
const newEnvelope = await must(`cat /var/lib/mesh/anthropic-manager/out/grant.json`);
assert.doesNotMatch(newEnvelope, new RegExp(ROTATED_REFRESH),
`the re-sealed envelope holds the rotated refresh token in the clear:\n${newEnvelope}`);
// Licence-grain usage was read and recorded.
const usage = await must(`cat /var/lib/mesh/anthropic-manager/out/usage.json`);
assert.match(usage, /"sessionPct":12/, `the licence-grain usage reading is wrong:\n${usage}`);
// --- 3. submit: the control plane is handed only the access token + opaque envelope -------------
await must(`docker cp /var/lib/mesh/anthropic-manager/out/access-token mesh-control:/access-token`);
await must(`docker cp /var/lib/mesh/anthropic-manager/out/grant.json mesh-control:/new-grant.json`);
const submitted = await mesh(`licence submit-refresh personal --access-file /access-token --grant-file /new-grant.json`);
assert.match(submitted, /sealed to 1 holder/, submitted);
// --- 4. deliver: the consumer gets the sealed access token and writes the credential -------------
await mesh(`push ${MACHINE}`);
await settled();
// Drive the consumer's apply once the token has been delivered to its secret path.
let delivered = false;
for (let i = 0; i < 20 && !delivered; i++) {
delivered = (await on(`test -s /var/lib/anthropic-consumer/access-token`)).ok;
@@ -363,7 +381,8 @@ test("model access refreshes on the manager node and delivers only the access to
assert.equal(parsed.claudeAiOauth?.accessToken, ACCESS_TOKEN);
assert.ok(!parsed.claudeAiOauth?.refreshToken, "the consumer was given a refresh token");
// The refresh token — original or rotated — is nowhere on the consuming node.
// The refresh token — original or rotated — is nowhere on the CONSUMING node's tree. (It IS on the
// manager's, as cleartext the host mounted for it — that is the one node allowed to read it.)
for (const secret of [REFRESH_TOKEN, ROTATED_REFRESH]) {
const found = await on(`grep -rq ${quote(secret)} /var/lib/anthropic-consumer`);
assert.ok(!found.ok, `a refresh token is on the consuming node under /var/lib/anthropic-consumer`);
@@ -371,20 +390,20 @@ test("model access refreshes on the manager node and delivers only the access to
// The control plane's own database holds the refresh token only as ciphertext.
const grantRow = await must(
`docker exec mesh-store psql -U postgres -d licences -qAt -c "select token, wrapped_key from refresh_grant where licence='personal'"`,
`docker exec mesh-store psql -U postgres -d licences -qAt -c "select sealed, manager_key from refresh_grant where licence='personal'"`,
);
assert.ok(grantRow.trim().length > 0, "no refresh grant was stored");
for (const secret of [REFRESH_TOKEN, ROTATED_REFRESH]) {
assert.doesNotMatch(grantRow, new RegExp(secret),
`the refresh token is in the control plane's database in the clear:\n${grantRow}`);
}
// And the holder's sealed column carries the access token's seal, never a refresh token.
// And the CONSUMER holder's sealed column carries the access token's seal, never a refresh token.
const holder = await must(
`docker exec mesh-store psql -U postgres -d licences -qAt -c "select coalesce(sealed,'') from licence_holder where licence='personal'"`,
`docker exec mesh-store psql -U postgres -d licences -qAt -c "select coalesce(sealed,'') from licence_holder where licence='personal' and module='anthropic-consumer'"`,
);
assert.ok(holder.trim().length > 0, "nothing was sealed to the holder");
assert.ok(holder.trim().length > 0, "nothing was sealed to the consumer holder");
for (const secret of [REFRESH_TOKEN, ROTATED_REFRESH]) {
assert.doesNotMatch(holder, new RegExp(secret), "a refresh token is in the holder row");
assert.doesNotMatch(holder, new RegExp(secret), "a refresh token is in the consumer holder row");
}
await must(`docker rm -f oauth-stub 2>/dev/null || true`);