import test from 'node:test'; import assert from 'node:assert/strict'; import {createHash} from 'node:crypto'; import {readFile} from 'node:fs/promises'; import {initialize, unlock, encryptItem, decryptItem, isHttpsOrigin} from '../human-crypto.mjs'; import {EXTENSION_ID} from '../identity.mjs'; import {vaultRequest} from '../human-api.mjs'; import {VAULT_CAPABILITY} from '../connection-state.mjs'; test('deterministic unpacked ID agrees with the exact server Origin', async () => { const manifest = JSON.parse(await readFile(new URL('../manifest.json', import.meta.url))); const derived = createHash('sha256').update(Buffer.from(manifest.key, 'base64')).digest('hex').slice(0, 32) .replace(/[0-9a-f]/g, c => String.fromCharCode(97 + parseInt(c, 16))); assert.equal(derived, EXTENSION_ID); const server = await readFile(new URL('../../tea_mcp/human_vault.py', import.meta.url), 'utf8'); assert.ok(server.includes(`EXTENSION_ORIGIN = "chrome-extension://${derived}"`)); }); test('exact HTTPS origins are data while extension origin remains the caller boundary', () => { for (const origin of ['https://example.com','https://accounts.example.com','https://example.com:8443']) assert.equal(isHttpsOrigin(origin),true); for (const origin of ['http://example.com','https://example.com/path','https://user@example.com','not-a-url']) assert.equal(isHttpsOrigin(origin),false); }); test('local wrapping and authenticated ciphertext work for ordinary HTTPS sites', async () => { const passphrase = 'disposable test passphrase'; const owner = 'fixture'; const record = await initialize(passphrase, owner); assert.ok(!JSON.stringify(record).includes(passphrase)); const key = await unlock(passphrase, owner, record); assert.equal(key.extractable, false); await assert.rejects(crypto.subtle.exportKey('raw', key)); await assert.rejects(unlock('wrong passphrase', owner, record), /Unlock failed/); const account = {username: 'DISPOSABLE_USER_SENTINEL', password: 'DISPOSABLE_PASSWORD_SENTINEL'}; const item = await encryptItem(key, owner, 'https://example.com', account); assert.deepEqual(await decryptItem(key, owner, item), account); assert.equal(item.username, account.username); assert.ok(!JSON.stringify(item).includes(account.password)); const updated = await encryptItem(key, owner, item.origin, account, item.id); assert.notEqual(updated.iv, item.iv); assert.notEqual(updated.ciphertext, item.ciphertext); for (const bad of [{...item,id:'0'.repeat(32)},{...item,origin:'https://other.example'},{...item,v:2}]) await assert.rejects(decryptItem(key, owner, bad), /failed authentication/); }); test('vault HTTP has no query or keys; session failures never echo bodies', async () => { globalThis.chrome={storage:{local:{get:async()=>({[VAULT_CAPABILITY]:{owner:'fixture',capability:'A'.repeat(43)}})}}}; await vaultRequest('GET', 'fixture', '1'.repeat(32), undefined, async (url, options) => { assert.equal(url, 'https://teahub.xyz/tea/site/chat/persimmons/human-vault/items/' + '1'.repeat(32) + '/read'); assert.equal(options.method, 'POST'); assert.equal(options.credentials, 'omit'); assert.equal(options.headers['X-Vault-Capability'], 'A'.repeat(43)); assert.equal(options.headers['X-Vault-Owner'], 'fixture'); return new Response(JSON.stringify({owner: 'fixture'}), {headers: {'Content-Type': 'application/json'}}); }); for (const status of [401,403,500]) await assert.rejects( vaultRequest('GET', undefined, undefined, undefined, async()=>new Response('PRIVATE_SENTINEL',{status})), /Vault unavailable/ ); });