deadbyte-mcp 0.15.74 → 0.15.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/MANIFEST.SHA256 +108 -42
  2. package/PROVENANCE.json +20 -22
  3. package/README.txt +23 -18
  4. package/RELEASE.json +69 -18
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +32 -2
  7. package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
  8. package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
  9. package/bin/deadbyte-desktop-uia-r199.exe +0 -0
  10. package/bin/deadbyte-desktop-uia-r199.obj +0 -0
  11. package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
  12. package/bin/deadbyte-fs-read-r198.exe +0 -0
  13. package/bin/deadbyte-fs-read-r198.obj +0 -0
  14. package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
  15. package/bridge/package.json +1 -1
  16. package/controller/deadbyte-process-policy.json +4 -4
  17. package/docs/R184-package-lock.json +2 -8
  18. package/native/deadbyte-desktop-uia-r199.cpp +345 -0
  19. package/native/deadbyte-fs-read-r197.cpp +174 -0
  20. package/native/deadbyte-fs-read-r198.cpp +181 -0
  21. package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
  22. package/package.json +5 -2
  23. package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
  24. package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
  25. package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
  26. package/scripts/build-native-fs-read-r197.ps1 +28 -0
  27. package/scripts/build-native-fs-read-r198.ps1 +28 -0
  28. package/scripts/build-native-root-probe-r197.ps1 +28 -0
  29. package/scripts/gate-windows-r195.ps1 +114 -0
  30. package/scripts/gate-windows-r206.ps1 +116 -0
  31. package/scripts/gate-windows-r207.ps1 +104 -0
  32. package/scripts/init-desktop-policy-r199.ps1 +85 -0
  33. package/scripts/npm-release-r48.mjs +27 -0
  34. package/scripts/npm-release-readiness-r195.mjs +72 -0
  35. package/scripts/npm-release-readiness-r206.mjs +76 -0
  36. package/scripts/npm-release-readiness-r207.mjs +76 -0
  37. package/scripts/r193-owner-request-live.mjs +8 -7
  38. package/scripts/r193-owner-uac-broker.mjs +6 -5
  39. package/scripts/r205-candidate-selection.mjs +22 -0
  40. package/src/candidate-selection-evidence-r206.mjs +71 -0
  41. package/src/candidate-selection-r205.mjs +96 -0
  42. package/src/desktop-postcondition-r196.mjs +30 -0
  43. package/src/desktop-runtime-r40.mjs +44 -7
  44. package/src/desktop-uia-identity-r199.mjs +51 -0
  45. package/src/desktop-uia-r40.mjs +9 -1
  46. package/src/machine-fs-handle-read-r196.mjs +43 -0
  47. package/src/machine-fs-native-read-r197.mjs +73 -0
  48. package/src/machine-fs-native-read-r198.mjs +76 -0
  49. package/src/machine-fs-read-worker.mjs +5 -25
  50. package/src/machine-fs.mjs +31 -16
  51. package/src/r195-canonical-owner-observation.mjs +15 -0
  52. package/src/r207-owner-enrollment-io.mjs +30 -0
  53. package/src/remote-destination-journal-r202.mjs +120 -0
  54. package/src/remote-device-r44.mjs +89 -14
  55. package/src/remote-payload-boundary-r201.mjs +71 -0
  56. package/src/remote-terminal-evidence-r200.mjs +60 -0
  57. package/src/version.mjs +1 -1
  58. package/test/desktop-runtime-r40.test.mjs +111 -0
  59. package/test/desktop-uia-r40.test.mjs +15 -0
  60. package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
  61. package/test/fixtures/r203-journal-writer.mjs +6 -0
  62. package/test/process-policy-refresh.test.mjs +4 -4
  63. package/test/r130-release-security-contract.test.mjs +8 -8
  64. package/test/r132-release-identity.test.mjs +7 -7
  65. package/test/r153-prepublish-identity.test.mjs +1 -1
  66. package/test/r160-release-contract.test.mjs +3 -3
  67. package/test/r165-live-cli-skew.test.mjs +1 -1
  68. package/test/r167-release-contract.test.mjs +11 -11
  69. package/test/r168-release-contract.test.mjs +4 -4
  70. package/test/r172-release-contract.test.mjs +6 -6
  71. package/test/r173-release-contract.test.mjs +16 -16
  72. package/test/r179-release-contract.test.mjs +12 -12
  73. package/test/r190-release-contract.test.mjs +10 -10
  74. package/test/r191-release-contract.test.mjs +12 -12
  75. package/test/r192-release-identity.test.mjs +9 -9
  76. package/test/r193-release-identity.test.mjs +24 -22
  77. package/test/r193-release-readiness.test.mjs +2 -2
  78. package/test/r193-windows-gate.test.mjs +10 -10
  79. package/test/r194-release-identity.test.mjs +19 -15
  80. package/test/r194-release-readiness.test.mjs +3 -3
  81. package/test/r194-windows-gate.test.mjs +9 -8
  82. package/test/r195-canonical-owner-observation.test.mjs +32 -0
  83. package/test/r195-release-readiness.test.mjs +22 -0
  84. package/test/r196-desktop-postcondition.test.mjs +53 -0
  85. package/test/r196-handle-bound-read.test.mjs +99 -0
  86. package/test/r196-remote-outcome.test.mjs +94 -0
  87. package/test/r197-namespace-race.test.mjs +41 -0
  88. package/test/r197-native-dispatch-race.test.mjs +49 -0
  89. package/test/r197-native-file-read.test.mjs +60 -0
  90. package/test/r197-native-root-open.test.mjs +61 -0
  91. package/test/r198-root-identity.test.mjs +88 -0
  92. package/test/r199-native-uia-worker.test.mjs +28 -0
  93. package/test/r199-uia-identity.test.mjs +43 -0
  94. package/test/r200-remote-reconcile.test.mjs +106 -0
  95. package/test/r201-payload-boundary.test.mjs +78 -0
  96. package/test/r201-payload-fuzz.test.mjs +37 -0
  97. package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
  98. package/test/r202-destination-journal.test.mjs +92 -0
  99. package/test/r203-atomic-journal.test.mjs +86 -0
  100. package/test/r205-candidate-selection-cli.test.mjs +31 -0
  101. package/test/r205-candidate-selection.test.mjs +83 -0
  102. package/test/r206-candidate-evidence.test.mjs +64 -0
  103. package/test/r206-release-contract.test.mjs +42 -0
  104. package/test/r206-release-readiness.test.mjs +23 -0
  105. package/test/r207-owner-enrollment-io.test.mjs +37 -0
  106. package/test/r207-release-contract.test.mjs +51 -0
  107. package/test/r207-release-readiness.test.mjs +23 -0
  108. package/test/r59-tool-surface-compat.test.mjs +1 -1
  109. package/test/r64-machine-fs-large-read.test.mjs +26 -12
