/** Genuine Ed25519/files; POSIX permission metadata is modeled on Windows. * No SQL, service, deployment or real POSIX acceptance is claimed. */ import test from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs/promises' import path from 'node:path' import os from 'node:os' import crypto from 'node:crypto' import {createRequire} from 'node:module' import type {Stats,BigIntStats} from 'node:fs' import type {DatabaseSourceDeploymentPin} from '../../src/db/database-source-binding.ts' import type * as ProtectedApi from './protected-source-registry.mts' import {runInNewContext} from 'node:vm' import {readProtectedDatabaseSourceBindingRegistry} from './protected-source-registry.mts' import {readDatabaseSourceBindingRegistry,sourceBindingBytes,sourceBindingCanonical,sourceBindingSha256,DATABASE_SOURCE_SIGNATURE_DOMAINS} from '../../src/db/database-source-binding.ts' const require=createRequire(import.meta.url),ts=require('typescript') const helperSource=await fs.readFile(new URL('./operator-files.mjs',import.meta.url),'utf8') const source=await fs.readFile(new URL('./protected-source-registry.mts',import.meta.url),'utf8') const deadline=()=>({deadlineAt:Date.now()+10000}) function compile(text:string){return ts.transpileModule(text,{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022,esModuleInterop:true}}).outputText} function evaluate>(text:string,imports:Record,processModel:unknown=process):T{ const exports:Record={} runInNewContext(compile(text),{exports,require:(name:string)=>imports[name]??require(name),process:processModel,Buffer,TextDecoder,console}) return exports as T } interface Fixture {pin:DatabaseSourceDeploymentPin;faults:Record;api:typeof ProtectedApi;verificationReads:()=>number;root:string} async function own(work:(f:Fixture)=>Promise){ const root=await fs.mkdtemp(path.join(os.tmpdir(),'mm-protected-source-')) try{ const key=crypto.generateKeyPairSync('ed25519'),installationId=crypto.randomUUID() const signed=(body:unknown,domain:string)=>sourceBindingBytes({body,signature:crypto.sign(null,Buffer.from(domain+sourceBindingCanonical(body)),key.privateKey).toString('base64url')}) const body={format:'database-source-binding-v1',installationId,sourceIdentityId:crypto.randomUUID(),generation:crypto.randomUUID(),ordinal:1, source:{systemIdentifier:'123',databaseOid:16384,databaseName:'synthetic',sessionUser:'capture',currentUser:'capture',inRecovery:false,serverVersionNum:180004}} const binding=signed(body,DATABASE_SOURCE_SIGNATURE_DOMAINS.binding),bindingSha256=sourceBindingSha256(binding) const head=signed({format:'database-source-registry-head-v1',installationId,sourceIdentityId:body.sourceIdentityId,generation:body.generation,ordinal:1,bindingSha256,previousHeadSha256:null},DATABASE_SOURCE_SIGNATURE_DOMAINS.head) const pin={registryDirectory:root,installationId,publicKeySpkiSha256:sourceBindingSha256(key.publicKey.export({format:'der',type:'spki'})),currentHeadSha256:sourceBindingSha256(head)} await fs.writeFile(path.join(root,'operator-public.pem'),key.publicKey.export({format:'pem',type:'spki'}),{mode:0o600}) await fs.writeFile(path.join(root,'head-'+pin.currentHeadSha256+'.json'),head,{mode:0o600}) await fs.writeFile(path.join(root,'binding-'+bindingSha256+'.json'),binding,{mode:0o600}) const faults={modeFile:false,modeDirectory:false,symlink:false,replacement:false,lateMode:false,changed:false};let verificationReads=0 const metadata=(s:Stats|BigIntStats,filename:string,big:boolean)=>{ const n=(value:number)=>big?BigInt(value):value,isRoot=filename===root,isFile=s.isFile() const mode=(isFile?0o100600:0o40700)|((isFile&&faults.modeFile||isRoot&&faults.modeDirectory||isFile&&faults.lateMode&&faults.changed)?0o022:0) return {...s,mode:n(mode),uid:n(1000),gid:n(1000),ino:faults.replacement&&faults.changed&&isFile?(typeof s.ino==='bigint'?s.ino+1n:s.ino+1):s.ino, isFile:()=>isFile,isDirectory:()=>s.isDirectory(),isSymbolicLink:()=>faults.symlink&&isRoot} } const modelFs={...fs,lstat:async(filename:string,options?:{bigint?:boolean})=>metadata(await fs.lstat(filename,{bigint:options?.bigint===true}),filename,options?.bigint===true),open:async(filename:string,flags:number)=>{ const file=await fs.open(filename,flags) return {stat:async()=>metadata(await file.stat(),filename,false),read:file.read.bind(file),close:file.close.bind(file)} }} const processModel={platform:'linux',getuid:()=>1000} const proven=evaluate(helperSource,{'node:fs/promises':modelFs},processModel) const bindingModule={readDatabaseSourceBindingRegistry:async(p:DatabaseSourceDeploymentPin)=>{ verificationReads++;const result=await readDatabaseSourceBindingRegistry(p);faults.changed=true;return result },createDatabaseSourceBindingVerifier:()=>({verify:async()=>{verificationReads++;faults.changed=true;return {genuineSourceFactsMatched:true,generationAuthenticated:false,readyAuthorized:false}}})} const api=evaluate(source,{'node:fs/promises':modelFs,'./operator-files.mjs':proven,'../../src/db/database-source-binding.js':bindingModule},processModel) await work({pin,faults,api,verificationReads:()=>verificationReads,root}) }finally{const rel=path.relative(os.tmpdir(),root);assert(rel&&!rel.startsWith('..')&&!path.isAbsolute(rel));await fs.rm(root,{recursive:true,force:true})} } test('unsupported Windows refuses before registry signature reads',{skip:process.platform!=='win32'},async()=>{ await assert.rejects(readProtectedDatabaseSourceBindingRegistry({registryDirectory:path.resolve('not-created-protected-source'),installationId:crypto.randomUUID(),publicKeySpkiSha256:'a'.repeat(64),currentHeadSha256:'b'.repeat(64)},deadline()),/Windows ACL/) }) test('proven POSIX helpers plus genuine signature/head bytes retain false generation/READY',()=>own(async f=>{ const value=await f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()) assert.equal(value.platformProtectionVerified,true);assert.equal(value.generationAuthenticated,false);assert.equal(value.allWriterDrainAuthenticated,false);assert.equal(value.readyAuthorized,false) assert.equal(value.headSha256,f.pin.currentHeadSha256);assert.equal(f.verificationReads(),1) })) for(const fault of ['modeFile','modeDirectory','symlink'])test(fault+' refuses before cryptographic registry read',()=>own(async f=>{ f.faults[fault]=true;await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()));assert.equal(f.verificationReads(),0) })) for(const fault of ['replacement','lateMode'])test(fault+' during genuine signature verification refuses protected output',()=>own(async f=>{ f.faults[fault]=true;await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,deadline()));assert.equal(f.verificationReads(),1) })) test('expired/aborted lifetime refuses before filesystem or source access',()=>own(async f=>{ const abort=new AbortController();abort.abort() for(const options of [{deadlineAt:Date.now()-1},{deadlineAt:Date.now()+1000,signal:abort.signal}])await assert.rejects(f.api.readProtectedDatabaseSourceBindingRegistry(f.pin,options)) assert.equal(f.verificationReads(),0) })) test('source verifier drift cannot grant protected output',()=>own(async f=>{ f.faults.lateMode=true const verify=f.api.createProtectedDatabaseSourceBindingVerifier(f.pin).verify await assert.rejects(verify({} as Parameters[0],{} as Parameters[1],deadline())) assert.equal(f.verificationReads(),1) }))