Files
safetynet/tests/dpapi-helper.test.mjs
T

36 lines
3.7 KiB
JavaScript
Raw Normal View History

import test from 'node:test';
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {EventEmitter} from 'node:events';
const {createCapabilityCrypto}=createRequire(import.meta.url)('../apps/browser/dpapi.cjs');
test('capability helper launches fixed isolated Python without startup injection or inherited secrets',async()=>{
let observed;const child=new EventEmitter();child.stdout=new EventEmitter();child.stderr=new EventEmitter();child.stdin=new EventEmitter();child.stdin.end=()=>{queueMicrotask(()=>{child.stdout.emit('data',Buffer.from('synthetic-ciphertext'));child.emit('close',0);});};child.kill=()=>{};
const convert=createCapabilityCrypto({root:'C:/trusted/safetynet',environment:{SystemRoot:'C:/Windows',TEMP:'C:/temporary',PYTHONPATH:'injected',PYTHONHOME:'injected',PYTHONINSPECT:'1',NODE_OPTIONS:'injected',UNLISTED_SECRET:'synthetic-secret'},launch:(file,args,options)=>{observed={file,args,options};return child;}});
assert.equal(await convert('protect','synthetic-input'),'synthetic-ciphertext');
assert.ok(observed.file.endsWith('python.exe'));assert.deepEqual(observed.args.slice(0,2),['-I','-B']);assert.equal(observed.args.at(-1),'protect');assert.ok(observed.args[2].endsWith('dpapi.py'));
assert.deepEqual(observed.options.env,{SystemRoot:'C:/Windows',TEMP:'C:/temporary'});assert.equal(observed.options.shell,false);assert.equal(observed.options.windowsHide,true);
});
test('invalid capability input never spawns a helper',async()=>{
let calls=0;const convert=createCapabilityCrypto({root:'C:/trusted',launch:()=>{calls++;throw Error('must not launch');}});
for(const [operation,input] of [['arbitrary','x'],['protect',''],['protect',{}],['protect',Buffer.alloc(32769)],['unprotect',Buffer.alloc(65537)]])await assert.rejects(convert(operation,input),/Capability rejected/);
assert.equal(calls,0);
});
function fakeChild(){const child=new EventEmitter();child.stdout=new EventEmitter();child.stderr=new EventEmitter();child.stdin=new EventEmitter();child.stdin.end=()=>{};child.kills=0;child.kill=()=>{child.kills++;};return child;}
test('failed output stays bounded and original unclosed helpers retain eight slots',async()=>{
const children=[];const convert=createCapabilityCrypto({root:'C:/trusted',launch:()=>{const child=fakeChild();children.push(child);return child;}});
for(let index=0;index<8;index++){
const pending=assert.rejects(convert('protect','synthetic'),/Windows capability protection failed/),child=children[index];
child.stdout.emit('data',Buffer.alloc(65537));for(let late=0;late<10;late++)child.stdout.emit('data',Buffer.alloc(65536));await pending;assert.equal(child.kills,1);
}
await assert.rejects(convert('protect','synthetic'),/Windows capability protection failed/);assert.equal(children.length,8);
children[0].emit('close',1);const next=convert('protect','synthetic');assert.equal(children.length,9);
children[8].stdout.emit('data',Buffer.from('ciphertext'));children[8].emit('close',0);assert.equal(await next,'ciphertext');
for(const child of children.slice(1,8))child.emit('close',1);
});
test('synchronous launch and pipe faults expose fixed errors with owned cleanup',async()=>{
const convert=createCapabilityCrypto({root:'C:/trusted',launch:()=>{throw Error('PRIVATE_PATH_TOKEN');}});
for(let attempt=0;attempt<2;attempt++)await assert.rejects(convert('protect','synthetic'),{message:'Windows capability protection failed'});
const child=fakeChild(),pipe=createCapabilityCrypto({root:'C:/trusted',launch:()=>child});
const pending=assert.rejects(pipe('protect','synthetic'),{message:'Windows capability protection failed'});
child.stdout.emit('error',Error('PRIVATE_PATH_TOKEN'));await pending;assert.equal(child.kills,1);child.emit('close',1);
});