deadbyte-mcp 0.11.2 → 0.12.0
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.
- package/MANIFEST.SHA256 +101 -39
- package/README.txt +10 -9
- package/acceptance/r40/desktop-live-drift.mjs +106 -0
- package/acceptance/r40/desktop-live-ground.mjs +110 -0
- package/acceptance/r40/desktop-live-input.mjs +128 -0
- package/acceptance/r40/desktop-live-observe.mjs +100 -0
- package/acceptance/r40/desktop-live-uia.mjs +121 -0
- package/bin/deadbyte-desktop-input-r40.exe +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.exe +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.exe +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/controller/README.TXT +7 -5
- package/controller/deadbyte-controller.config.psd1.example +3 -0
- package/controller/deadbyte-controller.ps1 +149 -4
- package/controller/deadbyte-desktop-policy.json +41 -0
- package/docs/ARCHITECTURE.md +6 -1
- package/docs/superpowers/specs/2026-09-19-r40-desktop-computer-use-design.md +214 -0
- package/native/deadbyte-desktop-input.cpp +455 -0
- package/native/deadbyte-desktop-observer.cpp +253 -0
- package/native/deadbyte-desktop-uia.cpp +307 -0
- package/package.json +10 -8
- package/scripts/build-desktop-r40.ps1 +106 -0
- package/scripts/deferred-slot-operation-r39.mjs +124 -0
- package/scripts/deferred-slot-operation-r40.mjs +126 -0
- package/scripts/final-closure-r39.mjs +113 -0
- package/scripts/final-closure-r40.mjs +114 -0
- package/scripts/final-closure-verify-r39.mjs +208 -0
- package/scripts/final-closure-verify-r40.mjs +212 -0
- package/scripts/gate-windows-r39.ps1 +42 -0
- package/scripts/gate-windows-r40.ps1 +43 -0
- package/scripts/init-desktop-policy-r40.ps1 +79 -0
- package/scripts/r40-compact-ledger-probe.mjs +4 -0
- package/scripts/release-parity-r39.mjs +200 -0
- package/scripts/release-parity-r40.mjs +212 -0
- package/scripts/release-parity-tests-r39.ps1 +9 -0
- package/scripts/release-parity-tests-r40.ps1 +9 -0
- package/scripts/windows-gate-evidence-r39.mjs +52 -0
- package/scripts/windows-gate-evidence-r40.mjs +52 -0
- package/src/autonomous-context-engine.mjs +2 -1
- package/src/autonomous-planner-contracts.mjs +19 -1
- package/src/autonomous-planner.mjs +22 -0
- package/src/autonomous-runtime.mjs +144 -24
- package/src/capability-ledger.mjs +6 -1
- package/src/compact-gateway-mcp-tools.mjs +14 -2
- package/src/deadbyte-cli.mjs +79 -47
- package/src/desktop-contract.mjs +263 -0
- package/src/desktop-input-r40.mjs +148 -0
- package/src/desktop-mcp-tools-r40.mjs +126 -0
- package/src/desktop-memory-r40.mjs +272 -0
- package/src/desktop-observer-r40.mjs +231 -0
- package/src/desktop-policy-r40.mjs +110 -0
- package/src/desktop-runtime-r40.mjs +318 -0
- package/src/desktop-uia-r40.mjs +123 -0
- package/src/mcp-server.mjs +74 -19
- package/src/r40-compact-disarmed-probe.mjs +71 -0
- package/src/r40-full-surface-probe.mjs +34 -0
- package/src/release-generation.mjs +38 -5
- package/src/remote-console.mjs +41 -0
- package/src/remote-live-log.mjs +215 -45
- package/src/remote-mcp-desktop-r40-smoke-client.mjs +70 -0
- package/src/remote-mcp-r40-parity-client.mjs +200 -0
- package/src/remote-mcp-smoke-client.mjs +20 -6
- package/src/remote-result-journal.mjs +10 -2
- package/src/runtime-update.mjs +55 -17
- package/src/tool-audit.mjs +29 -2
- package/src/version.mjs +1 -1
- package/test/autonomous-capability-r36.test.mjs +43 -1
- package/test/autonomous-desktop-r40.test.mjs +188 -0
- package/test/capability-ledger-r36.test.mjs +1 -0
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/compact-catalog-r36.test.mjs +2 -2
- package/test/compact-gateway-r36.test.mjs +56 -4
- package/test/compact-max-catalog-r40.test.mjs +95 -0
- package/test/controller-desktop-r40.test.mjs +85 -0
- package/test/core.test.mjs +1 -1
- package/test/desktop-adversarial-r40.test.mjs +62 -0
- package/test/desktop-contract-r40.test.mjs +200 -0
- package/test/desktop-input-r40.test.mjs +63 -0
- package/test/desktop-mcp-tools-r40.test.mjs +61 -0
- package/test/desktop-memory-r40.test.mjs +141 -0
- package/test/desktop-observer-r40.test.mjs +119 -0
- package/test/desktop-policy-r40.test.mjs +111 -0
- package/test/desktop-runtime-r40.test.mjs +278 -0
- package/test/desktop-uia-r40.test.mjs +42 -0
- package/test/full-max-catalog-r40.test.mjs +91 -0
- package/test/helpers/autonomous-r34-fixture.mjs +3 -3
- package/test/production-docs.test.mjs +3 -3
- package/test/r33-closeout-regression.test.mjs +8 -8
- package/test/r33-finalization.test.mjs +14 -14
- package/test/r37-remote-tool-call-timeline.test.mjs +7 -6
- package/test/r39-finalization.test.mjs +74 -0
- package/test/r40-finalization.test.mjs +118 -0
- package/test/release-generation.test.mjs +41 -9
- package/test/release-version.test.mjs +19 -58
- package/test/remote-console-r40.test.mjs +38 -0
- package/test/remote-live-log-r37.test.mjs +89 -32
- package/test/remote-r24-catalog.test.mjs +12 -3
- package/test/remote-result-journal.test.mjs +15 -0
- package/test/remote-session-cli.test.mjs +112 -55
- package/test/runtime-self-update.test.mjs +44 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {
|
|
4
|
+
desktopActionInputSchema,desktopControlInputSchema,desktopTargetInputSchema,registerDesktopTools
|
|
5
|
+
} from '../src/desktop-mcp-tools-r40.mjs';
|
|
6
|
+
|
|
7
|
+
const H=c=>c.repeat(64);
|
|
8
|
+
const frame=H('a');
|
|
9
|
+
const intent=H('b');
|
|
10
|
+
|
|
11
|
+
function fakeServer(){
|
|
12
|
+
const tools=new Map();
|
|
13
|
+
return {tools,registerTool(name,definition,handler){tools.set(name,{definition,handler});}};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('R40 desktop action schema rejects ambiguous or authority-bypassing shapes',()=>{
|
|
17
|
+
assert.equal(desktopActionInputSchema.safeParse({
|
|
18
|
+
intent_id:intent,kind:'click_primary',expected_frame_id:frame,target_id:H('c'),point:{x:1,y:2}
|
|
19
|
+
}).success,false);
|
|
20
|
+
assert.equal(desktopActionInputSchema.safeParse({
|
|
21
|
+
intent_id:intent,kind:'text_input',expected_frame_id:frame,text:'hello',point:{x:1,y:2}
|
|
22
|
+
}).success,false);
|
|
23
|
+
assert.equal(desktopControlInputSchema.safeParse({op:'action',intent_id:intent,kind:'key_chord',expected_frame_id:frame,key_chord:['CTRL','CTRL']}).success,false);
|
|
24
|
+
assert.equal(desktopControlInputSchema.safeParse({op:'action',intent_id:intent,kind:'click_primary',expected_frame_id:frame,point:{x:1,y:2}}).success,true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('R40 T5 target schema supports local UIA query without arbitrary dispatch',()=>{
|
|
28
|
+
assert.equal(desktopTargetInputSchema.safeParse({frame_id:frame,uia_query:{kind:'name_exact',value:'Save',max_results:8,ordinal:0}}).success,true);
|
|
29
|
+
assert.equal(desktopTargetInputSchema.safeParse({frame_id:frame,uia_query:{kind:'root',value:'leak'}}).success,false);
|
|
30
|
+
assert.equal(desktopControlInputSchema.safeParse({op:'target',frame_id:frame,uia_query:{kind:'control_type',value:'button',max_results:4,ordinal:0}}).success,true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('R40 full desktop surface registers four narrow typed tools',()=>{
|
|
34
|
+
const server=fakeServer();
|
|
35
|
+
const runtime={status(){},observe(){},target(){},action(){}};
|
|
36
|
+
registerDesktopTools(server,{runtime});
|
|
37
|
+
assert.deepEqual([...server.tools.keys()].sort(),['desktop_action','desktop_observe','desktop_status','desktop_target']);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('R40 public desktop action output removes raw text while preserving content and action hashes',async()=>{
|
|
41
|
+
const server=fakeServer();
|
|
42
|
+
const action={
|
|
43
|
+
schema:'deadbyte.desktop-action.v1',intent_id:intent,kind:'text_input',frame_id:frame,
|
|
44
|
+
desktop_epoch:H('d'),topology_sha256:H('e'),expected_window:null,target_id:null,point:null,
|
|
45
|
+
scroll_delta:null,key_chord:null,text:'DO_NOT_ECHO_ME',text_sha256:H('f'),action_sha256:H('1')
|
|
46
|
+
};
|
|
47
|
+
const runtime={
|
|
48
|
+
action:async()=>({status:'ok',injected:true,task_success:null,action,guard:{guard_sha256:H('2')},
|
|
49
|
+
post_observation:{change_observed:true},started_receipt_id:'1'.repeat(24),started_receipt_sha256:H('3'),
|
|
50
|
+
receipt_id:'2'.repeat(24),receipt_sha256:H('4')})
|
|
51
|
+
};
|
|
52
|
+
registerDesktopTools(server,{runtime});
|
|
53
|
+
const result=await server.tools.get('desktop_action').handler({
|
|
54
|
+
intent_id:intent,kind:'text_input',expected_frame_id:frame,target_id:null,point:null,scroll_delta:null,key_chord:null,text:'DO_NOT_ECHO_ME'
|
|
55
|
+
});
|
|
56
|
+
const encoded=JSON.stringify(result.structuredContent);
|
|
57
|
+
assert.equal(encoded.includes('DO_NOT_ECHO_ME'),false);
|
|
58
|
+
assert.equal(result.structuredContent.result.action,undefined);
|
|
59
|
+
assert.equal(result.structuredContent.result.action_summary.text_sha256,H('f'));
|
|
60
|
+
assert.equal(result.structuredContent.result.action_summary.action_sha256,H('1'));
|
|
61
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash, generateKeyPairSync, sign as cryptoSign } from 'node:crypto';
|
|
4
|
+
import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { canonicalJson } from '../src/canonical-json.mjs';
|
|
8
|
+
import {
|
|
9
|
+
deriveDesktopMemory,signDesktopMemory,verifyDesktopMemorySnapshot,writeDesktopMemorySnapshot,
|
|
10
|
+
loadDesktopEvidenceReceipts,createDesktopMemoryProvider
|
|
11
|
+
} from '../src/desktop-memory-r40.mjs';
|
|
12
|
+
|
|
13
|
+
const H=v=>createHash('sha256').update(String(v)).digest('hex');
|
|
14
|
+
const DOMAIN=Buffer.from('DEADBYTE-DESKTOP-RECEIPT-V1\0','utf8');
|
|
15
|
+
const policy=H('policy');
|
|
16
|
+
const {privateKey,publicKey}=generateKeyPairSync('ed25519');
|
|
17
|
+
const keyId='memory-test-key';
|
|
18
|
+
let seq=0;
|
|
19
|
+
function receipt(kind,payload,at='2026-09-19T03:00:00.000Z',policySha256=policy){
|
|
20
|
+
const receipt_id=(++seq).toString(16).padStart(24,'0');
|
|
21
|
+
const body={schema:'deadbyte.desktop-receipt.v1',receipt_id,kind,policy_sha256:policySha256,observed_at_utc:at,payload};
|
|
22
|
+
const receipt_sha256=createHash('sha256').update(Buffer.from(canonicalJson(body),'utf8')).digest('hex');
|
|
23
|
+
const signed={...body,receipt_sha256};
|
|
24
|
+
const signature_base64=cryptoSign(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),privateKey).toString('base64');
|
|
25
|
+
return {...signed,attestation:{algorithm:'ed25519',key_id:keyId,signature_base64}};
|
|
26
|
+
}
|
|
27
|
+
const win={hwnd:'0x1234',pid:55,process_image_sha256:H('app'),title_sha256:H('title')};
|
|
28
|
+
|
|
29
|
+
test('T6 memory derives only authenticated compact Desktop facts and never carries raw payload or authority nonce',()=>{
|
|
30
|
+
const effect=H('effect'),fingerprint=H('fingerprint'),target=H('target');
|
|
31
|
+
const receipts=[
|
|
32
|
+
receipt('desktop_observation',{frame_id:H('f1'),desktop_epoch:H('epoch'),topology_sha256:H('topo'),active_window:win,
|
|
33
|
+
preview:{sha256:H('preview'),byte_length:100,width:10,height:10,encoding:'ppm-base64'}},'2026-09-19T03:00:00.000Z'),
|
|
34
|
+
receipt('desktop_grounding',{requested_frame_id:H('f1'),grounded_frame_id:H('f2'),target_id:target,
|
|
35
|
+
query_sha256:H('query'),query_kind:'name_exact',control_type:50000},'2026-09-19T03:00:01.000Z'),
|
|
36
|
+
receipt('desktop_action_started',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('action'),
|
|
37
|
+
target_id:target,kind:'click_primary',pre_frame_id:H('f2'),authority_instance_nonce:H('SECRET-NONCE')},'2026-09-19T03:00:02.000Z'),
|
|
38
|
+
receipt('desktop_post_observation',{frame_id:H('f3'),desktop_epoch:H('epoch'),topology_sha256:H('topo'),active_window:win},'2026-09-19T03:00:03.000Z'),
|
|
39
|
+
receipt('desktop_action_completed',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('action'),
|
|
40
|
+
effect:{side_effect_state:'confirmed_injected'},action_chain:{chain_sha256:H('chain')},
|
|
41
|
+
post_observation:{post_observation_sha256:H('post'),foreground_transition:{changed:false}}},'2026-09-19T03:00:04.000Z')
|
|
42
|
+
];
|
|
43
|
+
const memory=deriveDesktopMemory(receipts,{policySha256:policy,publicKey,keyId});
|
|
44
|
+
assert.equal(memory.schema,'deadbyte.desktop-memory.v1');
|
|
45
|
+
assert.equal(memory.recent_effects.length,1);
|
|
46
|
+
assert.equal(memory.recent_effects[0].effect_id,effect);
|
|
47
|
+
assert.equal(memory.grounded_targets[0].target_id,target);
|
|
48
|
+
assert.equal(memory.windows.length,1);
|
|
49
|
+
assert.equal(memory.last_verified_desktop.frame_id,H('f3'));
|
|
50
|
+
const encoded=JSON.stringify(memory);
|
|
51
|
+
assert.equal(encoded.includes('SECRET-NONCE'),false);
|
|
52
|
+
assert.equal(encoded.includes('data_base64'),false);
|
|
53
|
+
assert.equal(encoded.includes('"query":"'),false);
|
|
54
|
+
assert.equal(encoded.includes('authority_instance_nonce'),false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('T6 memory keeps unknown effects unresolved until authenticated completion appears',()=>{
|
|
58
|
+
const effect=H('unknown-effect'),fingerprint=H('unknown-fp');
|
|
59
|
+
const started=receipt('desktop_action_started',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('a'),
|
|
60
|
+
target_id:H('t'),kind:'click_primary',pre_frame_id:H('f')});
|
|
61
|
+
const unknown=receipt('desktop_action_outcome_unknown',{effect_id:effect,effect_fingerprint:fingerprint,action_sha256:H('a'),side_effect_state:'unknown'});
|
|
62
|
+
const memory=deriveDesktopMemory([started,unknown],{policySha256:policy,publicKey,keyId});
|
|
63
|
+
assert.deepEqual(memory.unresolved_unknown_effects.map(x=>x.effect_id),[effect]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('T6 rejects receipt substitution before memory trust',()=>{
|
|
67
|
+
const r=receipt('desktop_observation',{frame_id:H('f'),desktop_epoch:H('e'),topology_sha256:H('t'),active_window:win});
|
|
68
|
+
const forged=structuredClone(r);forged.payload.frame_id=H('forged');
|
|
69
|
+
assert.throws(()=>deriveDesktopMemory([forged],{policySha256:policy,publicKey,keyId}),/unauthenticated receipt/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('T6 signed snapshot persists atomically and verifies independently',async()=>{
|
|
73
|
+
const r=receipt('desktop_observation',{frame_id:H('f'),desktop_epoch:H('e'),topology_sha256:H('t'),active_window:win});
|
|
74
|
+
const memory=deriveDesktopMemory([r],{policySha256:policy,publicKey,keyId});
|
|
75
|
+
const snapshot=signDesktopMemory(memory,{privateKey,keyId});
|
|
76
|
+
const proof=verifyDesktopMemorySnapshot(snapshot,{publicKey,keyId,policySha256:policy});
|
|
77
|
+
assert.equal(proof.ok,true);
|
|
78
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-'));
|
|
79
|
+
const file=path.join(root,'desktop-memory.json');
|
|
80
|
+
const written=await writeDesktopMemorySnapshot(file,snapshot);
|
|
81
|
+
assert.equal(written.snapshot_sha256,snapshot.snapshot_sha256);
|
|
82
|
+
const disk=JSON.parse(await readFile(file,'utf8'));
|
|
83
|
+
assert.equal(verifyDesktopMemorySnapshot(disk,{publicKey,keyId,policySha256:policy}).ok,true);
|
|
84
|
+
const tampered=structuredClone(disk);tampered.memory.last_verified_desktop.frame_id=H('tamper');
|
|
85
|
+
assert.equal(verifyDesktopMemorySnapshot(tampered,{publicKey,keyId,policySha256:policy}).ok,false);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
test('T6 evidence provider loads bounded signed receipts and emits compact planner projection',async()=>{
|
|
90
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-provider-'));
|
|
91
|
+
const evidence=path.join(root,'evidence');await mkdir(evidence);
|
|
92
|
+
const r1=receipt('desktop_observation',{frame_id:H('pf'),desktop_epoch:H('pe'),topology_sha256:H('pt'),active_window:win});
|
|
93
|
+
const r2=receipt('desktop_grounding',{requested_frame_id:H('pf'),grounded_frame_id:H('pg'),target_id:H('target'),
|
|
94
|
+
query_sha256:H('query'),query_kind:'root',control_type:50032});
|
|
95
|
+
for(const r of [r1,r2])await writeFile(path.join(evidence,r.receipt_id+'.json'),canonicalJson(r)+'\n');
|
|
96
|
+
await writeFile(path.join(evidence,'desktop-memory.json'),'{"ignored":true}\n');
|
|
97
|
+
const loaded=await loadDesktopEvidenceReceipts(evidence,{maxReceipts:4,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024});
|
|
98
|
+
assert.equal(loaded.length,2);
|
|
99
|
+
const provider=createDesktopMemoryProvider({
|
|
100
|
+
evidenceDir:evidence,snapshotPath:path.join(evidence,'desktop-memory.json'),policySha256:policy,
|
|
101
|
+
privateKey,publicKey,keyId,maxReceipts:4,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024
|
|
102
|
+
});
|
|
103
|
+
const refreshed=await provider.refresh();
|
|
104
|
+
assert.equal(refreshed.status,'ok');
|
|
105
|
+
assert.equal(refreshed.source_receipt_count,2);
|
|
106
|
+
assert.equal(refreshed.planner_context.schema,'deadbyte.desktop-memory-planner.v1');
|
|
107
|
+
assert.equal(refreshed.planner_context.source_receipt_count,2);
|
|
108
|
+
assert.equal(refreshed.planner_context.grounded_targets.length,1);
|
|
109
|
+
const encoded=JSON.stringify(refreshed.planner_context);
|
|
110
|
+
assert.equal(encoded.includes('data_base64'),false);
|
|
111
|
+
assert.equal(encoded.includes('authority_instance_nonce'),false);
|
|
112
|
+
const disk=JSON.parse(await readFile(path.join(evidence,'desktop-memory.json'),'utf8'));
|
|
113
|
+
assert.equal(verifyDesktopMemorySnapshot(disk,{publicKey,keyId,policySha256:policy}).ok,true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('T6 evidence loader fails closed instead of silently truncating receipt history',async()=>{
|
|
117
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-limit-'));
|
|
118
|
+
for(let i=0;i<3;i++){
|
|
119
|
+
const r=receipt('desktop_observation',{frame_id:H('limit-'+i),desktop_epoch:H('e'),topology_sha256:H('t'),active_window:win});
|
|
120
|
+
await writeFile(path.join(root,r.receipt_id+'.json'),canonicalJson(r)+'\n');
|
|
121
|
+
}
|
|
122
|
+
await assert.rejects(loadDesktopEvidenceReceipts(root,{maxReceipts:2,maxDirectoryEntries:8}),/receipt limit exceeded/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
test('T6 current-policy memory rebuild ignores authenticated historical receipts from superseded Desktop policy',async()=>{
|
|
127
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-desktop-memory-policy-rollover-'));
|
|
128
|
+
const stalePolicy=H('stale-policy');
|
|
129
|
+
const stale=receipt('desktop_observation',{frame_id:H('stale-f'),desktop_epoch:H('stale-e'),topology_sha256:H('stale-t'),active_window:win},'2026-09-19T02:00:00.000Z',stalePolicy);
|
|
130
|
+
const current1=receipt('desktop_observation',{frame_id:H('current-f1'),desktop_epoch:H('current-e'),topology_sha256:H('current-t'),active_window:win});
|
|
131
|
+
const current2=receipt('desktop_grounding',{requested_frame_id:H('current-f1'),grounded_frame_id:H('current-f2'),target_id:H('current-target'),query_sha256:H('current-query'),query_kind:'root',control_type:50032});
|
|
132
|
+
for(const item of [stale,current1,current2]) await writeFile(path.join(root,item.receipt_id+'.json'),canonicalJson(item)+'\n');
|
|
133
|
+
const filtered=await loadDesktopEvidenceReceipts(root,{policySha256:policy,maxReceipts:2,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024});
|
|
134
|
+
assert.equal(filtered.length,2);
|
|
135
|
+
assert.ok(filtered.every(item=>item.policy_sha256===policy));
|
|
136
|
+
const provider=createDesktopMemoryProvider({evidenceDir:root,snapshotPath:path.join(root,'desktop-memory.json'),policySha256:policy,privateKey,publicKey,keyId,maxReceipts:2,maxDirectoryEntries:8,maxReceiptBytes:64*1024,maxTotalBytes:128*1024});
|
|
137
|
+
const refreshed=await provider.refresh();
|
|
138
|
+
assert.equal(refreshed.source_receipt_count,2);
|
|
139
|
+
assert.equal(refreshed.planner_context.source_receipt_count,2);
|
|
140
|
+
assert.equal(refreshed.planner_context.policy_sha256,policy);
|
|
141
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { parseNativeDesktopFrame,createDesktopObserverRuntime,createDesktopPreview } from '../src/desktop-observer-r40.mjs';
|
|
5
|
+
|
|
6
|
+
const H=value=>createHash('sha256').update(value).digest('hex');
|
|
7
|
+
|
|
8
|
+
function packet(overrides={},pixelByte=0x23){
|
|
9
|
+
const header={
|
|
10
|
+
schema:'deadbyte.desktop-native-frame.v1',
|
|
11
|
+
backend:'gdi-bitblt-fallback',
|
|
12
|
+
pixel_format:'BGRA8',
|
|
13
|
+
left:-4,top:0,width:8,height:2,stride:32,byte_length:64,
|
|
14
|
+
system_dpi:96,session_id:1,input_desktop:'Default',
|
|
15
|
+
foreground_hwnd:'0x1234',foreground_pid:444,
|
|
16
|
+
foreground_title:'Editor',foreground_image:'C:\\Apps\\editor.exe',
|
|
17
|
+
monitors:[
|
|
18
|
+
{left:-4,top:0,width:4,height:2,primary:false,device:'DISPLAY2'},
|
|
19
|
+
{left:0,top:0,width:4,height:2,primary:true,device:'DISPLAY1'}
|
|
20
|
+
],
|
|
21
|
+
...overrides
|
|
22
|
+
};
|
|
23
|
+
const pixels=Buffer.alloc(header.byte_length,pixelByte);
|
|
24
|
+
return Buffer.concat([Buffer.from(JSON.stringify(header)+'\n','utf8'),pixels]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('native parser binds pixels, topology and foreground identity',()=>{
|
|
28
|
+
const parsed=parseNativeDesktopFrame(packet());
|
|
29
|
+
assert.equal(parsed.image_sha256,H(Buffer.alloc(64,0x23)));
|
|
30
|
+
assert.match(parsed.topology_sha256,/^[0-9a-f]{64}$/);
|
|
31
|
+
assert.deepEqual(parsed.bounds,{left:-4,top:0,width:8,height:2});
|
|
32
|
+
assert.equal(parsed.active_window.hwnd,'0x1234');
|
|
33
|
+
assert.equal(parsed.active_window.pid,444);
|
|
34
|
+
assert.equal(parsed.active_window.title_sha256,H('Editor'));
|
|
35
|
+
assert.equal(parsed.active_window.process_image_sha256,H('C:\\Apps\\editor.exe'));
|
|
36
|
+
assert.equal(parsed.pixels.length,64);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('native parser rejects payload length substitution',()=>{
|
|
40
|
+
const value=packet();
|
|
41
|
+
assert.throws(()=>parseNativeDesktopFrame(value.subarray(0,value.length-1)),/payload length mismatch/);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('native parser rejects stride and byte length lies',()=>{
|
|
45
|
+
assert.throws(()=>parseNativeDesktopFrame(packet({stride:64,byte_length:128})),/stride mismatch/);
|
|
46
|
+
assert.throws(()=>parseNativeDesktopFrame(packet({byte_length:68})),/byte_length mismatch/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('native parser rejects monitor topology outside virtual desktop',()=>{
|
|
50
|
+
assert.throws(()=>parseNativeDesktopFrame(packet({monitors:[
|
|
51
|
+
{left:0,top:0,width:4,height:2,primary:true,device:'DISPLAY1'},
|
|
52
|
+
{left:10,top:0,width:4,height:2,primary:false,device:'BAD'}
|
|
53
|
+
]})),/outside virtual desktop/);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('native parser requires exactly one primary monitor',()=>{
|
|
57
|
+
assert.throws(()=>parseNativeDesktopFrame(packet({monitors:[
|
|
58
|
+
{left:-4,top:0,width:4,height:2,primary:false,device:'DISPLAY2'},
|
|
59
|
+
{left:0,top:0,width:4,height:2,primary:false,device:'DISPLAY1'}
|
|
60
|
+
]})),/exactly one primary/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('observer runtime creates stable epoch family and distinct frame identities',async()=>{
|
|
64
|
+
let index=0;
|
|
65
|
+
const provider=async()=>parseNativeDesktopFrame(packet({},++index));
|
|
66
|
+
const runtime=createDesktopObserverRuntime({
|
|
67
|
+
captureProvider:provider,expectedObserverSha256:H('observer'),runtimeNonce:'r40-test-runtime',
|
|
68
|
+
now:()=>index===1?'2026-09-19T02:10:00.000Z':'2026-09-19T02:10:00.500Z'
|
|
69
|
+
});
|
|
70
|
+
const a=await runtime.capture();
|
|
71
|
+
const b=await runtime.capture();
|
|
72
|
+
assert.equal(a.frame.desktop_epoch,b.frame.desktop_epoch);
|
|
73
|
+
assert.notEqual(a.frame.frame_id,b.frame.frame_id);
|
|
74
|
+
assert.notEqual(a.frame.image_sha256,b.frame.image_sha256);
|
|
75
|
+
assert.equal(runtime.frameStatus(b.frame.frame_id).generation,2);
|
|
76
|
+
assert.equal(runtime.framePixels(b.frame.frame_id).length,64);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('observer runtime evicts old raw frames by bounded cache',async()=>{
|
|
80
|
+
let index=0;
|
|
81
|
+
const runtime=createDesktopObserverRuntime({
|
|
82
|
+
captureProvider:async()=>parseNativeDesktopFrame(packet({},++index)),
|
|
83
|
+
expectedObserverSha256:H('observer'),runtimeNonce:'r40-cache',maxCachedFrames:2,
|
|
84
|
+
now:()=>new Date(1_700_000_000_000+index*100).toISOString()
|
|
85
|
+
});
|
|
86
|
+
const a=await runtime.capture();
|
|
87
|
+
await runtime.capture();
|
|
88
|
+
const c=await runtime.capture();
|
|
89
|
+
assert.throws(()=>runtime.frameStatus(a.frame.frame_id),/expired or unknown/);
|
|
90
|
+
assert.equal(runtime.framePixels(c.frame.frame_id).length,64);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('observer runtime rejects secure or unexpected input desktop',async()=>{
|
|
94
|
+
const runtime=createDesktopObserverRuntime({
|
|
95
|
+
captureProvider:async()=>parseNativeDesktopFrame(packet({input_desktop:'Winlogon'})),
|
|
96
|
+
expectedObserverSha256:H('observer'),runtimeNonce:'r40-secure'
|
|
97
|
+
});
|
|
98
|
+
await assert.rejects(runtime.capture(),/is not allowed/);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('raw title and process image do not escape parsed active-window identity',()=>{
|
|
102
|
+
const parsed=parseNativeDesktopFrame(packet());
|
|
103
|
+
assert.equal(Object.hasOwn(parsed.active_window,'title'),false);
|
|
104
|
+
assert.equal(Object.hasOwn(parsed.active_window,'image'),false);
|
|
105
|
+
assert.equal(JSON.stringify(parsed.active_window).includes('Editor'),false);
|
|
106
|
+
assert.equal(JSON.stringify(parsed.active_window).includes('editor.exe'),false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('T5 preview is deterministic PPM and hard-bounded by max_preview_bytes',()=>{
|
|
110
|
+
const parsed=parseNativeDesktopFrame(packet());
|
|
111
|
+
const a=createDesktopPreview({pixels:parsed.pixels,width:8,height:2,stride:32,maxBytes:128});
|
|
112
|
+
const b=createDesktopPreview({pixels:parsed.pixels,width:8,height:2,stride:32,maxBytes:128});
|
|
113
|
+
assert.equal(a.schema,'deadbyte.desktop-preview.v1');
|
|
114
|
+
assert.equal(a.encoding,'ppm-base64');
|
|
115
|
+
assert.ok(a.byte_length<=128);
|
|
116
|
+
assert.equal(a.sha256,b.sha256);
|
|
117
|
+
assert.equal(a.data_base64,b.data_base64);
|
|
118
|
+
assert.equal(Buffer.from(a.data_base64,'base64').subarray(0,2).toString('ascii'),'P6');
|
|
119
|
+
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp,writeFile } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import {
|
|
8
|
+
DESKTOP_POLICY_SCHEMA,DESKTOP_GRANT_SCHEMA,DESKTOP_ACTION_KINDS,
|
|
9
|
+
loadDesktopPolicy,desktopSessionStatus,assertDesktopArmed
|
|
10
|
+
} from '../src/desktop-policy-r40.mjs';
|
|
11
|
+
|
|
12
|
+
const H=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
13
|
+
|
|
14
|
+
async function fixture(){
|
|
15
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-policy-'));
|
|
16
|
+
const grant=path.join(root,'desktop-grant.json');
|
|
17
|
+
const evidence=path.join(root,'evidence');
|
|
18
|
+
const observer=path.join(root,'observer.exe');
|
|
19
|
+
const input=path.join(root,'input.exe');
|
|
20
|
+
const grounder=path.join(root,'grounder.exe');
|
|
21
|
+
await writeFile(observer,'observer');
|
|
22
|
+
await writeFile(input,'input');
|
|
23
|
+
await writeFile(grounder,'grounder');
|
|
24
|
+
const policyPath=path.join(root,'desktop-policy.json');
|
|
25
|
+
const raw={
|
|
26
|
+
schema:DESKTOP_POLICY_SCHEMA,enabled:true,grant_file:grant,evidence_dir:evidence,
|
|
27
|
+
observer:{executable:observer,sha256:H('observer'),backend:'gdi-bitblt-fallback'},
|
|
28
|
+
input:{executable:input,sha256:H('input')},
|
|
29
|
+
grounder:{executable:grounder,sha256:H('grounder')},
|
|
30
|
+
allowed_input_desktops:['Default'],
|
|
31
|
+
allowed_actions:DESKTOP_ACTION_KINDS,
|
|
32
|
+
limits:{max_frame_age_ms:2000,max_frame_bytes:8*1024*1024,capture_timeout_ms:5000,input_timeout_ms:2000,ground_timeout_ms:2000,post_observe_timeout_ms:5000,max_preview_bytes:512*1024,max_text_bytes:4096}
|
|
33
|
+
};
|
|
34
|
+
await writeFile(policyPath,JSON.stringify(raw));
|
|
35
|
+
const policy=await loadDesktopPolicy(policyPath);
|
|
36
|
+
return {root,grant,policyPath,policy};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test('desktop policy loads bounded observer/input authority metadata',async()=>{
|
|
40
|
+
const {policy}=await fixture();
|
|
41
|
+
assert.equal(policy.schema,DESKTOP_POLICY_SCHEMA);
|
|
42
|
+
assert.equal(policy.allowed_input_desktops[0],'Default');
|
|
43
|
+
assert.deepEqual(policy.allowed_actions,DESKTOP_ACTION_KINDS);
|
|
44
|
+
assert.equal(policy.limits.max_frame_age_ms,2000);
|
|
45
|
+
assert.equal(policy.limits.ground_timeout_ms,2000);
|
|
46
|
+
assert.equal(policy.grounder.sha256,H('grounder'));
|
|
47
|
+
assert.match(policy.policy_sha256,/^[0-9a-f]{64}$/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('desktop plane is fail-closed when no grant exists',async()=>{
|
|
51
|
+
const {policy}=await fixture();
|
|
52
|
+
assert.deepEqual(await desktopSessionStatus(policy),{
|
|
53
|
+
configured:true,armed:false,reason:'disarmed',standing:false,action_kinds:[]
|
|
54
|
+
});
|
|
55
|
+
await assert.rejects(assertDesktopArmed(policy,'click_primary'),/desktop plane is disarmed/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('desktop standing grant is policy-hash-bound and action-scoped',async()=>{
|
|
59
|
+
const {policy,grant}=await fixture();
|
|
60
|
+
await writeFile(grant,JSON.stringify({
|
|
61
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('nonce'),
|
|
62
|
+
action_kinds:['pointer_move','click_primary']
|
|
63
|
+
}));
|
|
64
|
+
const status=await desktopSessionStatus(policy);
|
|
65
|
+
assert.equal(status.armed,true);
|
|
66
|
+
assert.equal(status.reason,'standing-grant');
|
|
67
|
+
assert.deepEqual(status.action_kinds,['pointer_move','click_primary']);
|
|
68
|
+
assert.equal((await assertDesktopArmed(policy,'click_primary')).armed,true);
|
|
69
|
+
await assert.rejects(assertDesktopArmed(policy,'text_input'),/outside active grant/);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('desktop grant rejects policy substitution',async()=>{
|
|
73
|
+
const {policy,grant}=await fixture();
|
|
74
|
+
await writeFile(grant,JSON.stringify({
|
|
75
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:H('wrong-policy'),instance_nonce:H('nonce'),
|
|
76
|
+
action_kinds:['click_primary']
|
|
77
|
+
}));
|
|
78
|
+
assert.equal((await desktopSessionStatus(policy)).reason,'policy-digest');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('desktop grant rejects unknown or duplicate action kinds',async()=>{
|
|
82
|
+
const {policy,grant}=await fixture();
|
|
83
|
+
await writeFile(grant,JSON.stringify({
|
|
84
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('nonce'),
|
|
85
|
+
action_kinds:['click_primary','click_primary']
|
|
86
|
+
}));
|
|
87
|
+
assert.equal((await desktopSessionStatus(policy)).reason,'actions-invalid');
|
|
88
|
+
await writeFile(grant,JSON.stringify({
|
|
89
|
+
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('nonce'),
|
|
90
|
+
action_kinds:['click_primary','power_off']
|
|
91
|
+
}));
|
|
92
|
+
assert.equal((await desktopSessionStatus(policy)).reason,'actions-invalid');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('desktop policy rejects unsafe limits and malformed desktops',async()=>{
|
|
96
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-policy-bad-'));
|
|
97
|
+
const base={
|
|
98
|
+
schema:DESKTOP_POLICY_SCHEMA,enabled:true,
|
|
99
|
+
grant_file:path.join(root,'grant.json'),evidence_dir:path.join(root,'evidence'),
|
|
100
|
+
observer:{executable:path.join(root,'observer.exe'),sha256:H('observer')},
|
|
101
|
+
input:{executable:path.join(root,'input.exe'),sha256:H('input')},
|
|
102
|
+
grounder:{executable:path.join(root,'grounder.exe'),sha256:H('grounder')},
|
|
103
|
+
allowed_actions:['click_primary']
|
|
104
|
+
};
|
|
105
|
+
const p1=path.join(root,'bad-limit.json');
|
|
106
|
+
await writeFile(p1,JSON.stringify({...base,allowed_input_desktops:['Default'],limits:{max_frame_age_ms:999999}}));
|
|
107
|
+
await assert.rejects(loadDesktopPolicy(p1),/max_frame_age_ms/);
|
|
108
|
+
const p2=path.join(root,'bad-desktop.json');
|
|
109
|
+
await writeFile(p2,JSON.stringify({...base,allowed_input_desktops:['Default','Default']}));
|
|
110
|
+
await assert.rejects(loadDesktopPolicy(p2),/allowed_input_desktops invalid/);
|
|
111
|
+
});
|