33 lines
3.0 KiB
JavaScript
33 lines
3.0 KiB
JavaScript
import test from 'node:test';
|
|||
|
|
import assert from 'node:assert/strict';
|
||
|
|
import net from 'node:net';
|
||
|
|
import {createServer} from 'node:http';
|
||
|
|
import {mkdtempSync,rmSync} from 'node:fs';
|
||
|
|
import {tmpdir} from 'node:os';
|
||
|
|
import {join} from 'node:path';
|
||
|
|
import {randomUUID} from 'node:crypto';
|
||
|
|
import {openStore} from '../apps/server/store.mjs';
|
||
|
|
import {runApprovedAI} from '../apps/server/ai-gateway.mjs';
|
||
|
|
|
||
|
|
for(const change of ['revoke','same-version-policy','output-revoke','client-abort'])test(`gateway suppresses release after ${change} during actual ICAP scan`,async t=>{
|
||
|
|
const directory=mkdtempSync(join(tmpdir(),'safetynet-ai-race-')),store=openStore(directory),deviceId=randomUUID();
|
||
|
|
store.db.prepare('INSERT INTO devices(id,name,platform,user,token_hash) VALUES(?,?,?,?,?)').run(deviceId,'Fixture','win32','Fixture',randomUUID());
|
||
|
|
let deliveries=0,scans=0;const sockets=new Set(),controller=new AbortController();
|
||
|
|
const provider=createServer((req,res)=>{deliveries++;req.resume();res.end(JSON.stringify({choices:[{message:{content:'Public answer'}}]}));}).listen(0,'127.0.0.1');
|
||
|
|
await new Promise(r=>provider.once('listening',r));
|
||
|
|
const scanner=net.createServer(socket=>{sockets.add(socket);socket.on('error',()=>{});socket.once('close',()=>sockets.delete(socket));let bytes=Buffer.alloc(0),done=false;
|
||
|
|
socket.on('data',chunk=>{bytes=Buffer.concat([bytes,chunk]);if(done||!bytes.subarray(-5).equals(Buffer.from('0\r\n\r\n')))return;done=true;
|
||
|
|
if(bytes.subarray(0,7).toString()==='OPTIONS'){socket.end('ICAP/1.0 200 OK\r\nMethods: RESPMOD\r\nAllow: 204\r\nISTag: "fixture"\r\nEncapsulated: null-body=0\r\n\r\n');return;}
|
||
|
|
scans++;
|
||
|
|
if(change==='client-abort')controller.abort();
|
||
|
|
if(change==='revoke'||change==='output-revoke'&&scans===2)store.db.prepare('UPDATE devices SET revoked=1 WHERE id=?').run(deviceId);
|
||
|
|
else if(change==='same-version-policy'){const p=store.get('policy');p.ai.enabled=false;store.put('policy',p);}
|
||
|
|
socket.end('ICAP/1.0 204 No Content\r\nISTag: "fixture"\r\n\r\n');
|
||
|
|
});
|
||
|
|
}).listen(0,'127.0.0.1');await new Promise(r=>scanner.once('listening',r));
|
||
|
|
t.after(async()=>{for(const socket of sockets)socket.destroy();provider.closeAllConnections();await Promise.all([provider,scanner].map(server=>new Promise(r=>server.close(r))));store.db.close();rmSync(directory,{recursive:true,force:true});});
|
||
|
|
const p=store.get('policy');p.ai={...p.ai,enabled:true,trustMode:'local',model:'fixture'};p.enterprise.icap={...p.enterprise.icap,enabled:true,url:`icap://127.0.0.1:${scanner.address().port}/scan`,timeoutMs:1000};store.put('policy',p);
|
||
|
|
const policy=store.effectivePolicy(deviceId),config={aiBaseUrl:`http://127.0.0.1:${provider.address().port}`,aiModel:'fixture',aiEndpointId:'default'};
|
||
|
|
await assert.rejects(runApprovedAI({store,config,deviceId,policy,prompt:'Public fixture prompt',signal:controller.signal}),error=>error.status===(change==='same-version-policy'?409:403)&&error.ruleId==='ai-authorization-changed');
|
||
|
|
assert.equal(deliveries,change==='output-revoke'?1:0);
|
||
|
|
});
|