2026-10-14 17:25:52 -07:00
import assert from 'node:assert/strict' ;
2026-10-14 22:03:21 -07:00
import { execFileSync } from 'node:child_process' ;
import { mkdtempSync , rmSync } from 'node:fs' ;
import { tmpdir } from 'node:os' ;
import { dirname , join , resolve } from 'node:path' ;
import { fileURLToPath } from 'node:url' ;
2026-10-14 17:25:52 -07:00
import { once } from 'node:events' ;
import { createServer , request as httpRequest , type ServerResponse } from 'node:http' ;
import { Readable } from 'node:stream' ;
import test from 'node:test' ;
import Fastify from 'fastify' ;
import sensible from '@fastify/sensible' ;
2026-10-14 22:03:21 -07:00
process . env . AGENT_PUSH_ENABLED = 'false' ;
2026-10-14 17:25:52 -07:00
process . env . ADMIN_TOKEN = 'attachments-browser-token-test' ;
2026-10-14 22:03:21 -07:00
const directory = mkdtempSync ( join ( tmpdir (), 'sybil-attachment-proxy-' ));
const root = resolve ( dirname ( fileURLToPath ( import . meta . url )), '..' );
process . env . DATABASE_URL = `file: ${ join ( directory , 'test.db' ) } ` ;
execFileSync ( process . execPath , [ join ( root , 'node_modules/prisma/build/index.js' ), 'migrate' , 'deploy' ], { cwd : root , env : process.env , stdio : 'pipe' });
2026-10-14 17:25:52 -07:00
const [{ registerAgentRoutes }, { env }] = await Promise . all ([ import ( '../src/agent.js' ), import ( '../src/env.js' )]);
const auth = { authorization : `Bearer ${ process . env . ADMIN_TOKEN } ` }, key = 'runtime-private-test-key' ;
const file = { id : 'att_12345678-1234-4234-8234-123456789abc' , kind : 'text' , filename : 'résumé.txt' , mimeType : 'text/plain' , sizeBytes :4 , sha256 : 'a' . repeat ( 64 ), workspacePath : '/data/workspace/uploads/att_12345678-1234-4234-8234-123456789abc/résumé.txt' };
const bodies : any [] = [];
2026-10-20 14:14:09 -07:00
let capable = true , assistantImages = false , badReceipt = false , errorStatus = 0 , uploadCalls = 0 ;
2026-10-14 17:25:52 -07:00
let holdDescriptor = false , holdDownload = false ; const held :ServerResponse [] = [], closed :ServerResponse [] = [];
const source = createServer ( async ( req , res ) => {
assert . equal ( req . headers . authorization , `Bearer ${ key } ` );
const chunks : Buffer [] = [];
try { for await ( const chunk of req ) chunks . push ( chunk ); } catch { return ; }
const bytes = Buffer . concat ( chunks ); res . setHeader ( 'Content-Type' , 'application/json' );
2026-10-20 14:14:09 -07:00
if ( req . url === '/api/conversations/root' ){ if ( holdDescriptor ){ held . push ( res ); res . once ( 'close' ,() => closed . push ( res )); return ;} res . end ( JSON . stringify ({ id : '1' , capabilities : {...( capable ? { attachments : { version :1 }} : {}),...( assistantImages ? { assistantImages : { version :1 }} : {})}})); return ;}
2026-10-14 17:25:52 -07:00
if ( errorStatus ){ res . statusCode = errorStatus ; res . end ( JSON . stringify ({ error :true , message : 'Storage quota reached' , statusCode :errorStatus , code : 'attachment_quota_exceeded' , remainingBytes :12 , quotaBytes :1024 , requiredBytes :99 })); return ;}
if ( req . url ? . endsWith ( '/attachments' )){ uploadCalls ++ ; assert . match ( req . headers [ 'content-type' ] ! , /^multipart\/form-data; boundary=/ ); assert . ok ( bytes . includes ( Buffer . from ( 'résumé.txt' ))); bodies . push ( bytes . toString ()); res . statusCode = 201 ; res . end ( JSON . stringify ({ attachment :file })); return ;}
if ( req . url ? . endsWith ( '/content' )){ if ( holdDownload ){ held . push ( res ); res . once ( 'close' ,() => closed . push ( res )); return ;} res . writeHead ( 200 ,{ 'Content-Type' : 'text/plain; charset=utf-8' , 'Content-Length' : '4' , 'Content-Disposition' : "attachment; filename=notes.txt; filename*=UTF-8''notes.txt" }); res . end ( 'data' ); return ;}
if ( req . method === 'POST' ){ const body = JSON . parse ( bytes . toString ()); bodies . push ( body ); res . statusCode = 202 ; res . end ( JSON . stringify ({ id :1 , status : 'queued' , messageId : `inbound:sybil: ${ body . requestId } ` ,...( ! badReceipt ? { attachmentIds :body.attachments.map (( f :any ) => f . id )} : {})})); return ;}
res . end ( JSON . stringify ({ conversationId : '1' , messages : [{ id : 'inbound:sybil:send' , role : 'user' , content : 'Attached: résumé.txt.' , metadata : { agentAttachments : [ file ]}},{ id : 'tool:2' , role : 'tool' , metadata : { kind : 'tool_call' }}], cursor : '2' , hasMore :false }));
});
source . listen ( 0 , '127.0.0.1' ); await once ( source , 'listening' );
const app = Fastify (); await app . register ( sensible ); await registerAgentRoutes ( app ,{ baseUrl : `http://127.0.0.1: ${ ( source . address () as { port :number } ).port}` , apiKey :key });
await app . listen ({ port :0 , host : '127.0.0.1' }); const base = `http://127.0.0.1: ${ ( app . server . address () as { port :number } ).port}` ;
2026-10-14 22:03:21 -07:00
test . after ( async () => { await app . close (); source . closeAllConnections (); await new Promise < void >( r => source . close (() => r ())); const { prisma } = await import ( '../src/db.js' ); await prisma . $disconnect (); rmSync ( directory ,{ recursive :true , force :true });});
2026-10-14 17:25:52 -07:00
function form (){ const body = new FormData (); body . append ( 'file' , new Blob ([ 'data' ],{ type : 'text/plain' }), 'résumé.txt' ); body . append ( 'uploadId' , '12345678-1234-4234-8234-123456789abc' ); return body ;}
test ( 'auth precedes parsing/forwarding; capability gates old or unsafe upstream and open backend' , async () => {
const before = uploadCalls ;
assert . equal (( await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , body :form ()})). status , 401 ); assert . equal ( uploadCalls , before );
capable = false ; assert . equal (( await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers :auth , body :form ()})). status , 503 );
assert . equal (( await app . inject ({ method : 'POST' , url : '/v1/agent/messages' , headers :auth , payload : { content : '' , requestId : 'x' , attachments : [{ id :file.id }]}})). statusCode , 503 );
capable = true ; const saved = env . ADMIN_TOKEN ; env . ADMIN_TOKEN = undefined ;
try { assert . equal (( await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , body :form ()})). status , 503 ); assert . equal (( await app . inject ({ url : '/v1/agent' })). statusCode , 503 ); assert . equal (( await app . inject ({ url : '/v1/agent/messages' })). statusCode , 503 );} finally { env . ADMIN_TOKEN = saved ;}
});
test ( 'multipart forwards Unicode filename/uploadId with private auth; quota details and authenticated originals survive' , async () => {
const response = await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers :auth , body :form ()}); assert . equal ( response . status , 201 ); assert . deepEqual ( await response . json (),{ attachment :file }); assert . match ( bodies . at ( - 1 ), /12345678-1234-4234-8234-123456789abc/ );
errorStatus = 507 ; try { const full = await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers :auth , body :form ()}); assert . equal ( full . status , 507 ); assert . equal (( await full . json () as any ). remainingBytes , 12 );} finally { errorStatus = 0 ;}
assert . equal (( await fetch ( base + `/v1/agent/attachments/ ${ file . id } /content` )). status , 401 );
const download = await fetch ( base + `/v1/agent/attachments/ ${ file . id } /content` ,{ headers :auth }); assert . equal ( download . status , 200 ); assert . equal ( await download . text (), 'data' ); assert . match ( download . headers . get ( 'cache-control' ) ! , /no-store/ ); assert . equal ( download . headers . get ( 'x-content-type-options' ), 'nosniff' );
assert . equal (( await app . inject ({ url : '/v1/agent/attachments/not-an-id/content' , headers :auth })). statusCode , 404 );
});
2026-10-20 14:14:09 -07:00
test ( 'assistant-only capability allows authenticated retrieval without granting input uploads or open history' , async () => {
capable = false ; assistantImages = true ;
const saved = env . ADMIN_TOKEN ;
try {
const descriptor = ( await app . inject ({ url : '/v1/agent' , headers :auth })). json ();
assert . deepEqual ( descriptor . capabilities ,{ assistantImages : { version :1 }});
assert . equal (( await fetch ( base + `/v1/agent/attachments/ ${ file . id } /content` ,{ headers :auth })). status , 200 );
assert . equal (( await fetch ( base + `/v1/agent/attachments/ ${ file . id } /content` )). status , 401 );
assert . equal (( await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers :auth , body :form ()})). status , 503 );
assert . equal (( await app . inject ({ method : 'POST' , url : '/v1/agent/messages' , headers :auth , payload : { content : '' , requestId : 'assistant-only' , attachments : [{ id :file.id }]}})). statusCode , 503 );
assert . equal (( await app . inject ({ method : 'POST' , url : '/v1/agent/output_image' , headers :auth , payload : { path : 'https://never-fetch.invalid/image' }})). statusCode , 404 );
env . ADMIN_TOKEN = undefined ;
for ( const url of [ '/v1/agent' , '/v1/agent/messages' , `/v1/agent/attachments/ ${ file . id } /content` ]) assert . equal (( await app . inject ({ url })). statusCode , 503 );
} finally { env . ADMIN_TOKEN = saved ; capable = true ; assistantImages = false ;}
});
2026-10-14 17:25:52 -07:00
test ( 'ID-only sends require matching receipts; history retains attachment metadata and raw tools' , async () => {
const payload = { content : '' , requestId : 'send' , attachments : [{ id :file.id }]};
assert . equal (( await app . inject ({ method : 'POST' , url : '/v1/agent/messages' , headers :auth , payload })). statusCode , 202 ); assert . deepEqual ( bodies . at ( - 1 ),{... payload , channel : 'sybil' , whenBusy : 'followUp' , wait :false });
badReceipt = true ; try { assert . equal (( await app . inject ({ method : 'POST' , url : '/v1/agent/messages' , headers :auth , payload })). statusCode , 502 );} finally { badReceipt = false ;}
for ( const attachments of [[{... file }],[{ id :file.id , workspacePath : '/etc/passwd' }],[{ id :file.id },{ id :file.id }], Array ( 9 ). fill ({ id :file.id })]) assert . equal (( await app . inject ({ method : 'POST' , url : '/v1/agent/messages' , headers :auth , payload : {... payload , attachments }})). statusCode , 400 );
const page = ( await app . inject ({ url : '/v1/agent/messages' , headers :auth })). json (); assert . deepEqual ( page . messages [ 0 ]. metadata . agentAttachments ,[ file ]); assert . equal ( page . messages [ 1 ]. role , 'tool' );
});
test ( 'proxy rejects oversized declared bodies and chunked bodies' , async () => {
const before = uploadCalls ;
const large = await app . inject ({ method : 'POST' , url : '/v1/agent/attachments' , headers : {... auth , 'content-type' : 'multipart/form-data; boundary=x' , 'content-length' : String ( 9 * 1024 * 1024 )}}); assert . equal ( large . statusCode , 413 ); assert . equal ( uploadCalls , before );
const chunks = Readable . from (( async function * (){ for ( let i = 0 ; i < 145 ; i ++) yield Buffer.alloc ( 64 * 1024 ); } )());
const result = await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers : { ...auth , 'content-type' : 'multipart/form-data; boundary=x' }, body : chunks as unknown as RequestInit [ 'body' ] , duplex : 'half' } as RequestInit & { duplex : string } );
assert.equal ( result.status , 413 ); assert.equal (( await result.json () as any ) .code , 'attachment_limit_exceeded' );
} );
async function eventually ( check :()= > boolean ){ const end = Date . now () + 3000 ; while ( ! check () && Date . now ()< end ) await new Promise ( r = > setTimeout ( r , 10 )); assert . ok ( check (), 'Expected cancellation to finish' );}
test ( 'disconnect releases upload/download slots exactly once, cancels upstream and never continues after capability cancellation' , async () => {
const before = uploadCalls ;
holdDescriptor = true ;
for ( let n = 0 ; n < 3 ; n ++) {
const expected = held.length + 1 , terminal = closed.length + 1 ;
const req = httpRequest ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers : { ...auth , 'content-type' : 'multipart/form-data; boundary=x' }}, ()= >{}); req . on ( 'error' ,() => {}); req . end ( '--x--' );
await eventually (() => held . length === expected ); req . destroy (); await eventually (() => closed . length === terminal );
}
holdDescriptor = false ; assert . equal ( uploadCalls , before , 'canceled capability checks never forward the upload' );
assert . equal (( await fetch ( base + '/v1/agent/attachments' ,{ method : 'POST' , headers :auth , body :form ()})). status , 201 );
holdDownload = true ;
for ( let n = 0 ; n < 5 ; n ++) {
const expected = held.length + 1 , terminal = closed.length + 1 ;
const req = httpRequest ( base + `/v1/agent/attachments/ ${ file . id } /content` ,{ headers : auth }, ()= >{}); req . on ( 'error' ,() => {}); req . end ();
await eventually (() => held . length === expected ); req . destroy (); await eventually (() => closed . length === terminal );
}
holdDownload = false ; assert . equal (( await fetch ( base + `/v1/agent/attachments/ ${ file . id } /content` ,{ headers :auth })). status , 200 );
});