37 lines
3.7 KiB
JavaScript
37 lines
3.7 KiB
JavaScript
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);
|
|
});
|