import assert from 'node:assert/strict'; import { readFile } from 'node:fs/promises'; import test from 'node:test'; import { sha256Bytes } from '../src/crypto.js'; import { InputAttachmentError, createPinnedAttachmentLookup, decodeInlineInputAttachment, downloadAgentBusInputAttachment, parseAgentBusInputAttachment, resolveAgentBusAttachmentAddresses, validateAgentBusAttachmentUrl } from '../src/input-attachment.js'; function codeOf(callback: () => unknown): string { try { callback(); return ''; } catch (error) { return error instanceof InputAttachmentError ? error.code : String(error); } } test('manual roster attachment base64 is bounded and hash verified', () => { const content = Buffer.from('PK\u0003\u0004synthetic-workbook'); const decoded = decodeInlineInputAttachment({ name: 'synthetic.xlsx', content_type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', size: content.byteLength, sha256: sha256Bytes(content), content_base64: content.toString('base64') }, 1_000); assert.equal(decoded.fileName, 'synthetic.xlsx'); assert.deepEqual(decoded.content, content); assert.equal(decoded.source, 'manual'); }); test('manual roster attachment rejects unsupported names, invalid base64 and mismatched hash', () => { assert.equal(codeOf(() => decodeInlineInputAttachment({ name: 'synthetic.csv', content_base64: 'YQ==' }, 1_000)), 'roster_file_type_unsupported'); assert.equal(codeOf(() => decodeInlineInputAttachment({ name: 'synthetic.xls', content_base64: '%%%' }, 1_000)), 'roster_file_base64_invalid'); assert.equal(codeOf(() => decodeInlineInputAttachment({ name: 'synthetic.xls', sha256: 'a'.repeat(64), content_base64: 'YQ==' }, 1_000)), 'roster_file_sha256_mismatch'); }); test('AgentBus attachment URLs allow private HTTPS endpoints but reject unsafe URL syntax', () => { for (const value of [ 'http://files.example.test/list.xlsx', 'ftp://files.example.test/list.xlsx', 'https://user:secret@files.example.test/list.xlsx' ]) { assert.equal(codeOf(() => validateAgentBusAttachmentUrl(value)), 'roster_attachment_url_unsafe'); } for (const value of [ 'https://files.example.com/list.xlsx', 'https://localhost/list.xlsx', 'https://127.0.0.1/list.xlsx', 'https://10.0.0.8/list.xlsx', 'https://[::1]/list.xlsx' ]) { assert.equal(validateAgentBusAttachmentUrl(value).protocol, 'https:', value); } }); test('AgentBus attachment metadata is normalized without exposing URL credentials', () => { const reference = parseAgentBusInputAttachment({ name: 'synthetic.xls', content_type: 'application/vnd.ms-excel', size: 128, sha256: 'a'.repeat(64), url: 'https://files.example.com/roster.xls' }, 1_000); assert.equal(reference.name, 'synthetic.xls'); assert.equal(reference.size, 128); assert.equal(reference.sha256, 'a'.repeat(64)); }); test('AgentBus attachment DNS accepts internal names and private literal addresses', async () => { assert.deepEqual(await resolveAgentBusAttachmentAddresses('10.0.0.8'), [ { address: '10.0.0.8', family: 4 } ]); assert.deepEqual(await resolveAgentBusAttachmentAddresses('fd00::8'), [ { address: 'fd00::8', family: 6 } ]); const localhost = await resolveAgentBusAttachmentAddresses('localhost'); assert.ok(localhost.length > 0); assert.ok(localhost.every((entry) => entry.family === 4 || entry.family === 6)); }); test('AgentBus attachment DNS pinning follows the Node lookup callback shape', async () => { const address = { address: '10.0.0.8', family: 4 }; const lookup = createPinnedAttachmentLookup(address); await new Promise((resolve, reject) => { lookup('internal.example', { all: true }, (error, addresses, family) => { try { assert.ifError(error); assert.deepEqual(addresses, [address]); assert.equal(family, undefined); resolve(); } catch (assertionError) { reject(assertionError); } }); }); await new Promise((resolve, reject) => { lookup('internal.example', { all: false }, (error, resolvedAddress, family) => { try { assert.ifError(error); assert.equal(resolvedAddress, address.address); assert.equal(family, address.family); resolve(); } catch (assertionError) { reject(assertionError); } }); }); }); test('AgentBus attachment diagnostics expose stages and codes without URL data', async () => { const events: Array<{ event: string; metadata: Record }> = []; await assert.rejects( () => downloadAgentBusInputAttachment({ name: 'synthetic.xls', contentType: 'application/vnd.ms-excel', size: 128, url: 'https://user:secret@127.0.0.1/private-roster.xls?token=secret' }, 1_000, (event, metadata) => events.push({ event, metadata })), (error: unknown) => error instanceof InputAttachmentError && error.code === 'roster_attachment_url_unsafe' ); assert.deepEqual(events.map((event) => event.event), ['download_started', 'download_failed']); assert.equal(events[1].metadata.error_code, 'roster_attachment_url_unsafe'); assert.doesNotMatch(JSON.stringify(events), /127\.0\.0\.1|private-roster|token|secret/u); }); test('input attachment migration stores only encrypted normalized data and waiting-task index', async () => { const sql = await readFile(new URL('../migrations/014_task_input_attachments.sql', import.meta.url), 'utf8'); assert.match(sql, /CREATE TABLE IF NOT EXISTS task_input_attachments/); assert.match(sql, /normalized_text_ciphertext text/); assert.match(sql, /file_name_ciphertext text NOT NULL/); assert.doesNotMatch(sql, /\n\s*file_name text/); assert.doesNotMatch(sql, /public_url|content bytea|content_ciphertext/); assert.match(sql, /WHERE status = 'awaiting_attachment'/); });