import test from 'node:test'; import assert from 'node:assert/strict'; import { chmodSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { spawnSync } from 'node:child_process'; import { checkManifest } from '../src/manifest.mjs'; import { pluginHash, readPluginLock, verifiedPlugin } from '../src/pinned.mjs'; import { installPlugin, removePlugin } from '../src/install.mjs'; import { createLoader } from '../src/loader.mjs'; import { organizationPlugins, assertPluginCapabilities } from '../src/organization.mjs'; import { pluginService } from '../src/tools.mjs'; import { typedTools } from '../../harness/src/tools.mjs'; import { resolveInstance } from '../../organization/src/resolve.mjs'; import { pluginInstallCommand } from '../../cli/src/plugin-install.mjs'; import { pluginSettings } from '../../organization/src/plugin-settings.mjs'; import { hasModule } from '../src/loader.mjs'; import { businessDoc, rolesCopy, writeJson } from '../../organization/tests/helpers.mjs'; const SECRET='sk-test-NOT-A-SECRET-1234'; function fixture(t) { const root=mkdtempSync(join(tmpdir(),'pl1-')),src=join(root,'src'),dataRoot=join(root,'data'); mkdirSync(src);mkdirSync(dataRoot);mkdirSync(join(src,'usage-source'));mkdirSync(join(src,'tools')); t.after(()=>{const thaw=p=>{const st=lstatSync(p);if(st.isSymbolicLink())return;if(st.isDirectory()){chmodSync(p,0o700);for(const n of readdirSync(p))thaw(join(p,n));}};thaw(root);rmSync(root,{recursive:true,force:true});}); writeFileSync(join(src,'usage-source','index.mjs'),'export const interfaceVersion=1; export const readTurn=()=>"fixture";'); writeFileSync(join(src,'tools','echo.mjs'),`export const execute=(args)=>({answer:args.text}); // ${SECRET}`); const manifest={name:'acmeplug',version:'1.0.0',kinds:['usage-source'],interfaceVersions:{'usage-source':1},sha256:'0'.repeat(64),source:{path:'fixture',version:'1.0.0'},signature:null,tools:[{name:'acmeplug.echo',description:SECRET,arguments:{type:'object',properties:{text:{type:'string'}},required:['text'],additionalProperties:false},runs:'host',entry:'tools/echo.mjs'}],actions:['acmeplug.publish'],services:[{name:'acmeplug.account',scopes:['read']}],roleTemplates:[{name:'acmeplug.writer',file:'writer.json'}]}; writeFileSync(join(src,'writer.json'),'{}'); const seal=()=>{writeFileSync(join(src,'plugin.json'),JSON.stringify(manifest));manifest.sha256=pluginHash(src);writeFileSync(join(src,'plugin.json'),JSON.stringify(manifest));return manifest.sha256;}; const hash=seal();const install=(source=src,extra={})=>installPlugin({dataRoot,source,expectedHash:hash,...extra}); return {root,src,dataRoot,manifest,seal,hash,install}; } const refused=code=>e=>e.code===code && !e.message.includes(SECRET); test('PL1 strict fields, kinds, core names, prefixes, hard waits and traversal refuse without echo',t=>{ const f=fixture(t); for(const mutate of [m=>m.extra=SECRET,m=>m.tools[0].extra=SECRET,m=>m.kinds=['unknown'],m=>m.tools[0].name='read',m=>m.tools[0].name='acmeplug.read',m=>m.actions=['acmeplug.deploy'],m=>m.actions=['acmeplug.git.push.remote'],m=>m.roleTemplates[0].file='../file',m=>m.tools[0].arguments.$ref=SECRET,m=>m.name='git']){ const m=structuredClone(f.manifest);mutate(m);assert.throws(()=>checkManifest(m),e=>!e.message.includes(SECRET)); } }); test('PL1 directory install is pinned and read-only; host tools printed before any instance write',t=>{ const f=fixture(t),printed=[];const entry=f.install(f.src,{print:line=>{assert.deepEqual(readPluginLock(f.dataRoot).plugins,{});assert.equal(existsSync(join(f.dataRoot,'plugins')),false);printed.push(line);}}); assert.equal(entry.sha256,f.hash);assert.deepEqual(printed,['host tool: acmeplug.echo']); for(const p of ['plugins/acmeplug','plugins/acmeplug/tools'])assert.equal(lstatSync(join(f.dataRoot,p)).mode&0o777,0o555); assert.equal(lstatSync(join(f.dataRoot,'plugins/acmeplug/plugin.json')).mode&0o777,0o444); assert.equal(verifiedPlugin(f.dataRoot,'acmeplug').manifest.sha256,f.hash); }); test('PL1 mismatch and symlink sources refuse before instance writes',t=>{ const f=fixture(t);assert.throws(()=>f.install(f.src,{expectedHash:'f'.repeat(64)}),refused('hash-mismatch')); symlinkSync('/etc/passwd',join(f.src,'escape'));assert.throws(()=>f.install(),refused('unsafe-path')); assert.equal(existsSync(join(f.dataRoot,'plugins')),false); }); test('PL1 zip traversal, symlinks, and git URL without a full commit refuse',t=>{ const f=fixture(t);const zip=join(f.root,'bad.zip'); for(const script of ["z.writestr('../escape','bad')","e=zipfile.ZipInfo('link');e.create_system=3;e.external_attr=(0o120777<<16);z.writestr(e,'/etc/passwd')"]){ assert.equal(spawnSync('python3',['-c',`import zipfile\nz=zipfile.ZipFile(${JSON.stringify(zip)},'w')\n${script}\nz.close()`]).status,0); assert.throws(()=>f.install(zip),refused('source-refused')); } assert.throws(()=>f.install('https://example.invalid/repo.git'),refused('commit-required')); assert.throws(()=>f.install(f.src,{commit:'abc'}),refused('commit-required')); }); test('PL1 directory, generated zip and local bare git at full commit land the same hash',t=>{ const f=fixture(t);const zip=join(f.root,'source.zip'); assert.equal(spawnSync('python3',['-c',`import zipfile,os\nz=zipfile.ZipFile(${JSON.stringify(zip)},'w')\nfor root,dirs,files in os.walk(${JSON.stringify(f.src)}):\n for n in files:z.write(os.path.join(root,n),os.path.relpath(os.path.join(root,n),${JSON.stringify(f.src)}))\nz.close()`]).status,0); const git=(args)=>{const r=spawnSync('git',args,{encoding:'utf8'});assert.equal(r.status,0,r.stderr);return r.stdout.trim();}; git(['init',f.src]);git(['-C',f.src,'add','.']);git(['-C',f.src,'-c','user.name=Fixture','-c','user.email=fixture@example.invalid','commit','-m','fixture']); const commit=git(['-C',f.src,'rev-parse','HEAD']),bare=join(f.root,'bare.git');git(['clone','--bare',f.src,bare]); // The local directory used for install intentionally has no .git administrative files. rmSync(join(f.src,'.git'),{recursive:true}); for(const [source,opts]of [[f.src,{}],[zip,{}],[bare,{commit}]]){ const d=mkdtempSync(join(f.root,'instance-'));const r=installPlugin({dataRoot:d,source,expectedHash:f.hash,...opts});assert.equal(r.sha256,f.hash);assert.equal(readPluginLock(d).plugins.acmeplug.sha256,f.hash); } }); test('PL1 changed file/missing lock refuses next load and evicts installed code, core keeps running',async t=>{ const f=fixture(t);f.install();const logs=[],loader=createLoader({dataRoot:f.dataRoot,log:e=>logs.push(e)}); const settings={'usage-source':['transcript','acmeplug'],harness:['pi']};assert.equal((await loader.load(settings)).refused.length,0); const file=join(f.dataRoot,'plugins/acmeplug/tools/echo.mjs');chmodSync(file,0o600);writeFileSync(file,SECRET); const result=await loader.load(settings);assert.equal(result.refused[0].code,'hash-mismatch');assert.deepEqual(loader.loaded()['usage-source'],['transcript']);assert.deepEqual(loader.loaded().harness,['pi']); assert.ok(!JSON.stringify(logs).includes(SECRET)); rmSync(join(f.dataRoot,'plugins.lock.json'));assert.equal((await loader.load(settings)).refused[0].code,'missing-lock'); }); test('PL1 registry includes only enabled verified declarations, services and templates',t=>{ const f=fixture(t);f.install();const view=organizationPlugins(f.dataRoot,{'usage-source':['acmeplug']}); assert.ok(view.registry.tools.includes('acmeplug.echo'));assert.ok(view.registry.routed.includes('acmeplug.publish'));assert.deepEqual(view.registry.services['acmeplug.account'].scopes,['read']);assert.equal(view.roleTemplates[0].name,'acmeplug.writer'); for(const settings of [{},{'usage-source':['missing']}])assert.throws(()=>assertPluginCapabilities({tools:['acmeplug.echo']},organizationPlugins(f.dataRoot,settings)),e=>e.code==='plugin-disabled' && e.plugin==='acmeplug' && !e.message.includes(SECRET)); }); test('PL1 disabled or missing tools/actions refuse at launch with name only, enabled tool is bundled',t=>{ const f=fixture(t);f.install();const definition={name:'writer',contractPath:'/contract',tools:['acmeplug.echo'],network:'none',authority:{withinRole:['acmeplug.publish'],crossRole:[]},credentials:[],messaging:null,skills:[]}; const business={id:'acme',roles:{writer:{definition:'writer',vars:{},credentials:{}}},definitions:{writer:definition},vars:{},arbiters:{}}; const args={system:{dataRoot:f.dataRoot},business,instance:'writer',enforcePlugins:true}; assert.throws(()=>resolveInstance(args),e=>e.code==='plugin-disabled' && e.plugin==='acmeplug'); business.plugins={'usage-source':['acmeplug']};const r=resolveInstance(args);assert.equal(typedTools({resolved:r}).find(t=>t.name==='acmeplug.echo').pluginTool,'acmeplug.echo'); business.plugins={};assert.throws(()=>resolveInstance(args),refused('plugin-disabled')); }); test('PL1 running tool calls recheck enabled setting, pin, arguments and held tool',async t=>{ const f=fixture(t);f.install();const role={tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]}};let enabled=true; const service=pluginService({dataRoot:f.dataRoot,read:()=>({plugins:enabled?{'usage-source':['acmeplug']}:{},roles:{writer:role}})}); assert.deepEqual(await service.call('acme','writer','acmeplug.echo',{text:'hello'}),{answer:'hello'}); await assert.rejects(service.call('acme','writer','acmeplug.echo',{unexpected:SECRET}),refused('invalid-request')); enabled=false;await assert.rejects(service.call('acme','writer','acmeplug.echo',{text:SECRET}),e=>e.code==='plugin-disabled' && e.plugin==='acmeplug' && !e.message.includes(SECRET)); }); test('PL1 remove refuses enabled organizations; host CLI rejects organization/tester options and agents',async t=>{ const f=fixture(t);f.install();assert.throws(()=>removePlugin({dataRoot:f.dataRoot,name:'acmeplug',enabled:()=>true}),refused('plugin-enabled')); const dir=join(f.root,'config');mkdirSync(dir,{mode:0o700});let out='';const io={env:{},stdout:{write:v=>out+=v}}; for(const args of [['install',f.src,'--sha256',f.hash,'--org','acme'],['install',f.src,'--sha256',f.hash,'--tester']])await assert.rejects(pluginInstallCommand(args,io,{system:{dataRoot:f.dataRoot},dir})); await assert.rejects(pluginInstallCommand(['list'],{...io,env:{MOSAIC_RUN_ID:'fixture'}},{system:{dataRoot:f.dataRoot},dir})); writeJson(join(dir,'businesses','acme.json'),{plugins:{'usage-source':['acmeplug']}}); await assert.rejects(pluginInstallCommand(['remove','acmeplug'],io,{system:{dataRoot:f.dataRoot},dir}),e=>e.message.includes('plugin-enabled')); }); test('PL1 enabling a tampered installed plugin refuses before organization write',t=>{ const f=fixture(t);f.install();const doc=businessDoc(f.root),rolesDir=rolesCopy(f.root);writeJson(join(f.root,'businesses','acme.json'),doc); const settings=pluginSettings({dir:f.root,rolesDir,dataRoot:f.dataRoot,exists:(k,n)=>hasModule(k,n,f.dataRoot)}); const before=settings.read('acme');const file=join(f.dataRoot,'plugins/acmeplug/tools/echo.mjs');chmodSync(file,0o600);writeFileSync(file,SECRET); assert.throws(()=>settings.set('acme',{revision:before.revision,plugins:{'usage-source':['acmeplug']}},'jason'),refused('invalid-plugins')); assert.deepEqual(settings.read('acme'),before); }); test('PL1 sandbox tools execute with no home or network namespace access',async t=>{ const f=fixture(t);f.manifest.tools[0].runs='sandbox'; writeFileSync(join(f.src,'tools/echo.mjs'),`import {existsSync} from 'node:fs';export const execute=()=>({home:existsSync('/home'),config:existsSync('/mnt/storage')});`); const hash=f.seal();f.install(f.src,{expectedHash:hash}); const service=pluginService({dataRoot:f.dataRoot,read:()=>({plugins:{'usage-source':['acmeplug']},roles:{writer:{tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]}}}})}); assert.deepEqual(await service.call('acme','writer','acmeplug.echo',{text:'hello'}),{home:false,config:false}); }); test('PL1 broker launch and running dispatch enforce enable state; organization install and member/agent enable refuse',async t=>{ const {Store}=await import('../../bus/src/store.mjs');const {Broker}=await import('../../bus/src/broker.mjs'); const f=fixture(t);f.install();const authority={withinRole:[],crossRole:[]}; const business={id:'acme',human:'owner',plugins:{'usage-source':['acmeplug']},arbiters:{technical:'chief',delivery:'chief'},roles:{chief:{authority},writer:{tools:['acmeplug.echo'],authority}}}; const service=pluginService({dataRoot:f.dataRoot,read:()=>business});const store=new Store(f.dataRoot);t.after(()=>store.close()); const b=new Broker({store,businesses:{acme:business},pluginService:service}); const cap=b.bindLaunch({business:'acme',role:'writer',run:'writer-1',harness:'pi'});b.request(cap,{verb:'role.claim'}); assert.deepEqual(await b.request(cap,{verb:'plugin.tool',args:{tool:'acmeplug.echo',arguments:{text:'working'}}}),{answer:'working'}); const member=b.bindReader({business:'acme'}); for(const c of [cap,member])assert.throws(()=>b.request(c,{verb:'plugins.set',args:{revision:'a'.repeat(64),plugins:{'usage-source':['acmeplug']}}}),/human-required/); assert.throws(()=>b.request(cap,{verb:'plugins.install',args:{source:SECRET}}),e=>!e.message.includes(SECRET)); business.plugins={}; await assert.rejects(b.request(cap,{verb:'plugin.tool',args:{tool:'acmeplug.echo',arguments:{text:SECRET}}}),e=>e.code==='plugin-disabled' && e.reason==='acmeplug' && !e.message.includes(SECRET)); assert.throws(()=>b.bindLaunch({business:'acme',role:'writer',run:'writer-2',harness:'pi'}),e=>e.code==='plugin-disabled' && e.reason==='acmeplug'); assert.equal(b.bindLaunch({business:'acme',role:'chief',run:'chief-1',harness:'pi'}).length,64,'core can still launch'); }); test('PL1 registry preserves capability categories and credential scope ceilings',t=>{ const f=fixture(t);f.install();const view=organizationPlugins(f.dataRoot,{'usage-source':['acmeplug']}); for(const role of [{tools:['acmeplug.publish']},{authority:{withinRole:['acmeplug.echo']}},{credentials:[{service:'acmeplug.account',scopes:['admin']}]}])assert.throws(()=>assertPluginCapabilities(role,view),refused('invalid-request')); }); test('PL1 manifest whitespace is pinned; remove succeeds after the owner disables it',t=>{ const f=fixture(t);f.install();const file=join(f.dataRoot,'plugins/acmeplug/plugin.json');chmodSync(file,0o600);writeFileSync(file,readFileSync(file,'utf8')+'\n'); assert.throws(()=>verifiedPlugin(f.dataRoot,'acmeplug'),refused('hash-mismatch')); removePlugin({dataRoot:f.dataRoot,name:'acmeplug',enabled:()=>false}); assert.deepEqual(readPluginLock(f.dataRoot).plugins,{});assert.equal(existsSync(join(f.dataRoot,'plugins/acmeplug')),false); }); test('PL1 CLI host install/list/remove never calls a bus transport',async t=>{ const {main}=await import('../../cli/src/cli.mjs');const f=fixture(t),dir=join(f.root,'config');mkdirSync(dir,{mode:0o700}); let output='';const io={env:{},stdout:{write:v=>output+=v},stderr:{write:()=>{}}};const deps={system:{dataRoot:f.dataRoot},accountsConfigDir:dir,transport:()=>{throw Error('bus must not be used');}}; await main(['plugins','install',f.src,'--sha256',f.hash],io,deps);assert.match(output,/host tool: acmeplug.echo/);assert.ok(!output.includes(SECRET)); output='';await main(['plugins','list'],io,deps);assert.equal(JSON.parse(output).plugins.acmeplug.sha256,f.hash); output='';await main(['plugins','remove','acmeplug'],io,deps);assert.deepEqual(JSON.parse(output),{removed:'acmeplug'}); }); test('PL1 a running plugin tool obeys a newly narrowed tool ceiling',async t=>{ const f=fixture(t);f.install();const b={plugins:{'usage-source':['acmeplug']},roles:{writer:{tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]},vars:{}}}}; const service=pluginService({dataRoot:f.dataRoot,read:()=>b});assert.deepEqual(await service.call('acme','writer','acmeplug.echo',{text:'before'}),{answer:'before'}); b.roles.writer.vars['limits.tools']=['read'];await assert.rejects(service.call('acme','writer','acmeplug.echo',{text:'after'}),refused('not-authorized')); }); test('PL1 an installed package cannot reuse a core module name from another kind',t=>{ const f=fixture(t);f.manifest.name='pi';for(const tool of f.manifest.tools)tool.name=tool.name.replace('acmeplug.','pi.');f.manifest.actions=['pi.publish'];f.manifest.services[0].name='pi.account';f.manifest.roleTemplates[0].name='pi.writer'; const hash=f.seal();assert.throws(()=>f.install(f.src,{expectedHash:hash}),refused('core-name')); assert.equal(existsSync(join(f.dataRoot,'plugins')),false); }); test('PL1 malformed settings cannot retain an installed module whose pin is now bad',async t=>{ const f=fixture(t);f.install();const loader=createLoader({dataRoot:f.dataRoot,log:()=>{}});await loader.load({'usage-source':['transcript','acmeplug'],harness:['pi']}); const file=join(f.dataRoot,'plugins/acmeplug/tools/echo.mjs');chmodSync(file,0o600);writeFileSync(file,SECRET); await loader.load(null);assert.deepEqual(loader.loaded()['usage-source'],['transcript']);assert.deepEqual(loader.loaded().harness,['pi']); }); test('PL1 runtime discovers installed harness levels from the instance root',async t=>{ const {harnessLevels}=await import('../../bus/src/runtime.mjs'); const {KINDS}=await import('../src/kinds.mjs'); const f=fixture(t);f.manifest.kinds=['harness'];f.manifest.interfaceVersions={harness:1}; mkdirSync(join(f.src,'harness')); writeFileSync(join(f.src,'harness/index.mjs'),KINDS.harness.exports.map(name=>`export function ${name}(){return ${name==='levels'?"['high']":name==='countsAccount'?'true':'null'};}`).join('\n')+'\nexport const interfaceVersion=1;'); const hash=f.seal();f.install(f.src,{expectedHash:hash}); const levels=await harnessLevels({dataRoot:f.dataRoot,log:()=>{}}); assert.deepEqual(levels('acmeplug'),['high']);assert.ok(levels.names.includes('pi')); // #1873: the module's countsAccount answer, until the lock pins another package for that name. assert.equal(levels.countsAccount('acmeplug'),true); const lock=join(f.dataRoot,'plugins.lock.json'),doc=JSON.parse(readFileSync(lock,'utf8')); doc.plugins.acmeplug.sha256='f'.repeat(64);writeFileSync(lock,JSON.stringify(doc)); assert.equal(levels.countsAccount('acmeplug'),null,'a replaced pin is not covered until restart'); assert.equal(levels.countsAccount('pi'),false,'a tree module keeps its answer'); }); test('PL1 invalid UTF-8 manifest bytes refuse instead of normalizing outside the pin',t=>{ const f=fixture(t),path=join(f.src,'plugin.json'),bytes=readFileSync(path); bytes[bytes.indexOf(SECRET)]=0xff;writeFileSync(path,bytes); assert.throws(()=>pluginHash(f.src),refused('invalid-manifest')); }); test('PL1 tool exceptions cannot impersonate trusted refusals or echo payload bytes',async t=>{ const f=fixture(t); writeFileSync(join(f.src,'tools/echo.mjs'),`export function execute(){throw Object.assign(new Error(${JSON.stringify(SECRET)}),{name:'PluginError',code:'plugin-disabled',plugin:${JSON.stringify(SECRET)}});}`); const hash=f.seal();f.install(f.src,{expectedHash:hash}); const service=pluginService({dataRoot:f.dataRoot,read:()=>({plugins:{'usage-source':['acmeplug']},roles:{writer:{tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]}}}})}); await assert.rejects(service.call('acme','writer','acmeplug.echo',{text:'hello'}),e=>e.code==='plugin-tool-failed' && e.plugin==='acmeplug' && !e.message.includes(SECRET)); }); test('PL1 A3 organization copy readers preserve the derived installed-plugin root',async t=>{ const {withCopies,loadWithCopies}=await import('../../organization/src/agent-copies.mjs'); const f=fixture(t);f.manifest.kinds=['system-type'];f.manifest.interfaceVersions={'system-type':1}; mkdirSync(join(f.src,'system-type'));writeFileSync(join(f.src,'system-type/index.mjs'),'export const interfaceVersion=1;export function systemType(){return {}};'); const hash=f.seal();f.install(f.src,{expectedHash:hash}); const doc=businessDoc(f.root),rolesDir=rolesCopy(f.root);doc.plugins={...doc.plugins,'system-type':['acmeplug']};writeJson(join(f.root,'businesses/acme.json'),doc); const options={rolesDir,dataRoot:f.dataRoot,snapshot:null}; assert.deepEqual(withCopies(doc,join(f.root,'businesses/acme.json'),options).plugins['system-type'],['acmeplug']); assert.deepEqual(loadWithCopies('acme',{...options,dir:f.root}).plugins['system-type'],['acmeplug']); const installed=join(f.dataRoot,'plugins/acmeplug/system-type/index.mjs');chmodSync(installed,0o600);writeFileSync(installed,SECRET); // Config checks names only; the loader owns bad-pin refusal and preserves core. assert.deepEqual(withCopies(doc,join(f.root,'businesses/acme.json'),options).plugins['system-type'],['acmeplug']); doc.plugins['system-type']=['defualt']; assert.throws(()=>withCopies(doc,join(f.root,'businesses/acme.json'),options),/unknown module/); }); test('PL1 replacement pins refuse across loader and host tools until a fresh process',async t=>{ for(const first of ['loader','tool']) { const f=fixture(t); writeFileSync(join(f.src,'helper.mjs'),'export const value=1;'); writeFileSync(join(f.src,'usage-source/index.mjs'),"import {value} from '../helper.mjs';export const interfaceVersion=1;export const readTurn=()=>value;"); writeFileSync(join(f.src,'tools/echo.mjs'),"import {value} from '../helper.mjs';export const execute=()=>value;"); const pin=f.seal();f.install(f.src,{expectedHash:pin}); const settings={'usage-source':['transcript','acmeplug'],harness:['pi']}; const loader=createLoader({dataRoot:f.dataRoot,log:()=>{}}); const service=pluginService({dataRoot:f.dataRoot,read:()=>({plugins:settings,roles:{writer:{tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]}}}})}); if(first==='loader'){await loader.load(settings);assert.equal(loader.modules('usage-source')[1].readTurn(),1);} else assert.equal(await service.call('acme','writer','acmeplug.echo',{text:'before'}),1); removePlugin({dataRoot:f.dataRoot,name:'acmeplug',enabled:()=>false}); writeFileSync(join(f.src,'helper.mjs'),'export const value=2;');f.manifest.version='2.0.0'; const next=f.seal();assert.notEqual(next,pin);f.install(f.src,{expectedHash:next}); // Verification, new loaders, and the other import path share one process latch. assert.throws(()=>verifiedPlugin(f.dataRoot,'acmeplug'),refused('plugin-restart-required')); await assert.rejects(service.call('acme','writer','acmeplug.echo',{text:'after'}),refused('plugin-disabled')); const freshLoader=createLoader({dataRoot:f.dataRoot,log:()=>{}}),result=await freshLoader.load(settings); assert.equal(result.refused[0].code,'plugin-restart-required'); assert.deepEqual(result.loaded['usage-source'],['transcript']);assert.deepEqual(result.loaded.harness,['pi']); await loader.load(null);assert.deepEqual(loader.loaded()['usage-source'],first==='loader'?['transcript']:[]); const script=`import {createLoader} from ${JSON.stringify(import.meta.resolve('../src/loader.mjs'))}; const loader=createLoader({dataRoot:process.argv[1],log:()=>{}});const r=await loader.load({'usage-source':['acmeplug']}); console.log(JSON.stringify({refused:r.refused,value:loader.modules('usage-source')[0].readTurn()}));`; const child=spawnSync(process.execPath,['--input-type=module','-e',script,f.dataRoot],{encoding:'utf8'}); assert.equal(child.status,0,child.stderr);assert.deepEqual(JSON.parse(child.stdout),{refused:[],value:2}); } }); test('PL1 even a failed import claims its package pin before dependencies enter the cache',async t=>{ const f=fixture(t);writeFileSync(join(f.src,'helper.mjs'),'export const value=1;'); writeFileSync(join(f.src,'usage-source/index.mjs'),"import '../helper.mjs';throw Error('fixture');"); let pin=f.seal();f.install(f.src,{expectedHash:pin}); const loader=createLoader({dataRoot:f.dataRoot,log:()=>{}}); assert.equal((await loader.load({'usage-source':['acmeplug']})).refused[0].code,'load-failed'); removePlugin({dataRoot:f.dataRoot,name:'acmeplug',enabled:()=>false}); writeFileSync(join(f.src,'usage-source/index.mjs'),'export const interfaceVersion=1;export const readTurn=()=>2;'); pin=f.seal();f.install(f.src,{expectedHash:pin}); assert.equal((await loader.load({'usage-source':['transcript','acmeplug']})).refused[0].code,'plugin-restart-required'); assert.deepEqual(loader.loaded()['usage-source'],['transcript']); }); test('PL1 plugin imports refuse unpinned bare, escaping, dynamic and require dependencies',async t=>{ for(const spec of ['extdep','../../../outside.mjs']) { const f=fixture(t),marker=join(f.dataRoot,'executed'); const outside=`import {writeFileSync} from 'node:fs';writeFileSync(${JSON.stringify(marker)},'ran');export const value=1;`; mkdirSync(join(f.dataRoot,'node_modules/extdep'),{recursive:true}); writeFileSync(join(f.dataRoot,'node_modules/extdep/package.json'),JSON.stringify({type:'module',main:'index.mjs'})); writeFileSync(join(f.dataRoot,'node_modules/extdep/index.mjs'),outside);writeFileSync(join(f.dataRoot,'outside.mjs'),outside); writeFileSync(join(f.src,'usage-source/index.mjs'),`import ${JSON.stringify(spec)};export const interfaceVersion=1;export const readTurn=()=>1;`); const pin=f.seal();f.install(f.src,{expectedHash:pin}); const result=await createLoader({dataRoot:f.dataRoot,log:()=>{}}).load({'usage-source':['transcript','acmeplug'],harness:['pi']}); assert.equal(result.refused[0].code,'load-failed');assert.deepEqual(result.loaded['usage-source'],['transcript']);assert.deepEqual(result.loaded.harness,['pi']);assert.equal(existsSync(marker),false); } for(const body of ["return (await import('../../../outside.mjs')).value;","return require('../../../outside.cjs');"]) { const f=fixture(t),marker=join(f.dataRoot,'executed'); writeFileSync(join(f.dataRoot,'outside.mjs'),`import {writeFileSync} from 'node:fs';writeFileSync(${JSON.stringify(marker)},'ran');export const value=1;`); writeFileSync(join(f.dataRoot,'outside.cjs'),`require('node:fs').writeFileSync(${JSON.stringify(marker)},'ran');module.exports=1;`); writeFileSync(join(f.src,'tools/echo.mjs'),`import {createRequire} from 'node:module';const require=createRequire(import.meta.url);export async function execute(){${body}}`); const pin=f.seal();f.install(f.src,{expectedHash:pin}); const service=pluginService({dataRoot:f.dataRoot,read:()=>({plugins:{'usage-source':['acmeplug']},roles:{writer:{tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]}}}})}); await assert.rejects(service.call('acme','writer','acmeplug.echo',{text:SECRET}),refused('plugin-tool-failed'));assert.equal(existsSync(marker),false); } }); test('PL1 sandbox tools also refuse executable data URLs outside the pinned package',async t=>{ const f=fixture(t);f.manifest.tools[0].runs='sandbox'; writeFileSync(join(f.src,'tools/echo.mjs'),"export async function execute(){return (await import('data:text/javascript,export default 42')).default;}"); const pin=f.seal();f.install(f.src,{expectedHash:pin}); const service=pluginService({dataRoot:f.dataRoot,read:()=>({plugins:{'usage-source':['acmeplug']},roles:{writer:{tools:['acmeplug.echo'],authority:{withinRole:[],crossRole:[]}}}})}); await assert.rejects(service.call('acme','writer','acmeplug.echo',{text:SECRET}),refused('plugin-tool-failed')); }); test('PL1 lock package names use the manifest length bound',t=>{ const f=fixture(t);writeFileSync(join(f.dataRoot,'plugins.lock.json'),JSON.stringify({version:1,plugins:{['a'.repeat(33)]:{version:'1.0.0',source:f.manifest.source,sha256:f.hash}}}),{mode:0o600}); assert.throws(()=>readPluginLock(f.dataRoot),refused('invalid-lock')); assert.throws(()=>verifiedPlugin(f.dataRoot,'a'.repeat(33)),refused('bad-name')); });