From 5fc3cd57959a26b6f5b2ca98bd0b7b9283791689 Mon Sep 17 00:00:00 2001 From: matcha Date: Sun, 4 Oct 2026 02:46:28 +0000 Subject: [PATCH] Consolidate Persimmons source --- extension/tests/human-crypto.test.mjs | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 extension/tests/human-crypto.test.mjs diff --git a/extension/tests/human-crypto.test.mjs b/extension/tests/human-crypto.test.mjs new file mode 100644 index 0000000..6cd4c1f --- /dev/null +++ b/extension/tests/human-crypto.test.mjs @@ -0,0 +1,56 @@ +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'; + +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 () => { + 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, 'include'); + 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/ + ); +});