39 lines
4.6 KiB
JavaScript
39 lines
4.6 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {mkdtempSync,rmSync} from 'node:fs';
|
|
import {tmpdir} from 'node:os';
|
|
import {join} from 'node:path';
|
|
import {createRequire} from 'node:module';
|
|
import {openStore,digest} from '../apps/server/store.mjs';
|
|
import {createApp} from '../apps/server/app.mjs';
|
|
import {verifyPolicy} from '../packages/core/policy.mjs';
|
|
const {reconcileGenerationFloor}=createRequire(import.meta.url)('../apps/browser/agent-grants.cjs');
|
|
async function fixture(t){
|
|
const dir=mkdtempSync(join(tmpdir(),'safetynet-agent-publication-')),store=openStore(dir),origin='http://127.0.0.1:4310';
|
|
const server=createApp(store,{publicUrl:origin,secure:false,requireIndependentPolicyApproval:true}).listen(0,'127.0.0.1');await new Promise(r=>server.once('listening',r));
|
|
t.after(async()=>{await new Promise(r=>server.close(r));store.db.close();rmSync(dir,{recursive:true,force:true});});
|
|
async function api(path,method='GET',body,subject='author',role='admin'){
|
|
const credential=`synthetic-agent-publication-${subject}-12345678901234567890`;
|
|
store.db.prepare('INSERT OR REPLACE INTO sessions VALUES(?,?,?)').run(digest(credential),Date.now()+60000,JSON.stringify({sub:subject,role}));
|
|
const response=await fetch(`http://127.0.0.1:${server.address().port}${path}`,{method,headers:{cookie:`sn_session=${credential}`,origin,'content-type':'application/json'},body:body===undefined?undefined:JSON.stringify(body)});
|
|
return {status:response.status,body:await response.json()};
|
|
}
|
|
const approve=(review,subject='reviewer',role='admin')=>api(`/api/admin/policy-reviews/${review.reviewId}/approve`,'POST',{payloadHash:review.payloadHash,expectedVersion:review.expectedVersion,approve:true},subject,role);
|
|
return {store,api,approve,signed:()=>verifyPolicy(store.envelope(),store.publicKey())};
|
|
}
|
|
const grant=generation=>({id:'research-agent',name:'Research Agent',enabled:true,expiresAt:'2099-12-31T23:59:59Z',scopes:['sessions','read'],allowedHosts:['*.company.example'],generation});
|
|
test('individual grants require independent review and survive signed history restoration without credential resurrection',async t=>{
|
|
const f=await fixture(t),candidate=f.store.get('policy');candidate.version++;candidate.agents={...candidate.agents,enabled:true,identityMode:'individual',scopes:['sessions','read'],allowedHosts:['*.company.example'],grants:[grant(1)]};
|
|
const queued=await f.api('/api/admin/policy','PUT',candidate);assert.equal(queued.status,202);assert.equal(f.signed().agents.enabled,false);
|
|
const detail=await f.api(`/api/admin/policy-reviews/${queued.body.reviewId}`);assert.equal(detail.status,200);assert.ok(detail.body.diff.changes.some(c=>c.path==='/agents/grants'&&c.after[0].id==='research-agent'));
|
|
assert.equal((await f.approve(queued.body,'author')).status,403);assert.equal((await f.approve(queued.body,'auditor','auditor')).status,403);assert.equal((await f.approve(queued.body)).status,200);assert.deepEqual(f.signed().agents.grants,[grant(1)]);
|
|
const floor1=reconcileGenerationFloor(f.signed().agents.grants).floor;
|
|
const rotated=f.store.get('policy');rotated.version++;rotated.agents.grants[0].generation=2;rotated.agents.grants[0].scopes=['sessions'];const rotation=await f.api('/api/admin/policy','PUT',rotated);assert.equal(rotation.status,202);assert.equal((await f.approve(rotation.body)).status,200);
|
|
const floor2=reconcileGenerationFloor(f.signed().agents.grants,floor1).floor;assert.equal(floor2['research-agent'],2);
|
|
const restore=await f.api('/api/admin/policy/restore','POST',{sourceVersion:2,expectedVersion:3,approve:true});assert.equal(restore.status,202);assert.equal(f.signed().version,3);
|
|
const restoreReview=await f.api(`/api/admin/policy-reviews/${restore.body.reviewId}`);assert.ok(restoreReview.body.diff.changes.some(c=>c.path==='/agents/grants'&&c.after[0].generation===1));assert.equal((await f.approve(restore.body,'author')).status,403);assert.equal((await f.approve(restore.body)).status,200);
|
|
const restored=f.signed();assert.equal(restored.version,4);assert.deepEqual(restored.agents.grants,[grant(1)]);const effective=reconcileGenerationFloor(restored.agents.grants,floor2);assert.equal(effective.grants[0].enabled,false);assert.equal(effective.floor['research-agent'],2);
|
|
const history=await f.api('/api/admin/policy/history/2');assert.deepEqual(history.body.policy.agents.grants,[grant(1)]);
|
|
assert.equal((await f.api('/api/admin/policy','PUT',{...restored,version:5},'auditor','auditor')).status,403);assert.equal((await f.api('/api/admin/policy/restore','POST',{sourceVersion:2,expectedVersion:4,approve:true},'auditor','auditor')).status,403);assert.equal(f.signed().version,4);
|
|
});
|