agent-credentials-data-integrity.test.mjs
94 lines 3.7 KB
Raw
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago
1 /**
2 * Phase C + Lane D — data-integrity: secret never persisted; isolation; meta sibling file.
3 */
4
5 import { describe, it } from 'node:test';
6 import assert from 'node:assert/strict';
7 import fs from 'node:fs/promises';
8 import os from 'node:os';
9 import path from 'node:path';
10 import { readFile } from 'node:fs/promises';
11 import { createAgentCredentialStore, BLOB_GLOBAL } from '../hub/gateway/agent-credential-store.mjs';
12 import { mintCredential, listCredentialsForSub } from '../hub/lib/agent-credential-core.mjs';
13
14 const agentStoreSrc = await readFile(
15 new URL('../hub/gateway/agent-credential-store.mjs', import.meta.url),
16 'utf8'
17 );
18
19 describe('Phase C data-integrity — agent credentials', () => {
20 it('persisted JSON never contains raw secret; list has no credential field', async () => {
21 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kt-agent-di-'));
22 process.env.KNOWTATION_GATEWAY_DATA_DIR = dir;
23 try {
24 const store = createAgentCredentialStore();
25 const minted = await store.mint({
26 sub: 'google:1',
27 name: 'di',
28 vault_ids: ['default'],
29 scopes: ['propose', 'vault:read'],
30 });
31 const file = path.join(dir, 'hosted_agent_credentials.json');
32 const raw = await fs.readFile(file, 'utf8');
33 assert.ok(!raw.includes(minted.credential));
34 const secretPart = minted.credential.split('.')[1];
35 assert.ok(secretPart);
36 assert.ok(!raw.includes(secretPart));
37 assert.ok(raw.includes('token_hash'));
38 assert.ok(raw.includes('"credentials"'));
39 assert.ok(!raw.includes('refresh-tokens'));
40
41 const list = await store.list('google:1');
42 assert.equal(list.credentials[0].credential, undefined);
43 assert.equal(list.credentials[0].token_hash, undefined);
44 assert.ok(await fs.stat(path.join(dir, 'hosted_agent_credentials.meta.json')));
45 } finally {
46 delete process.env.KNOWTATION_GATEWAY_DATA_DIR;
47 await fs.rm(dir, { recursive: true, force: true });
48 }
49 });
50
51 it('listCredentialsForSub never exposes hashes', () => {
52 const { records, id } = mintCredential({}, {
53 sub: 'google:1',
54 name: 'x',
55 vault_ids: ['default'],
56 scopes: ['propose', 'vault:read'],
57 });
58 const list = listCredentialsForSub(records, 'google:1');
59 assert.equal(list[0].id, id);
60 assert.equal(list[0].token_hash, undefined);
61 assert.equal(list[0].lookup_id, undefined);
62 });
63
64 it('agent store source never references refresh-tokens-v1 or gateway-auth', () => {
65 const code = agentStoreSrc
66 .replace(/\/\*[\s\S]*?\*\//g, '')
67 .replace(/\/\/[^\n]*/g, '');
68 assert.ok(!code.includes('refresh-tokens-v1'));
69 assert.ok(!code.includes('gateway-auth'));
70 assert.ok(!code.includes('hosted_refresh_tokens.json'));
71 assert.ok(agentStoreSrc.includes('hosted_agent_credentials.meta.json'));
72 });
73
74 it('NETLIFY set + missing agent blob global throws and does not write file fallback', async () => {
75 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kt-agent-di-netlify-'));
76 process.env.KNOWTATION_GATEWAY_DATA_DIR = dir;
77 const prevNetlify = process.env.NETLIFY;
78 process.env.NETLIFY = 'true';
79 delete globalThis[BLOB_GLOBAL];
80 try {
81 const store = createAgentCredentialStore();
82 await assert.rejects(() => store.list('google:1'), (e) => e.code === 'AGENT_CREDENTIAL_STORE_UNAVAILABLE');
83 await assert.rejects(
84 () => fs.stat(path.join(dir, 'hosted_agent_credentials.json')),
85 (e) => e && e.code === 'ENOENT'
86 );
87 } finally {
88 if (prevNetlify === undefined) delete process.env.NETLIFY;
89 else process.env.NETLIFY = prevNetlify;
90 delete process.env.KNOWTATION_GATEWAY_DATA_DIR;
91 await fs.rm(dir, { recursive: true, force: true });
92 }
93 });
94 });
File History 1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6 docs: record AIP-b SD-21 land (KN #308) Human 10 days ago