@@ -0,0 +1,53 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {evaluateDesktopPostcondition,validateDesktopPostconditionExpectation} from '../src/desktop-postcondition-r196.mjs';
5
+
6
+ const H=x=>createHash('sha256').update(String(x)).digest('hex');
7
+ const frame=(id,img)=>Object.freeze({frame_id:H('frame-'+id),image_sha256:H(img)});
8
+ const before=frame(1,'initial');
9
+ const after=frame(2,'changed');
10
+
11
+ test('R196 input delivery or changed pixels cannot silently confirm application effect',()=>{
12
+ const value=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after});
13
+ assert.equal(value.status,'unknown');
14
+ assert.equal(value.reason,'no_explicit_expectation');
15
+ assert.equal(value.kind,null);
16
+ });
17
+
18
+ test('R196 explicit fresh expected image can satisfy an observable postcondition',()=>{
19
+ const expectation={kind:'frame_image_sha256',expected_sha256:after.image_sha256};
20
+ const result=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after,expectation});
21
+ assert.equal(result.status,'satisfied');
22
+ assert.equal(result.reason,'exact_post_image_observed');
23
+ assert.equal(result.expected_sha256,after.image_sha256);
24
+ assert.equal(result.after_frame_id,after.frame_id);
25
+ });
26
+
27
+ test('R196 desired postcondition absent after input is unsatisfied',()=>{
28
+ const expected=frame(3,'desired').image_sha256;
29
+ const result=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after,
30
+ expectation:{kind:'frame_image_sha256',expected_sha256:expected}});
31
+ assert.equal(result.status,'unsatisfied');
32
+ assert.equal(result.reason,'post_image_differs_from_expected');
33
+ });
34
+
35
+ test('R196 pre-existing expected state cannot prove input caused an effect',()=>{
36
+ const result=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after,
37
+ expectation:{kind:'frame_image_sha256',expected_sha256:before.image_sha256}});
38
+ assert.equal(result.status,'unknown');
39
+ assert.equal(result.reason,'expected_state_already_present_before_action');
40
+ });
41
+
42
+ test('R196 forged or unsupported postcondition must be rejected before injection',()=>{
43
+ for(const value of [
44
+ {},{kind:'file_sha256',expected_sha256:H('anything')},
45
+ {kind:'frame_image_sha256',expected_sha256:'bogus'},
46
+ {kind:'frame_image_sha256',expected_sha256:H('valid'),_internal:true}
47
+ ])assert.throws(()=>validateDesktopPostconditionExpectation(value),/unsupported/);
48
+ });
49
+
50
+ test('R196 invalid frames never produce a satisfied or unknown false evidence result',()=>{
51
+ assert.throws(()=>evaluateDesktopPostcondition({beforeFrame:{},afterFrame:after,
52
+ expectation:{kind:'frame_image_sha256',expected_sha256:after.image_sha256}}),/frames invalid/);
53
+ });
@@ -0,0 +1,99 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import {createHash} from 'node:crypto';
5
+ import {mkdtemp,mkdir,writeFile,rm,open,rename,lstat} from 'node:fs/promises';
6
+ import {tmpdir} from 'node:os';
7
+ import path from 'node:path';
8
+ import {syncBuiltinESMExports} from 'node:module';
9
+ import {spawnSync} from 'node:child_process';
10
+ import {fileURLToPath} from 'node:url';
11
+ import {loadCodingPolicy} from '../src/coding-plane.mjs';
12
+ import {createMachineFsRuntime} from '../src/machine-fs.mjs';
13
+ import {readRangeFromHandle} from '../src/machine-fs-handle-read-r196.mjs';
14
+ import {generateTrustKeypair} from '../src/trust.mjs';
15
+
16
+ const sha=b=>createHash('sha256').update(b).digest('hex');
17
+ async function fixture(t){
18
+ const root=await mkdtemp(path.join(tmpdir(),'deadbyte-r196-fd-'));
19
+ t.after(()=>rm(root,{recursive:true,force:true}));
20
+ const project=path.join(root,'project');await mkdir(project);
21
+ const policyPath=path.join(root,'policy.json');
22
+ await writeFile(policyPath,JSON.stringify({
23
+ schema:'deadbyte.coding-policy.v1',enabled:true,
24
+ lease_file:path.join(root,'grant.json'),evidence_dir:path.join(root,'evidence'),
25
+ roots:{project:{path:project,read:true,write:false,exec:false,deny_paths:[]}},
26
+ commands:{},max_read_bytes:4194304,max_write_bytes:65536
27
+ }));
28
+ const policy=await loadCodingPolicy(policyPath);
29
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
30
+ return {root,project,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
31
+ }
32
+
33
+ test('R196 read hash must not reopen path after reading different bytes',async t=>{
34
+ const f=await fixture(t),a=Buffer.from('original-fd-a'),b=Buffer.from('alternate-path-b');
35
+ await writeFile(path.join(f.project,'source.bin'),a);
36
+ await writeFile(path.join(f.project,'alternate.bin'),b);
37
+ const original=fs.createReadStream;
38
+ let calls=0;
39
+ fs.createReadStream=(_p,opts)=>{calls++;return original(path.join(f.project,'alternate.bin'),opts)};
40
+ syncBuiltinESMExports();
41
+ try{
42
+ const r=await f.runtime.read({rootId:'project',relativePath:'source.bin',offset:0,maxBytes:64,hashFile:true});
43
+ assert.deepEqual(Buffer.from(r.data_base64,'base64'),a,'range reads source bytes');
44
+ assert.equal(r.file_sha256,sha(a),'digest must belong to the same file object as returned data');
45
+ assert.equal(calls,0,'hash must not reopen a pathname through createReadStream');
46
+ }finally{
47
+ fs.createReadStream=original;syncBuiltinESMExports();
48
+ }
49
+ });
50
+
51
+ test('R196 standalone worker returns digest and bytes from source file',async t=>{
52
+ const f=await fixture(t),a=Buffer.from('worker-fd-identity\0\x01');
53
+ const p=path.join(f.project,'worker.bin');await writeFile(p,a);
54
+ const script=fileURLToPath(new URL('../src/machine-fs-read-worker.mjs',import.meta.url));
55
+ const run=spawnSync(process.execPath,[script,p,'0','32','1'],{encoding:'utf8',windowsHide:true,timeout:10000,shell:false});
56
+ assert.equal(run.status,0,run.stderr);
57
+ const v=JSON.parse(run.stdout);
58
+ assert.equal(v.schema,'deadbyte.machine-fs-read-worker.v1');
59
+ assert.deepEqual(Buffer.from(v.data_base64,'base64'),a);
60
+ assert.equal(v.file_sha256,sha(a));
61
+ });
62
+
63
+ test('R196 retained FileHandle survives same-size pathname replacement',async t=>{
64
+ const f=await fixture(t);
65
+ const name=path.join(f.project,'mutable.bin'),oldName=path.join(f.project,'old.bin'),replacement=path.join(f.project,'replacement.bin');
66
+ const a=Buffer.from('AAAAAAAA-ORIGINAL'),b=Buffer.from('BBBBBBBB-REPLACED');
67
+ assert.equal(a.length,b.length);
68
+ await writeFile(name,a);await writeFile(replacement,b);
69
+ const handle=await open(name,'r');
70
+ try{
71
+ const opened=await handle.stat({bigint:true});
72
+ await rename(name,oldName);
73
+ await rename(replacement,name);
74
+ const current=await lstat(name,{bigint:true});
75
+ assert.notEqual(opened.ino,current.ino,'test fixture did not replace the file object');
76
+ const v=await readRangeFromHandle(handle,{offset:0,length:32,hashFile:true});
77
+ assert.deepEqual(Buffer.from(v.data_base64,'base64'),a);
78
+ assert.equal(v.file_sha256,sha(a));
79
+ assert.equal(v.file_ino,String(opened.ino));
80
+ }finally{await handle.close()}
81
+ });
82
+
83
+ test('R196 in-place mutation between range and hash refuses mixed file epoch',async t=>{
84
+ const f=await fixture(t);
85
+ const file=path.join(f.project,'inplace.bin');
86
+ await writeFile(file,Buffer.from('old'));
87
+ const handle=await open(file,'r');
88
+ const proto=Object.getPrototypeOf(handle),original=proto.createReadStream;
89
+ let attempted=false;
90
+ proto.createReadStream=function(options){
91
+ attempted=true;
92
+ fs.writeFileSync(file,Buffer.from('new-bytes-with-different-size'));
93
+ return original.call(this,options);
94
+ };
95
+ try{
96
+ await assert.rejects(()=>readRangeFromHandle(handle,{offset:0,length:3,hashFile:true}),/opened file changed during read\/hash/);
97
+ assert.equal(attempted,true);
98
+ }finally{proto.createReadStream=original;await handle.close()}
99
+ });
@@ -0,0 +1,94 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,createHash} from 'node:crypto';
4
+ import {mkdtemp,rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {createRemoteDevicePlaneR44} from '../src/remote-device-r44.mjs';
8
+
9
+ const hash=b=>createHash('sha256').update(b).digest('hex');
10
+ function key(){
11
+ const {publicKey}=generateKeyPairSync('ed25519');
12
+ const der=Buffer.from(publicKey.export({type:'spki',format:'der'}));
13
+ return {id:hash(der),spki:der.toString('base64')};
14
+ }
15
+ async function fixture(t,relay,{timeoutMs=2000}={}){
16
+ const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r196-remote-'));
17
+ t.after(()=>rm(dir,{recursive:true,force:true}));
18
+ const owner=key();
19
+ const factory=()=>createRemoteDevicePlaneR44({stateDir:dir,relay,authorizeCapability:async()=>true,
20
+ relayTimeoutMs:timeoutMs,probeRetries:0,reachableTtlMs:5000});
21
+ const plane=await factory();
22
+ await plane.pair({publicKeySpkiBase64:owner.spki,displayName:'R196'});
23
+ await plane.reportOnline({deviceId:owner.id,epoch:1,connectionId:'r196-disarmed'});
24
+ await plane.probe({deviceId:owner.id,epoch:1});
25
+ return {owner,plane,factory};
26
+ }
27
+ const req=(owner,id)=>({deviceId:owner.id,epoch:1,capabilityId:'machine.fs.write',payload:{file:'safe.txt',content:'one'},requestId:id.repeat(64)});
28
+
29
+ test('R196 concurrent identical request ID has only one relay side effect',async t=>{
30
+ let release,started;
31
+ const entered=new Promise(resolve=>started=resolve);
32
+ const gate=new Promise(resolve=>release=resolve);
33
+ let effects=0;
34
+ const relay={
35
+ async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
36
+ async request(x){effects++;started();await gate;return {ack:'accepted',request_id:x.request_id,terminal:{status:'completed',result_sha256:'a'.repeat(64)}};}
37
+ };
38
+ const {owner,plane}=await fixture(t,relay);
39
+ const one=plane.dispatch(req(owner,'1'));
40
+ const two=plane.dispatch(req(owner,'1'));
41
+ await entered;
42
+ await new Promise(resolve=>setTimeout(resolve,20));
43
+ release();
44
+ const [first,replay]=await Promise.all([one,two]);
45
+ assert.equal(effects,1,'duplicate side effect for concurrent same-ID dispatch');
46
+ assert.equal(first.state,'completed');
47
+ assert.equal(first.effect_state,'confirmed');
48
+ assert.equal(first.transport_state,'terminal_received');
49
+ assert.equal(replay.replay,true);
50
+ assert.equal(replay.request_sha256,first.request_sha256);
51
+ });
52
+
53
+ test('R196 lost reply after committed remote effect is UNKNOWN not a confirmed operation failure',async t=>{
54
+ let effects=0;
55
+ const relay={
56
+ async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
57
+ async request(){effects++;return await new Promise(()=>{});}
58
+ };
59
+ const {owner,plane,factory}=await fixture(t,relay,{timeoutMs:100});
60
+ const args=req(owner,'2');
61
+ const result=await plane.dispatch(args);
62
+ assert.equal(result.state,'failed','legacy transport failure label remains for compatibility');
63
+ assert.equal(result.error_code,'transport_error');
64
+ assert.equal(result.transport_state,'timed_out');
65
+ assert.equal(result.effect_state,'unknown','transport timeout cannot prove no remote mutation');
66
+ assert.equal(effects,1);
67
+ const restarted=await factory();
68
+ const second=await restarted.dispatch(args);
69
+ assert.equal(second.replay,true);
70
+ assert.equal(second.effect_state,'unknown');
71
+ assert.equal(effects,1,'restart must never reexecute accepted unknown-outcome mutation');
72
+ });
73
+
74
+ test('R196 explicit missing ACK and terminal ACK preserve independent transport/effect facts',async t=>{
75
+ const relay={
76
+ async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
77
+ async request(){return null;}
78
+ };
79
+ const {owner,plane}=await fixture(t,relay);
80
+ const noAck=await plane.dispatch(req(owner,'3'));
81
+ assert.equal(noAck.error_code,'transport_no_ack');
82
+ assert.equal(noAck.transport_state,'ack_missing');
83
+ assert.equal(noAck.effect_state,'unknown');
84
+ relay.request=async x=>({ack:'accepted',request_id:x.request_id});
85
+ const noTerminal=await plane.dispatch(req(owner,'4'));
86
+ assert.equal(noTerminal.error_code,'transport_no_terminal');
87
+ assert.equal(noTerminal.transport_state,'acknowledged');
88
+ assert.equal(noTerminal.effect_state,'unknown');
89
+ relay.request=async x=>({ack:'accepted',request_id:x.request_id,terminal:{status:'completed',result_sha256:'b'.repeat(64)}});
90
+ const complete=await plane.dispatch(req(owner,'5'));
91
+ assert.equal(complete.transport_state,'terminal_received');
92
+ assert.equal(complete.effect_state,'confirmed');
93
+ assert.equal(complete.state,'completed');
94
+ });
@@ -0,0 +1,41 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,mkdir,writeFile,readFile,rm,lstat} from 'node:fs/promises';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import {fileURLToPath} from 'node:url';
7
+ import {loadCodingPolicy} from '../src/coding-plane.mjs';
8
+ import {createMachineFsRuntime} from '../src/machine-fs.mjs';
9
+ import {generateTrustKeypair} from '../src/trust.mjs';
10
+
11
+ test('R197 legacy injectable pathname worker demonstrates escaped open but parent rejects data',async t=>{
12
+ if(process.platform!=='win32')return t.skip('native Windows reparse fixture');
13
+ const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r197-namespace-'));
14
+ t.after(()=>rm(scratch,{recursive:true,force:true}));
15
+ const project=path.join(scratch,'project'),external=path.join(scratch,'external');
16
+ await mkdir(project);await mkdir(external);await mkdir(path.join(project,'mutable'));
17
+ await writeFile(path.join(project,'mutable','sample.bin'),'INSIDE-1234');
18
+ await writeFile(path.join(external,'sample.bin'),'OUTSIDE-123');
19
+ const policyFile=path.join(scratch,'policy.json');
20
+ await writeFile(policyFile,JSON.stringify({
21
+ schema:'deadbyte.coding-policy.v1',enabled:true,
22
+ lease_file:path.join(scratch,'grant.json'),evidence_dir:path.join(scratch,'evidence'),
23
+ roots:{project:{path:project,read:true,write:false,exec:false,deny_paths:[]}},
24
+ commands:{},max_read_bytes:65536,max_write_bytes:65536
25
+ }));
26
+ const policy=await loadCodingPolicy(policyFile);
27
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(scratch,'private.pem'),publicKeyPath:path.join(scratch,'public.pem')});
28
+ const worker=fileURLToPath(new URL('./fixtures/r197-junction-swap-worker.mjs',import.meta.url));
29
+ const runtime=createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,
30
+ verifyKeyPath:keys.publicKeyPath,readWorkerPath:worker});
31
+ let outcome;
32
+ try{outcome=await runtime.read({rootId:'project',relativePath:'mutable/sample.bin',offset:0,maxBytes:32,hashFile:true,timeoutMs:5000});}
33
+ catch(e){outcome={error:String(e?.message??e)}}
34
+ const marker=path.join(scratch,'r197-worker-entered.marker');
35
+ await lstat(marker); // Proves an actual post-precheck execution point was reached.
36
+ const escaped=path.join(scratch,'r197-escaped-open.marker');
37
+ const observed=await lstat(escaped).then(()=>true,()=>false);
38
+ // This deliberately characterizes the old injected-worker path. It is NOT a secure reader proof.
39
+ assert.equal(observed,true,'R197 diagnostic fixture must demonstrate the legacy escaped open');
40
+ assert.equal(outcome.error!==undefined,true,'raced request must fail closed');
41
+ });
@@ -0,0 +1,49 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import cp from 'node:child_process';
4
+ import fs from 'node:fs';
5
+ import {mkdtemp,mkdir,writeFile,rm,realpath} from 'node:fs/promises';
6
+ import {syncBuiltinESMExports} from 'node:module';
7
+ import {tmpdir} from 'node:os';
8
+ import path from 'node:path';
9
+ import {loadCodingPolicy} from '../src/coding-plane.mjs';
10
+ import {createMachineFsRuntime} from '../src/machine-fs.mjs';
11
+ import {generateTrustKeypair} from '../src/trust.mjs';
12
+
13
+ test('R197 default native read refuses a junction swapped after validation but before kernel open',async t=>{
14
+ if(process.platform!=='win32')return t.skip('Windows native test only');
15
+ const scratch=await mkdtemp(path.join(tmpdir(),'deadbyte-r197-native-race-'));
16
+ t.after(()=>rm(scratch,{recursive:true,force:true}));
17
+ const root=path.join(scratch,'project'),external=path.join(scratch,'external');
18
+ await mkdir(root);await mkdir(external);await mkdir(path.join(root,'mutable'));
19
+ await writeFile(path.join(root,'mutable','sample.bin'),'INSIDE-1234');
20
+ await writeFile(path.join(external,'sample.bin'),'OUTSIDE-123');
21
+ const file=path.join(scratch,'policy.json');
22
+ await writeFile(file,JSON.stringify({
23
+ schema:'deadbyte.coding-policy.v1',enabled:true,
24
+ lease_file:path.join(scratch,'grant.json'),evidence_dir:path.join(scratch,'evidence'),
25
+ roots:{project:{path:root,read:true,write:false,exec:false,deny_paths:[]}},
26
+ commands:{},max_read_bytes:65536,max_write_bytes:65536
27
+ }));
28
+ const policy=await loadCodingPolicy(file);
29
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(scratch,'private.pem'),publicKeyPath:path.join(scratch,'public.pem')});
30
+ const runtime=createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
31
+ const original=cp.spawn;
32
+ let intercepted=0;
33
+ cp.spawn=(command,args,options)=>{
34
+ if(path.basename(String(command)).toLowerCase()==='deadbyte-fs-read-r198.exe'){
35
+ intercepted++;
36
+ fs.renameSync(path.join(root,'mutable'),path.join(root,'mutable-original'));
37
+ fs.symlinkSync(external,path.join(root,'mutable'),'junction');
38
+ }
39
+ return original(command,args,options);
40
+ };
41
+ syncBuiltinESMExports();
42
+ try{
43
+ await assert.rejects(runtime.read({rootId:'project',relativePath:'mutable/sample.bin',
44
+ offset:0,maxBytes:32,hashFile:true,timeoutMs:5000}),/native root-relative read refused/);
45
+ }finally{cp.spawn=original;syncBuiltinESMExports();}
46
+ assert.equal(intercepted,1,'native operation was not reached after path precheck');
47
+ assert.equal((await realpath(path.join(root,'mutable','sample.bin'))).toLowerCase(),
48
+ path.join(external,'sample.bin').toLowerCase(),'fixture did not redirect the pathname outside root');
49
+ });
@@ -0,0 +1,60 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {readFileSync} from 'node:fs';
5
+ import {mkdtemp,mkdir,rm,writeFile,lstat,rename,symlink} from 'node:fs/promises';
6
+ import {spawnSync} from 'node:child_process';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import {fileURLToPath} from 'node:url';
10
+ const exe=fileURLToPath(new URL('../bin/deadbyte-fs-read-r197-fixed.exe',import.meta.url));
11
+ const pinned='7477ca3d94168c8b75711b734df5b9c20a43ecbd9338cfc234700d7e27982b63';
12
+ const hash=b=>createHash('sha256').update(b).digest('hex');
13
+ function native(root,rel,offset='0',length='64',whole='1'){
14
+ assert.equal(hash(readFileSync(exe)),pinned,'native executable hash drift');
15
+ return spawnSync(exe,[root,rel,offset,length,whole],{windowsHide:true,shell:false,encoding:'utf8',timeout:10000,maxBuffer:9*1024*1024});
16
+ }
17
+ async function fixture(t){
18
+ const tmp=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r197-fsread-'));
19
+ t.after(()=>rm(tmp,{force:true,recursive:true}));
20
+ const root=path.join(tmp,'root'),outside=path.join(tmp,'outside');
21
+ await mkdir(root);await mkdir(outside);await mkdir(path.join(root,'inner'));
22
+ const bytes=Buffer.from('R197-allowed-data');
23
+ await writeFile(path.join(root,'inner','read.bin'),bytes);
24
+ await writeFile(path.join(outside,'read.bin'),'OUTSIDE-private-data');
25
+ return {tmp,root,outside,bytes};
26
+ }
27
+ test('R197 native reader returns bounded bytes/hash and exact opened file identity',async t=>{
28
+ if(process.platform!=='win32')return t.skip();
29
+ const f=await fixture(t);
30
+ const r=native(f.root,'inner/read.bin');
31
+ assert.equal(r.status,0,r.stderr);
32
+ const data=JSON.parse(r.stdout),info=await lstat(path.join(f.root,'inner','read.bin'),{bigint:true});
33
+ assert.deepEqual(Buffer.from(data.data_base64,'base64'),f.bytes);
34
+ assert.equal(data.file_sha256,hash(f.bytes));
35
+ assert.equal(data.file_size,f.bytes.length);
36
+ assert.equal(data.file_dev,String(info.dev));
37
+ assert.equal(data.file_ino,String(info.ino));
38
+ });
39
+ test('R197 native refuses a replaced directory junction before reading external file',async t=>{
40
+ if(process.platform!=='win32')return t.skip();
41
+ const f=await fixture(t);
42
+ await rename(path.join(f.root,'inner'),path.join(f.root,'old'));
43
+ await symlink(f.outside,path.join(f.root,'inner'),'junction');
44
+ const r=native(f.root,'inner/read.bin');
45
+ assert.notEqual(r.status,0,'outside file was opened');
46
+ assert.match(r.stderr,/R197_NATIVE_REFUSE=/);
47
+ assert.doesNotMatch(r.stdout,/OUTSIDE/);
48
+ });
49
+ test('R197 native bounded offset and un-hashed read',async t=>{
50
+ if(process.platform!=='win32')return t.skip();
51
+ const f=await fixture(t);
52
+ const r=native(f.root,'inner/read.bin','5','4','0');
53
+ assert.equal(r.status,0,r.stderr);
54
+ const data=JSON.parse(r.stdout);
55
+ assert.equal(Buffer.from(data.data_base64,'base64').toString('utf8'),f.bytes.subarray(5,9).toString('utf8'));
56
+ assert.equal(data.file_sha256,null);
57
+ const invalid=native(f.root,'inner/read.bin','0','4194305','0');
58
+ assert.notEqual(invalid.status,0);
59
+ assert.match(invalid.stderr,/range_budget/);
60
+ });
@@ -0,0 +1,61 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {readFileSync} from 'node:fs';
5
+ import {mkdtemp,mkdir,rm,writeFile,rename,symlink} from 'node:fs/promises';
6
+ import {spawnSync} from 'node:child_process';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import {fileURLToPath} from 'node:url';
10
+ const exe=fileURLToPath(new URL('../bin/deadbyte-root-relative-probe-r197.exe',import.meta.url));
11
+ const expected='7cd989bd396a2871d322e1a9bb02fdbc299614e89d59d9b03d03e92a77038fd1';
12
+ function probe(root,rel){
13
+ const h=createHash('sha256').update(readFileSync(exe)).digest('hex');
14
+ assert.equal(h,expected,'native executable SHA-256 changed');
15
+ return spawnSync(exe,[root,rel],{windowsHide:true,shell:false,encoding:'utf8',timeout:5000,maxBuffer:100000});
16
+ }
17
+ async function fixture(t){
18
+ const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r197-native-'));
19
+ t.after(()=>rm(scratch,{recursive:true,force:true}));
20
+ const root=path.join(scratch,'root'),outside=path.join(scratch,'outside');
21
+ await mkdir(root);await mkdir(outside);
22
+ await mkdir(path.join(root,'sub'));
23
+ await writeFile(path.join(root,'sub','x.bin'),'root-data');
24
+ await writeFile(path.join(outside,'x.bin'),'secret-data');
25
+ return {scratch,root,outside};
26
+ }
27
+ test('R197 anchored native open accepts a regular in-root file',async t=>{
28
+ if(process.platform!=='win32')return t.skip();
29
+ const f=await fixture(t);
30
+ const r=probe(f.root,'sub/x.bin');
31
+ assert.equal(r.status,0,r.stderr);
32
+ assert.match(r.stdout,/R197_NATIVE_HANDLE_RELATIVE_OPEN_PASS bytes=9 components=2/);
33
+ });
34
+ test('R197 anchored native open refuses existing junction before any content read',async t=>{
35
+ if(process.platform!=='win32')return t.skip();
36
+ const f=await fixture(t);
37
+ await symlink(f.outside,path.join(f.root,'link'),'junction');
38
+ const r=probe(f.root,'link/x.bin');
39
+ assert.notEqual(r.status,0,'junction was followed');
40
+ assert.match(r.stderr,/R197_NATIVE_REFUSE=/);
41
+ });
42
+ test('R197 anchored native open refuses swapped directory junction after earlier nominal pathname',async t=>{
43
+ if(process.platform!=='win32')return t.skip();
44
+ const f=await fixture(t);
45
+ await rename(path.join(f.root,'sub'),path.join(f.root,'sub-old'));
46
+ await symlink(f.outside,path.join(f.root,'sub'),'junction');
47
+ const r=probe(f.root,'sub/x.bin');
48
+ assert.notEqual(r.status,0,'swapped junction was followed');
49
+ assert.match(r.stderr,/R197_NATIVE_REFUSE=/);
50
+ });
51
+ test('R197 anchored native open refuses traversal syntax and root reparse',async t=>{
52
+ if(process.platform!=='win32')return t.skip();
53
+ const f=await fixture(t);
54
+ const traversal=probe(f.root,'../outside/x.bin');
55
+ assert.notEqual(traversal.status,0);
56
+ assert.match(traversal.stderr,/invalid_segment/);
57
+ await symlink(f.root,path.join(f.scratch,'root-link'),'junction');
58
+ const reparseRoot=probe(path.join(f.scratch,'root-link'),'sub/x.bin');
59
+ assert.notEqual(reparseRoot.status,0);
60
+ assert.match(reparseRoot.stderr,/root_reparse_or_unverified/);
61
+ });
@@ -0,0 +1,88 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import fs from 'node:fs';
4
+ import cp from 'node:child_process';
5
+ import {createHash} from 'node:crypto';
6
+ import {mkdtemp,mkdir,rm,writeFile,lstat} from 'node:fs/promises';
7
+ import {syncBuiltinESMExports} from 'node:module';
8
+ import {tmpdir} from 'node:os';
9
+ import path from 'node:path';
10
+ import {fileURLToPath} from 'node:url';
11
+ import {loadCodingPolicy} from '../src/coding-plane.mjs';
12
+ import {createMachineFsRuntime} from '../src/machine-fs.mjs';
13
+ import {generateTrustKeypair} from '../src/trust.mjs';
14
+
15
+ const exe=fileURLToPath(new URL('../bin/deadbyte-fs-read-r198.exe',import.meta.url));
16
+ const digest=b=>createHash('sha256').update(b).digest('hex');
17
+ const pinned='4325ae4f033bba195b34d2c49b48493a1a468f3443e369a9bb887a7a129eac16';
18
+ const filename='leaf.bin';
19
+ async function fixture(t){
20
+ const scratch=await mkdtemp(path.join(tmpdir(),'deadbyte-r198-root-'));
21
+ t.after(()=>rm(scratch,{force:true,recursive:true}));
22
+ const root=path.join(scratch,'allowed'),data=Buffer.from('R198-ALLOWED-DATA');
23
+ await mkdir(root);
24
+ await writeFile(path.join(root,filename),data);
25
+ const policyPath=path.join(scratch,'policy.json');
26
+ await writeFile(policyPath,JSON.stringify({
27
+ schema:'deadbyte.coding-policy.v1',enabled:true,
28
+ lease_file:path.join(scratch,'grant.json'),evidence_dir:path.join(scratch,'evidence'),
29
+ roots:{project:{path:root,read:true,write:false,exec:false,deny_paths:[]}},
30
+ commands:{},max_read_bytes:65536,max_write_bytes:65536
31
+ }));
32
+ const policy=await loadCodingPolicy(policyPath);
33
+ const keys=await generateTrustKeypair({privateKeyPath:path.join(scratch,'private.pem'),publicKeyPath:path.join(scratch,'public.pem')});
34
+ return {scratch,root,data,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
35
+ }
36
+ function native(args){
37
+ assert.equal(digest(fs.readFileSync(exe)),pinned,'R198 native binary hash drift');
38
+ return cp.spawnSync(exe,args,{windowsHide:true,shell:false,encoding:'utf8',timeout:10000});
39
+ }
40
+ test('R198 native accepts exactly the root directory object prechecked by caller',async t=>{
41
+ if(process.platform!=='win32')return t.skip();
42
+ const f=await fixture(t),info=await lstat(f.root,{bigint:true});
43
+ const result=native([f.root,filename,'0','64','1',String(info.dev),String(info.ino)]);
44
+ assert.equal(result.status,0,result.stderr);
45
+ const response=JSON.parse(result.stdout);
46
+ assert.deepEqual(Buffer.from(response.data_base64,'base64'),f.data);
47
+ assert.equal(response.file_sha256,digest(f.data));
48
+ });
49
+ test('R198 native rejects a mismatched root identity BEFORE opening leaf',async t=>{
50
+ if(process.platform!=='win32')return t.skip();
51
+ const f=await fixture(t),info=await lstat(f.root,{bigint:true});
52
+ const result=native([f.root,filename,'0','64','1',String(info.dev),String(info.ino+1n)]);
53
+ assert.notEqual(result.status,0,'expected root identity did not gate native read');
54
+ assert.match(result.stderr,/root_identity_mismatch/);
55
+ assert.doesNotMatch(result.stdout,/data_base64|file_sha256/);
56
+ });
57
+ test('R198 runtime refuses root replacement after precheck before native open',async t=>{
58
+ if(process.platform!=='win32')return t.skip();
59
+ const f=await fixture(t);
60
+ const external=path.join(f.scratch,'external');
61
+ await mkdir(external);
62
+ // Same file length, different bytes and object ID. Not a junction: this tests root identity.
63
+ await writeFile(path.join(external,filename),Buffer.from('R198-ESCAPED-DATA'));
64
+ assert.equal(fs.statSync(path.join(external,filename)).size,f.data.length);
65
+ const original=cp.spawn;
66
+ let swaps=0;
67
+ cp.spawn=(command,args,opts)=>{
68
+ if(path.basename(String(command)).toLowerCase()==='deadbyte-fs-read-r198.exe'){
69
+ swaps++;
70
+ fs.renameSync(f.root,path.join(f.scratch,'original-root'));
71
+ fs.renameSync(external,f.root);
72
+ }
73
+ return original(command,args,opts);
74
+ };
75
+ syncBuiltinESMExports();
76
+ try{
77
+ await assert.rejects(f.runtime.read({rootId:'project',relativePath:filename,offset:0,
78
+ maxBytes:64,hashFile:true,timeoutMs:5000}),/root_identity_mismatch/);
79
+ }finally{cp.spawn=original;syncBuiltinESMExports();}
80
+ assert.equal(swaps,1,'root was not actually exchanged after precheck');
81
+ });
82
+ test('R198 ordinary runtime read remains operational with root pinned',async t=>{
83
+ if(process.platform!=='win32')return t.skip();
84
+ const f=await fixture(t);
85
+ const result=await f.runtime.read({rootId:'project',relativePath:filename,maxBytes:64,hashFile:true});
86
+ assert.deepEqual(Buffer.from(result.data_base64,'base64'),f.data);
87
+ assert.equal(result.file_sha256,digest(f.data));
88
+ });
@@ -0,0 +1,28 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {spawnSync} from 'node:child_process';
4
+ import {createHash} from 'node:crypto';
5
+ import {readFileSync} from 'node:fs';
6
+ import {fileURLToPath} from 'node:url';
7
+ const exe=fileURLToPath(new URL('../bin/deadbyte-desktop-uia-r199-fixed.exe',import.meta.url));
8
+ const pinned='583f5a2cdddb8640586d13c0066de4f872af543b467078b2e75a466092421972';
9
+ const sha=v=>createHash('sha256').update(v).digest('hex');
10
+ function run(args,options={}){
11
+ assert.equal(sha(readFileSync(exe)),pinned,'R199 native UIA executable changed');
12
+ return spawnSync(exe,args,{windowsHide:true,shell:false,encoding:'utf8',timeout:10000,maxBuffer:500000,...options});
13
+ }
14
+ test('R199 native UIA worker pinned binary completes bounded selftest',t=>{
15
+ if(process.platform!=='win32')return t.skip('Windows only');
16
+ const r=run(['--selftest']);
17
+ assert.equal(r.status,0,r.stderr);
18
+ assert.deepEqual(JSON.parse(r.stdout),{schema:'deadbyte.desktop-uia-selftest.v1',status:'passed'});
19
+ });
20
+ test('R199 native UIA worker refuses malformed protocol before COM query',t=>{
21
+ if(process.platform!=='win32')return t.skip('Windows only');
22
+ for(const bad of ['', 'BAD_MAGIC\n', 'DEADBYTE_DESKTOP_UIA_V1\nexpected_hwnd=0x1\n']){
23
+ const r=run([],{input:bad});
24
+ assert.notEqual(r.status,0);
25
+ assert.match(r.stderr,/deadbyte-desktop-uia:/);
26
+ assert.equal(r.stdout,'');
27
+ }
28
+ });
@@ -0,0 +1,43 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash} from 'node:crypto';
4
+ import {bindUiaElementR199,assertFreshUiaElementR199,normalizeUiaRuntimeIdR199} from '../src/desktop-uia-identity-r199.mjs';
5
+
6
+ const H=x=>createHash('sha256').update(x).digest('hex');
7
+ const frame={frame_id:H('frame'),desktop_epoch:H('epoch'),active_window:{hwnd:'0x1234',pid:42}};
8
+ const q={kind:'name_exact',value:'SECRET SAVE',max_results:8,ordinal:0};
9
+ const rect={left:20,top:30,width:90,height:40};
10
+ function ground(id=[1,-20,3],overrides={}){
11
+ return {status:'ok',query_kind:q.kind,query_sha256:H('query'),ordinal:0,truncated:false,visited:5,
12
+ matches:[{runtime_id:id,control_type:50000,rect,enabled:true,offscreen:false}],...overrides};
13
+ }
14
+ const binding=()=>bindUiaElementR199({frame,grounded:ground(),candidate:ground().matches[0],query:q});
15
+
16
+ test('R199 identity binding is ephemeral, bounded and has content-addressed token',()=>{
17
+ const b=binding();assert.equal(b.schema,'deadbyte.uia-element-binding-r199.v1');
18
+ assert.match(b.token_sha256,/^[0-9a-f]{64}$/);
19
+ assert.deepEqual(b.runtime_id,[1,-20,3]);
20
+ assert.ok(Object.isFrozen(b.runtime_id));assert.ok(Object.isFrozen(b.query));
21
+ assert.equal(JSON.stringify({token:b.token_sha256}).includes('SECRET'),false);
22
+ });
23
+ test('R199 exact native RuntimeId permits read-only revalidation',()=>{
24
+ const proof=assertFreshUiaElementR199(binding(),ground());
25
+ assert.equal(proof.verified,true);assert.match(proof.runtime_id_sha256,/^[0-9a-f]{64}$/);
26
+ });
27
+ test('R199 recreated element with identical rectangle is refused',()=>{
28
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground([1,-20,4])),/element identity changed/);
29
+ });
30
+ test('R199 runtime identity refuses query drift and incomplete scan',()=>{
31
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{query_sha256:H('other')})),/query identity/);
32
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{truncated:true})),/scan invalid or incomplete/);
33
+ });
34
+ test('R199 state, disappearance and rectangle changes must refuse mutation',()=>{
35
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{matches:[]})),/stale target/);
36
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{matches:[{...ground().matches[0],enabled:false}]})),/control state changed/);
37
+ assert.throws(()=>assertFreshUiaElementR199(binding(),ground(undefined,{matches:[{...ground().matches[0],rect:{...rect,left:21}}]})),/rectangle changed/);
38
+ });
39
+ test('R199 runtime identity refuses forged or oversized numeric arrays',()=>{
40
+ for(const id of [[],[1.5],[2147483648],Array(65).fill(1),['1'],null]){
41
+ assert.throws(()=>normalizeUiaRuntimeIdR199(id),/RuntimeId/);
42
+ }
43
+ });