deadbyte-mcp 0.15.47 → 0.15.49
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 +111 -48
- package/PROVENANCE.json +1 -1
- package/README.txt +18 -18
- package/RELEASE.json +3 -3
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bench/r121-epoch-readhead.mjs +54 -0
- package/bench/r124-epoch-predecessor.mjs +53 -0
- package/bin/deadbyte-epoch-lock-r85.node +0 -0
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/HARNESS2-ADAPTATION-20261020.md +24 -0
- package/docs/R128-RELEASE-CANDIDATE.md +26 -0
- package/docs/R75-RECOVERY-HANDOFF.md +16 -0
- package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
- package/native/deadbyte-epoch-lock-r85.cpp +241 -0
- package/package.json +1 -1
- package/scripts/build-epoch-lock-r85.ps1 +34 -0
- package/scripts/deploy-r48.mjs +3 -1
- package/scripts/npm-release-r48.mjs +18 -4
- package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
- package/scripts/release-manifest.mjs +1 -1
- package/scripts/verify-authority-epoch-history.mjs +38 -0
- package/scripts/verify-r70-full-regression.ps1 +12 -2
- package/src/activation-authority-proof-r75.mjs +67 -0
- package/src/activation-continuity-engine-r65.mjs +17 -10
- package/src/activation-preflight-r65.mjs +13 -0
- package/src/agent-eval-r42.mjs +30 -0
- package/src/authority-continuity-r65.mjs +442 -29
- package/src/autonomous-local-supervisor.mjs +77 -47
- package/src/deadbyte-cli.mjs +73 -46
- package/src/epoch-lock-r85.mjs +48 -0
- package/src/foreground-marker-cas-win32.mjs +284 -0
- package/src/foreground-process-identity.mjs +3 -3
- package/src/privileged-install-trust-r89.mjs +196 -0
- package/src/remote-live-log.mjs +15 -8
- package/src/remote-log-decode-r60.mjs +65 -0
- package/src/remote-session-guardian.mjs +13 -12
- package/src/runtime-update.mjs +5 -22
- package/src/version.mjs +1 -1
- package/src/windows-powershell-environment.mjs +24 -0
- package/test/autonomous-local-supervisor.test.mjs +1 -1
- package/test/cli-entrypoint.test.mjs +1 -1
- package/test/harness2-eval-context-isolation.test.mjs +90 -0
- package/test/helpers/r75-authority-proof.mjs +9 -0
- package/test/r100-posix-owned-lock-race.test.mjs +72 -0
- package/test/r101-acl-probe-environment.test.mjs +51 -0
- package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
- package/test/r107-updater-powershell-path.test.mjs +77 -0
- package/test/r108-remote-powershell-path.test.mjs +59 -0
- package/test/r110-supervisor-powershell-path.test.mjs +28 -0
- package/test/r111-supervisor-marker-cas.test.mjs +116 -0
- package/test/r112-supervisor-claim-cas.test.mjs +63 -0
- package/test/r113-supervisor-cleanup-terminality.test.mjs +100 -0
- package/test/r114-supervisor-absence-proof.test.mjs +45 -0
- package/test/r115-posix-continuity-entrypoint.test.mjs +17 -0
- package/test/r116-authority-crash-matrix.test.mjs +83 -0
- package/test/r117-concurrent-orphan-recovery.test.mjs +54 -0
- package/test/r118-cross-process-orphan-recovery.test.mjs +99 -0
- package/test/r119-recovery-crash-after-commit.test.mjs +91 -0
- package/test/r120-authority-history-audit.test.mjs +103 -0
- package/test/r121-signed-transition-invariant.test.mjs +132 -0
- package/test/r122-epoch-bounded-read.test.mjs +58 -0
- package/test/r123-recovery-bounded-read.test.mjs +64 -0
- package/test/r124-live-log-integrity.test.mjs +66 -0
- package/test/r124-live-log-tail-boundaries.test.mjs +57 -0
- package/test/r124-signed-predecessor-shape.test.mjs +66 -0
- package/test/r125-restore-binding-types.test.mjs +63 -0
- package/test/r126-restore-policy-envelope.test.mjs +89 -0
- package/test/r127-restore-append-policy-boundary.test.mjs +25 -0
- package/test/r33-closeout-regression.test.mjs +2 -1
- package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
- package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
- package/test/r56-rollback-launcher-restore.test.mjs +2 -2
- package/test/r57-core-startup-race.test.mjs +2 -2
- package/test/r58-concurrent-recovery.test.mjs +10 -7
- package/test/r58-stale-owner.test.mjs +5 -6
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r65-activation-orchestration.test.mjs +2 -1
- package/test/r65-activation-preflight.test.mjs +7 -6
- package/test/r65-authority-epoch-recovery.test.mjs +3 -1
- package/test/r65-authority-epoch-store.test.mjs +9 -6
- package/test/r65-authority-restored-safe.test.mjs +2 -1
- package/test/r65-deploy-preflight.test.mjs +13 -14
- package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
- package/test/r65-legacy-authority-migration.test.mjs +2 -1
- package/test/r67-npm-cross-volume.test.mjs +9 -2
- package/test/r71-full-verifier-artifact.test.mjs +21 -15
- package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
- package/test/r71-release-identity.test.mjs +3 -1
- package/test/r74-kernel-lock.test.mjs +12 -6
- package/test/r74-prepublish-contract.test.mjs +3 -1
- package/test/r75-activation-authority-proof.test.mjs +46 -0
- package/test/r75-authority-evidence.test.mjs +77 -0
- package/test/r76-pinned-marker-cas.test.mjs +78 -0
- package/test/r77-epoch-grant-cas.test.mjs +51 -0
- package/test/r78-parent-junction-cas.test.mjs +62 -0
- package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
- package/test/r79-windows-fsync-regression.test.mjs +45 -0
- package/test/r80-durable-epoch-commit.test.mjs +76 -0
- package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
- package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
- package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
- package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
- package/test/r85-epoch-repeat-append.test.mjs +30 -0
- package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
- package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
- package/test/r88-native-disposition-failure.test.mjs +35 -0
- package/test/r89-privileged-install-boundary.test.mjs +123 -0
- package/test/r92-acl-ace-applicability.test.mjs +45 -0
- package/test/r94-win32-propagation-enum.test.mjs +51 -0
- package/test/r99-privileged-import-closure.test.mjs +45 -0
- package/test/remote-session-cli.test.mjs +69 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { lstat
|
|
2
|
+
import { lstat } from 'node:fs/promises';
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -7,8 +7,10 @@ import { setTimeout as sleep } from 'node:timers/promises';
|
|
|
7
7
|
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
8
8
|
import { resolveStateRoot } from './state-root.mjs';
|
|
9
9
|
import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
|
|
10
|
+
import { readRemoteMarkerSnapshot, deletePinnedRemoteMarker } from './foreground-marker-cas-win32.mjs';
|
|
10
11
|
|
|
11
12
|
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
|
|
13
|
+
const TRUSTED_WINDOWS_ROOT='C:\\Windows';
|
|
12
14
|
// This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
|
|
13
15
|
// poll. A reused PID cannot keep a new foreground guardian alive.
|
|
14
16
|
let ownerDisconnected=false;
|
|
@@ -33,10 +35,8 @@ function pidAlive(pid){
|
|
|
33
35
|
catch(error){return error?.code!=='ESRCH';}
|
|
34
36
|
}
|
|
35
37
|
async function readMarker(file){
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
catch(error){if(error?.code==='ENOENT') return null;throw error;}
|
|
39
|
-
try{return JSON.parse(text);}catch{return null;}
|
|
38
|
+
const snapshot=await readRemoteMarkerSnapshot(file);
|
|
39
|
+
return snapshot?.marker??null;
|
|
40
40
|
}
|
|
41
41
|
function markerMatches(marker,{ownerPid,nonce}){
|
|
42
42
|
return marker?.schema==='deadbyte.remote-session.v2'&&
|
|
@@ -45,7 +45,7 @@ function markerMatches(marker,{ownerPid,nonce}){
|
|
|
45
45
|
}
|
|
46
46
|
function invokeCleanup(powershell,launcher,command,stateRoot,probeMode=null){
|
|
47
47
|
const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,command],{
|
|
48
|
-
stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot,
|
|
48
|
+
stdio:'ignore',windowsHide:true,shell:false,env:windowsPowerShellEnvironment({...process.env,SystemRoot:TRUSTED_WINDOWS_ROOT,WINDIR:TRUSTED_WINDOWS_ROOT,DEADBYTE_STATE_ROOT:stateRoot,
|
|
49
49
|
...(probeMode?{DEADBYTE_REMOTE_PROBE_MODE:probeMode}:{})}),timeout:probeMode?150000:30000
|
|
50
50
|
});
|
|
51
51
|
return !result.error&&result.status===0;
|
|
@@ -53,7 +53,7 @@ function invokeCleanup(powershell,launcher,command,stateRoot,probeMode=null){
|
|
|
53
53
|
function verifyRevoked(powershell,launcher,stateRoot){
|
|
54
54
|
const result=spawnSync(powershell,['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',launcher,'status','-Json'],{
|
|
55
55
|
stdio:['ignore','pipe','pipe'],encoding:'utf8',windowsHide:true,shell:false,
|
|
56
|
-
env:windowsPowerShellEnvironment({...process.env,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
|
|
56
|
+
env:windowsPowerShellEnvironment({...process.env,SystemRoot:TRUSTED_WINDOWS_ROOT,WINDIR:TRUSTED_WINDOWS_ROOT,DEADBYTE_STATE_ROOT:stateRoot}),timeout:30000
|
|
57
57
|
});
|
|
58
58
|
if(result.error||result.status!==0) return null;
|
|
59
59
|
try{
|
|
@@ -74,9 +74,7 @@ async function main(){
|
|
|
74
74
|
if(!/^[0-9a-f]{64}$/.test(nonce)) throw new Error('guardian session nonce invalid');
|
|
75
75
|
|
|
76
76
|
const launcher=await requireRegular(path.join(root,'bin','DEADBYTE-MCP.ps1'),'stable DEADBYTE launcher');
|
|
77
|
-
const
|
|
78
|
-
if(!systemRoot) throw new Error('SystemRoot/WINDIR missing');
|
|
79
|
-
const powershell=await requireRegular(path.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
|
|
77
|
+
const powershell=await requireRegular(path.join(TRUSTED_WINDOWS_ROOT,'System32','WindowsPowerShell','v1.0','powershell.exe'),'Windows PowerShell');
|
|
80
78
|
const markerPath=path.join(root,'runtime','remote-session.json');
|
|
81
79
|
|
|
82
80
|
for(;;){
|
|
@@ -99,8 +97,11 @@ async function main(){
|
|
|
99
97
|
if(observed.state==='ONLINE'&&!invokeCleanup(powershell,launcher,'machinetest',root,'revoked'))
|
|
100
98
|
throw new Error('guardian runtime enforcement cleanup unverified; ownership marker retained');
|
|
101
99
|
if(!invokeCleanup(powershell,launcher,'park',root)) throw new Error('guardian park failed; ownership marker retained');
|
|
102
|
-
const current=await
|
|
103
|
-
if(current
|
|
100
|
+
const current=await readRemoteMarkerSnapshot(markerPath);
|
|
101
|
+
if(!current)return;
|
|
102
|
+
if(!markerMatches(current.marker,{ownerPid,nonce}))return;
|
|
103
|
+
if(deletePinnedRemoteMarker(markerPath,current.sha256)!=='REMOVED')
|
|
104
|
+
throw new Error('guardian marker changed before native deletion; marker retained');
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
106
107
|
}
|
package/src/runtime-update.mjs
CHANGED
|
@@ -21,6 +21,9 @@ import { resolveStateRoot } from './state-root.mjs';
|
|
|
21
21
|
import {canonicalLauncherPaths,assertCanonicalLaunchers,renderStateBoundLauncherCmd} from './launcher-isolation.mjs';
|
|
22
22
|
import {assertStageOwnedPath,assertStagingBootstrapInputs,assertStagingControlConfig} from './staging-input-isolation.mjs';
|
|
23
23
|
import { verifyProjectOwnerApproval } from './r70-owner-approval.mjs';
|
|
24
|
+
import {windowsPowerShellEnvironment} from './windows-powershell-environment.mjs';
|
|
25
|
+
|
|
26
|
+
export {windowsPowerShellEnvironment};
|
|
24
27
|
|
|
25
28
|
const HEX64=/^[0-9a-f]{64}$/;
|
|
26
29
|
const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
@@ -216,31 +219,11 @@ export function parseRollbackArgs(argv){
|
|
|
216
219
|
}
|
|
217
220
|
|
|
218
221
|
function powershellPath(){
|
|
219
|
-
return
|
|
220
|
-
}
|
|
221
|
-
export function windowsPowerShellEnvironment(base=process.env){
|
|
222
|
-
const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
|
|
223
|
-
const userProfile=base.USERPROFILE||os.homedir();
|
|
224
|
-
const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
|
|
225
|
-
const environment={...base};
|
|
226
|
-
const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath').flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
|
|
227
|
-
for(const key of Object.keys(environment)) if(key.toLowerCase()==='psmodulepath') delete environment[key];
|
|
228
|
-
const modules=[
|
|
229
|
-
path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
|
|
230
|
-
path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
|
|
231
|
-
path.join(programFiles,'WindowsPowerShell','Modules'),
|
|
232
|
-
...inherited
|
|
233
|
-
];
|
|
234
|
-
const seen=new Set();
|
|
235
|
-
return {...environment,PSModulePath:modules.filter(modulePath=>{
|
|
236
|
-
const key=modulePath.toLowerCase();
|
|
237
|
-
if(seen.has(key)) return false;
|
|
238
|
-
seen.add(key);return true;
|
|
239
|
-
}).join(';')};
|
|
222
|
+
return 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
240
223
|
}
|
|
241
224
|
function commandEnvironment(exe,base=process.env){
|
|
242
225
|
return path.resolve(exe).toLowerCase()===path.resolve(powershellPath()).toLowerCase()
|
|
243
|
-
?windowsPowerShellEnvironment(base):base;
|
|
226
|
+
?windowsPowerShellEnvironment({...base,SystemRoot:'C:\\Windows',WINDIR:'C:\\Windows'}):base;
|
|
244
227
|
}
|
|
245
228
|
function runCaptured(exe,args,{cwd,env,timeoutMs=45000}={}){
|
|
246
229
|
const baseEnv=env??process.env;
|
package/src/version.mjs
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Dependency-light PowerShell child environment for security-critical process
|
|
2
|
+
// identity and pinned-marker operations. Do not import the updater here.
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
export function windowsPowerShellEnvironment(base=process.env){
|
|
7
|
+
const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
|
|
8
|
+
const userProfile=base.USERPROFILE||os.homedir();
|
|
9
|
+
const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
|
|
10
|
+
const hasWindir=Object.keys(base).some(key=>key.toLowerCase()==='windir');
|
|
11
|
+
const environment={...base};
|
|
12
|
+
const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath')
|
|
13
|
+
.flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
|
|
14
|
+
for(const key of Object.keys(environment))if(['psmodulepath','systemroot','windir'].includes(key.toLowerCase()))delete environment[key];
|
|
15
|
+
environment.SystemRoot=systemRoot;
|
|
16
|
+
if(hasWindir)environment.WINDIR=systemRoot;
|
|
17
|
+
const modules=[path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
|
|
18
|
+
path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
|
|
19
|
+
path.join(programFiles,'WindowsPowerShell','Modules'),...inherited];
|
|
20
|
+
const seen=new Set();
|
|
21
|
+
return {...environment,PSModulePath:modules.filter(item=>{
|
|
22
|
+
const key=item.toLowerCase();if(seen.has(key))return false;seen.add(key);return true;
|
|
23
|
+
}).join(';')};
|
|
24
|
+
}
|
|
@@ -365,7 +365,7 @@ test('continuous standalone supervisor remains alive after READY and hard-stop o
|
|
|
365
365
|
await new Promise(resolve=>child.once('exit',()=>resolve()));
|
|
366
366
|
const stale=await observeSupervisor({markerPath:f.markerPath});
|
|
367
367
|
assert.equal(stale.live,false);
|
|
368
|
-
assert.match(stale.reason,/
|
|
368
|
+
assert.match(stale.reason,/process_absent|pid_reused|identity_mismatch/);
|
|
369
369
|
const claimed=await takeoverSupervisor({markerPath:f.markerPath});
|
|
370
370
|
assert.equal(claimed.status,'claimed');
|
|
371
371
|
await releaseSupervisor({markerPath:f.markerPath,instanceNonce:claimed.instance_nonce});
|
|
@@ -44,7 +44,7 @@ test('npx CLI exposes local supervisor lifecycle without any supervisor arm comm
|
|
|
44
44
|
assert.match(source,/supervisorstop/);
|
|
45
45
|
assert.match(source,/supervisorstatus/);
|
|
46
46
|
assert.match(source,/supervisorrecover/);
|
|
47
|
-
const mapping=source.match(/const SUPERVISOR_COMMANDS
|
|
47
|
+
const mapping=source.match(/const SUPERVISOR_COMMANDS=[^;]*;/s)?.[0]??'';
|
|
48
48
|
assert.doesNotMatch(mapping,/arm(?:coding|autonomous|process|host)/i);
|
|
49
49
|
assert.match(source,/npx -y \$\{exact\} supervisor status/);
|
|
50
50
|
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { runIsolatedEvaluation } from '../src/agent-eval-r42.mjs';
|
|
4
|
+
|
|
5
|
+
const caseBase = {
|
|
6
|
+
schema: 'deadbyte.agent-eval-case.v1',
|
|
7
|
+
case_id: 'harness2-context-boundary',
|
|
8
|
+
task: 'Return a bounded plan.',
|
|
9
|
+
acceptance: ['Bounded plan']
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
test('judge-only oracle token nested in allowed context is rejected before generator invocation', async () => {
|
|
13
|
+
const token = 'JUDGE_ONLY_38d0f04a8a7b4ae8';
|
|
14
|
+
let generatorCalls = 0;
|
|
15
|
+
await assert.rejects(runIsolatedEvaluation({
|
|
16
|
+
caseSpec: {
|
|
17
|
+
...caseBase,
|
|
18
|
+
allowed_context: {files: [{path: 'context.md', contents: `visible notes: ${token}`}]},
|
|
19
|
+
hidden_oracle: {graded: {token}}
|
|
20
|
+
},
|
|
21
|
+
generator: async () => { generatorCalls++; return {artifact: {plan: ['safe']}}; },
|
|
22
|
+
judge: async () => ({verdict: 'pass', findings: [], evidence_refs: ['a'.repeat(64)]})
|
|
23
|
+
}), /judge-only.*context|oracle.*leak/i);
|
|
24
|
+
assert.equal(generatorCalls, 0);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('judge-only secret in task or acceptance fails closed without echoing secret', async () => {
|
|
28
|
+
const token = 'SECRET_4f725e0e1cbe4a15';
|
|
29
|
+
for (const visible of [
|
|
30
|
+
{task: `Return ${token}`, acceptance: ['Bounded plan']},
|
|
31
|
+
{task: 'Return a bounded plan.', acceptance: [`Must not reveal ${token}`]}
|
|
32
|
+
]) {
|
|
33
|
+
let generatorCalls = 0;
|
|
34
|
+
await assert.rejects(runIsolatedEvaluation({
|
|
35
|
+
caseSpec: {...caseBase, ...visible, allowed_context: {}, hidden_oracle: {secret: token}},
|
|
36
|
+
generator: async () => { generatorCalls++; return {artifact: {plan: ['safe']}}; },
|
|
37
|
+
judge: async () => ({verdict: 'pass', findings: [], evidence_refs: ['a'.repeat(64)]})
|
|
38
|
+
}), error => {
|
|
39
|
+
assert.match(error.message, /judge-only.*context/i);
|
|
40
|
+
assert.equal(error.message.includes(token), false);
|
|
41
|
+
return true;
|
|
42
|
+
});
|
|
43
|
+
assert.equal(generatorCalls, 0);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('short explicit judge-only secret is not exempt from context isolation', async () => {
|
|
48
|
+
let generatorCalls = 0;
|
|
49
|
+
await assert.rejects(runIsolatedEvaluation({
|
|
50
|
+
caseSpec: {
|
|
51
|
+
...caseBase,
|
|
52
|
+
allowed_context: {note: 'Visible value K9 is forbidden here'},
|
|
53
|
+
hidden_oracle: {secret: 'K9'}
|
|
54
|
+
},
|
|
55
|
+
generator: async () => { generatorCalls++; return {artifact: {plan: ['safe']}}; },
|
|
56
|
+
judge: async () => ({verdict: 'pass', findings: [], evidence_refs: ['a'.repeat(64)]})
|
|
57
|
+
}), /judge-only.*context/i);
|
|
58
|
+
assert.equal(generatorCalls, 0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('nested judge verdict objects cannot enter generator-visible context', async () => {
|
|
62
|
+
for (const forbidden of ['hidden_oracle', 'judge_result', 'grader_verdict', 'deterministic_eval_result']) {
|
|
63
|
+
let generatorCalls = 0;
|
|
64
|
+
await assert.rejects(runIsolatedEvaluation({
|
|
65
|
+
caseSpec: {
|
|
66
|
+
...caseBase,
|
|
67
|
+
allowed_context: {fixture: {metadata: {[forbidden]: {verdict: 'pass'}}}},
|
|
68
|
+
hidden_oracle: {expected: 'bounded'}
|
|
69
|
+
},
|
|
70
|
+
generator: async () => { generatorCalls++; return {artifact: {plan: ['safe']}}; },
|
|
71
|
+
judge: async () => ({verdict: 'pass', findings: [], evidence_refs: ['a'.repeat(64)]})
|
|
72
|
+
}), /judge-only.*context|grader.*context/i);
|
|
73
|
+
assert.equal(generatorCalls, 0, `${forbidden} must reject before generator dispatch`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('ordinary shared acceptance terms do not trigger oracle isolation tripwire', async () => {
|
|
78
|
+
let generatorCalls = 0;
|
|
79
|
+
const result = await runIsolatedEvaluation({
|
|
80
|
+
caseSpec: {
|
|
81
|
+
...caseBase,
|
|
82
|
+
allowed_context: {visible: 'bounded plan'},
|
|
83
|
+
hidden_oracle: {expected: 'bounded plan'}
|
|
84
|
+
},
|
|
85
|
+
generator: async () => { generatorCalls++; return {artifact: {plan: ['safe']}}; },
|
|
86
|
+
judge: async () => ({verdict: 'pass', findings: [], evidence_refs: ['a'.repeat(64)]})
|
|
87
|
+
});
|
|
88
|
+
assert.equal(result.verdict, 'pass');
|
|
89
|
+
assert.equal(generatorCalls, 1);
|
|
90
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Unit fixtures use synthetic observations; integration tests verify real policy/grant files.
|
|
2
|
+
export function withAuthorityProof(observation){
|
|
3
|
+
const authority_proof=Object.fromEntries(Object.entries(observation.authority).map(([plane,entry])=>{
|
|
4
|
+
const armed=typeof entry==='boolean'?entry:entry.armed;
|
|
5
|
+
return [plane,{state:armed?'VERIFIED_ARMED':'VERIFIED_DISARMED',
|
|
6
|
+
policy_sha256:observation.policy_sha256[plane],grant_absent:!armed}];
|
|
7
|
+
}));
|
|
8
|
+
return {...observation,authority_proof};
|
|
9
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {generateKeyPairSync,randomBytes} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,writeFile,readFile,rename,rm,access} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {pathToFileURL} from 'node:url';
|
|
8
|
+
|
|
9
|
+
const source=process.env.R100_BASELINE_SOURCE
|
|
10
|
+
?pathToFileURL(process.env.R100_BASELINE_SOURCE)
|
|
11
|
+
:new URL('../src/authority-continuity-r65.mjs',import.meta.url);
|
|
12
|
+
const {createAuthorityEpochStore}=await import(source.href);
|
|
13
|
+
|
|
14
|
+
test('R100 POSIX owned release cannot unlink a byte-identical successor lock',
|
|
15
|
+
{skip:process.platform==='win32'},async()=>{
|
|
16
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r100-posix-'));
|
|
17
|
+
try{
|
|
18
|
+
const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
|
|
19
|
+
await mkdir(runtimeDir);await mkdir(trust);
|
|
20
|
+
const keys=generateKeyPairSync('ed25519');
|
|
21
|
+
await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
|
|
22
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
|
|
23
|
+
const nonce=randomBytes(32).toString('hex');
|
|
24
|
+
const grantBytes=Buffer.from(JSON.stringify({instance_nonce:nonce}));
|
|
25
|
+
const lock=path.join(runtimeDir,'authority-epochs','process','.lock');
|
|
26
|
+
let replaced=0,successor;
|
|
27
|
+
const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async(stage,file)=>{
|
|
28
|
+
if(stage!=='release_before_cas')return;
|
|
29
|
+
assert.equal(file,lock);
|
|
30
|
+
successor=await readFile(lock);
|
|
31
|
+
const other=lock+'.successor';
|
|
32
|
+
await writeFile(other,successor,{flag:'wx'});
|
|
33
|
+
await rename(other,lock);
|
|
34
|
+
replaced++;
|
|
35
|
+
}});
|
|
36
|
+
await assert.rejects(store.append({plane:'process',action:'grant',grantBytes,
|
|
37
|
+
grantInstanceNonce:nonce}),/authority epoch lock changed|ownership lost|POSIX.*unsupported/);
|
|
38
|
+
if(replaced===0){
|
|
39
|
+
await assert.rejects(access(path.join(runtimeDir,'authority-epochs','process','head.json')),{code:'ENOENT'});
|
|
40
|
+
}else{
|
|
41
|
+
assert.equal(replaced,1,'replacement interleave must execute at most once');
|
|
42
|
+
assert.deepEqual(await readFile(lock),successor,'byte-identical successor lock must survive');
|
|
43
|
+
}
|
|
44
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('R100 POSIX unsupported mutation makes no epoch directory and preserves an existing lock',
|
|
48
|
+
{skip:process.platform==='win32'},async()=>{
|
|
49
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r100-no-side-effect-'));
|
|
50
|
+
try{
|
|
51
|
+
const runtimeDir=path.join(root,'runtime');
|
|
52
|
+
const trust=path.join(root,'trust');
|
|
53
|
+
await mkdir(runtimeDir);await mkdir(trust);
|
|
54
|
+
const keys=generateKeyPairSync('ed25519');
|
|
55
|
+
await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
|
|
56
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
|
|
57
|
+
const nonce=randomBytes(32).toString('hex');
|
|
58
|
+
const grantBytes=Buffer.from(JSON.stringify({instance_nonce:nonce}));
|
|
59
|
+
const operation=()=>createAuthorityEpochStore({runtimeDir}).append({plane:'process',action:'grant',
|
|
60
|
+
grantBytes,grantInstanceNonce:nonce});
|
|
61
|
+
await assert.rejects(operation,/authority epoch POSIX mutation unsupported/);
|
|
62
|
+
const epochDir=path.join(runtimeDir,'authority-epochs','process');
|
|
63
|
+
await assert.rejects(access(epochDir),{code:'ENOENT'});
|
|
64
|
+
assert.equal(await createAuthorityEpochStore({runtimeDir}).readHead({plane:'process'}),null,
|
|
65
|
+
'read-only empty-head inspection remains available');
|
|
66
|
+
await mkdir(epochDir,{recursive:true});
|
|
67
|
+
const lock=path.join(epochDir,'.lock'),original=Buffer.from('not-an-owned-lock');
|
|
68
|
+
await writeFile(lock,original,{flag:'wx'});
|
|
69
|
+
await assert.rejects(operation,/authority epoch POSIX mutation unsupported/);
|
|
70
|
+
assert.deepEqual(await readFile(lock),original);
|
|
71
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
72
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {createRequire, syncBuiltinESMExports} from 'node:module';
|
|
5
|
+
import {pathToFileURL} from 'node:url';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const require=createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
test('R101 privileged ACL probe excludes inherited module roots at child dispatch',
|
|
11
|
+
{skip:process.platform!=='win32'}, async()=>{
|
|
12
|
+
const childProcess=require('node:child_process');
|
|
13
|
+
const originalExecFile=childProcess.execFile;
|
|
14
|
+
const originalModulePath=process.env.PSModulePath;
|
|
15
|
+
const originalSystemRoot=process.env.SystemRoot;
|
|
16
|
+
let captured;
|
|
17
|
+
try {
|
|
18
|
+
childProcess.execFile=(executable,args,options,callback)=>{
|
|
19
|
+
captured={executable,args,environment:options.env};
|
|
20
|
+
callback(Object.assign(new Error('test stopped child before execution'),{code:'TEST_STOP'}));
|
|
21
|
+
};
|
|
22
|
+
syncBuiltinESMExports();
|
|
23
|
+
process.env.PSModulePath='C:\\r101-poisoned-module-root';
|
|
24
|
+
process.env.SystemRoot='C:\\r101-false-windows-root';
|
|
25
|
+
const target=process.env.R101_BASELINE_SOURCE
|
|
26
|
+
? pathToFileURL(path.join(process.env.R101_BASELINE_SOURCE,'src','privileged-install-trust-r89.mjs'))
|
|
27
|
+
: new URL('../src/privileged-install-trust-r89.mjs',import.meta.url);
|
|
28
|
+
const {inspectTrustedInstallation}=await import(`${target.href}?r101-acl-probe`);
|
|
29
|
+
await assert.rejects(inspectTrustedInstallation(['C:\\Program Files\\Deadbyte\\bin\\addon.node']),
|
|
30
|
+
{code:'EPOCH_NATIVE_SLOT_UNTRUSTED'});
|
|
31
|
+
assert.ok(captured,'ACL child must have been dispatched');
|
|
32
|
+
assert.equal(captured.executable,'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe');
|
|
33
|
+
assert.equal(captured.environment.PSModulePath,
|
|
34
|
+
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules');
|
|
35
|
+
assert.equal(captured.environment.SystemRoot,'C:\\Windows');
|
|
36
|
+
assert.equal(captured.environment.WINDIR,'C:\\Windows');
|
|
37
|
+
const observed=spawnSync(captured.executable,['-NoLogo','-NoProfile','-NonInteractive',
|
|
38
|
+
'-Command',"(Get-Command Get-Acl -ErrorAction Stop).Module.Path"],
|
|
39
|
+
{env:captured.environment,encoding:'utf8',timeout:12000,windowsHide:true});
|
|
40
|
+
assert.equal(observed.status,0,observed.stderr);
|
|
41
|
+
assert.match(observed.stdout.trim(),
|
|
42
|
+
/^C:\\Windows\\System32\\WindowsPowerShell\\v1\.0\\Modules\\Microsoft\.PowerShell\.Security\\/i);
|
|
43
|
+
} finally {
|
|
44
|
+
childProcess.execFile=originalExecFile;
|
|
45
|
+
syncBuiltinESMExports();
|
|
46
|
+
if(originalModulePath===undefined)delete process.env.PSModulePath;
|
|
47
|
+
else process.env.PSModulePath=originalModulePath;
|
|
48
|
+
if(originalSystemRoot===undefined)delete process.env.SystemRoot;
|
|
49
|
+
else process.env.SystemRoot=originalSystemRoot;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import childProcess from 'node:child_process';
|
|
4
|
+
import {syncBuiltinESMExports} from 'node:module';
|
|
5
|
+
import {windowsPowerShellEnvironment} from '../src/windows-powershell-environment.mjs';
|
|
6
|
+
|
|
7
|
+
const TRUSTED_POWERSHELL='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
8
|
+
|
|
9
|
+
test('PowerShell child environment collapses case-variant Windows root keys',()=>{
|
|
10
|
+
const environment=windowsPowerShellEnvironment({
|
|
11
|
+
SystemRoot:'C:\\Windows',SYSTEMROOT:'C:\\untrusted-root',
|
|
12
|
+
WINDIR:'C:\\Windows',windir:'C:\\untrusted-root',
|
|
13
|
+
PSModulePath:'C:\\custom-modules'
|
|
14
|
+
});
|
|
15
|
+
assert.deepEqual(Object.keys(environment).filter(key=>key.toLowerCase()==='systemroot'),['SystemRoot']);
|
|
16
|
+
assert.deepEqual(Object.keys(environment).filter(key=>key.toLowerCase()==='windir'),['WINDIR']);
|
|
17
|
+
assert.equal(environment.SystemRoot,'C:\\Windows');
|
|
18
|
+
assert.equal(environment.WINDIR,'C:\\Windows');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('security-sensitive Windows probes cannot select PowerShell via caller environment',
|
|
22
|
+
{skip:process.platform!=='win32',concurrency:false},async()=>{
|
|
23
|
+
const originalSpawn=childProcess.spawnSync;
|
|
24
|
+
const originalRoot=process.env.SystemRoot;
|
|
25
|
+
const originalWindir=process.env.WINDIR;
|
|
26
|
+
const calls=[];
|
|
27
|
+
try{
|
|
28
|
+
process.env.SystemRoot='C:\\untrusted-root';
|
|
29
|
+
process.env.WINDIR='C:\\untrusted-root';
|
|
30
|
+
childProcess.spawnSync=(file,args,options)=>{
|
|
31
|
+
calls.push({file,options});
|
|
32
|
+
return {status:0,stdout:calls.length===1?'MISSING\n':'null\n',stderr:''};
|
|
33
|
+
};
|
|
34
|
+
syncBuiltinESMExports();
|
|
35
|
+
const marker=await import('../src/foreground-marker-cas-win32.mjs');
|
|
36
|
+
const identity=await import('../src/foreground-process-identity.mjs');
|
|
37
|
+
assert.equal(marker.readPinnedEpochLockSnapshot('C:\\scratch\\lock.json'),null);
|
|
38
|
+
assert.equal(identity.queryForegroundIdentity(12345),null);
|
|
39
|
+
assert.equal(calls.length,2);
|
|
40
|
+
for(const call of calls){
|
|
41
|
+
assert.equal(call.file,TRUSTED_POWERSHELL);
|
|
42
|
+
assert.equal(call.options.env.SystemRoot,'C:\\Windows');
|
|
43
|
+
assert.equal(call.options.env.WINDIR,'C:\\Windows');
|
|
44
|
+
}
|
|
45
|
+
}finally{
|
|
46
|
+
childProcess.spawnSync=originalSpawn;
|
|
47
|
+
syncBuiltinESMExports();
|
|
48
|
+
if(originalRoot===undefined)delete process.env.SystemRoot;
|
|
49
|
+
else process.env.SystemRoot=originalRoot;
|
|
50
|
+
if(originalWindir===undefined)delete process.env.WINDIR;
|
|
51
|
+
else process.env.WINDIR=originalWindir;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import childProcess from 'node:child_process';
|
|
4
|
+
import {syncBuiltinESMExports} from 'node:module';
|
|
5
|
+
import {mkdtemp,writeFile,rm} from 'node:fs/promises';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {loadPowerShellDataFile,runControllerStartNoCapture,windowsPowerShellEnvironment} from '../src/runtime-update.mjs';
|
|
9
|
+
|
|
10
|
+
test('updater PowerShell environment rejects case-colliding Windows root keys',()=>{
|
|
11
|
+
const environment=windowsPowerShellEnvironment({
|
|
12
|
+
SystemRoot:'C:\\Windows',SYSTEMROOT:'C:\\untrusted-root',
|
|
13
|
+
WINDIR:'C:\\Windows',windir:'C:\\untrusted-root'
|
|
14
|
+
});
|
|
15
|
+
assert.deepEqual(Object.keys(environment).filter(key=>key.toLowerCase()==='systemroot'),['SystemRoot']);
|
|
16
|
+
assert.deepEqual(Object.keys(environment).filter(key=>key.toLowerCase()==='windir'),['WINDIR']);
|
|
17
|
+
assert.equal(environment.SystemRoot,'C:\\Windows');
|
|
18
|
+
assert.equal(environment.WINDIR,'C:\\Windows');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('real updater PowerShell still loads read-only config under poisoned Windows roots',
|
|
22
|
+
{skip:process.platform!=='win32',concurrency:false},async()=>{
|
|
23
|
+
const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r107-ps-'));
|
|
24
|
+
const priorRoot=process.env.SystemRoot;
|
|
25
|
+
const priorWindir=process.env.WINDIR;
|
|
26
|
+
try{
|
|
27
|
+
const config=path.join(scratch,'config.psd1');
|
|
28
|
+
await writeFile(config,"@{ Schema = 'r107.test'; Enabled = $true }\n",{flag:'wx'});
|
|
29
|
+
process.env.SystemRoot='C:\\untrusted-root';
|
|
30
|
+
process.env.WINDIR='C:\\untrusted-root';
|
|
31
|
+
assert.deepEqual(await loadPowerShellDataFile(config),{Enabled:true,Schema:'r107.test'});
|
|
32
|
+
}finally{
|
|
33
|
+
if(priorRoot===undefined)delete process.env.SystemRoot;
|
|
34
|
+
else process.env.SystemRoot=priorRoot;
|
|
35
|
+
if(priorWindir===undefined)delete process.env.WINDIR;
|
|
36
|
+
else process.env.WINDIR=priorWindir;
|
|
37
|
+
await rm(scratch,{recursive:true,force:true});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('runtime updater cannot launch caller-selected PowerShell for config and controller work',
|
|
42
|
+
{skip:process.platform!=='win32',concurrency:false},async()=>{
|
|
43
|
+
const originalSpawn=childProcess.spawnSync;
|
|
44
|
+
const originalRoot=process.env.SystemRoot;
|
|
45
|
+
const originalWindir=process.env.WINDIR;
|
|
46
|
+
let call=null;
|
|
47
|
+
try{
|
|
48
|
+
process.env.SystemRoot='C:\\untrusted-root';
|
|
49
|
+
process.env.WINDIR='C:\\untrusted-root';
|
|
50
|
+
childProcess.spawnSync=(file,args,options)=>{
|
|
51
|
+
call={file,args,options};
|
|
52
|
+
return {status:0,signal:null,error:null,stdout:'{}\n',stderr:''};
|
|
53
|
+
};
|
|
54
|
+
syncBuiltinESMExports();
|
|
55
|
+
assert.deepEqual(await loadPowerShellDataFile('C:\\scratch\\controller.psd1'),{});
|
|
56
|
+
assert.ok(call,'updater must dispatch its PowerShell subprocess');
|
|
57
|
+
assert.equal(call.file,'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe');
|
|
58
|
+
assert.equal(call.options.env.SystemRoot,'C:\\Windows');
|
|
59
|
+
assert.equal(call.options.env.WINDIR,'C:\\Windows');
|
|
60
|
+
assert.equal(call.options.shell,undefined);
|
|
61
|
+
call=null;
|
|
62
|
+
const started=runControllerStartNoCapture({kind:'legacy',
|
|
63
|
+
controller_path:'C:\\scratch\\deadbyte-controller.ps1',cwd:'C:\\scratch'},1000);
|
|
64
|
+
assert.equal(started.status,0);
|
|
65
|
+
assert.equal(call.file,'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe');
|
|
66
|
+
assert.ok(call.args.includes('start'));
|
|
67
|
+
assert.equal(call.options.env.SystemRoot,'C:\\Windows');
|
|
68
|
+
assert.equal(call.options.env.WINDIR,'C:\\Windows');
|
|
69
|
+
}finally{
|
|
70
|
+
childProcess.spawnSync=originalSpawn;
|
|
71
|
+
syncBuiltinESMExports();
|
|
72
|
+
if(originalRoot===undefined)delete process.env.SystemRoot;
|
|
73
|
+
else process.env.SystemRoot=originalRoot;
|
|
74
|
+
if(originalWindir===undefined)delete process.env.WINDIR;
|
|
75
|
+
else process.env.WINDIR=originalWindir;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
|
|
4
|
+
import {spawnSync} from 'node:child_process';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath,pathToFileURL} from 'node:url';
|
|
8
|
+
|
|
9
|
+
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
10
|
+
const systemPowerShell='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
11
|
+
const winTest=process.platform==='win32'?test:test.skip;
|
|
12
|
+
function poisonedRuntimeImport(source,args){
|
|
13
|
+
return `process.env.SystemRoot='C:\\\\untrusted-root';process.env.WINDIR='C:\\\\untrusted-root';`+
|
|
14
|
+
`process.argv=[process.execPath,${JSON.stringify(source)},...${JSON.stringify(args)}];`+
|
|
15
|
+
`await import(${JSON.stringify(pathToFileURL(source).href)});`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
winTest('remote supervisor dispatch ignores poisoned caller Windows roots',async()=>{
|
|
19
|
+
const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r108-cli-'));
|
|
20
|
+
try{
|
|
21
|
+
await mkdir(path.join(scratch,'bin'));
|
|
22
|
+
await writeFile(path.join(scratch,'bin','DEADBYTE-MCP.ps1'),
|
|
23
|
+
"Write-Output ('R108_CONTROLLER_EXECUTED root=' + $env:SystemRoot + ' windir=' + $env:WINDIR)\nexit 0\n",{flag:'wx'});
|
|
24
|
+
const source=path.join(root,'src','deadbyte-cli.mjs');
|
|
25
|
+
const result=spawnSync(process.execPath,['--input-type=module','-e',
|
|
26
|
+
poisonedRuntimeImport(source,['supervisor','status'])],{
|
|
27
|
+
cwd:root,encoding:'utf8',timeout:15000,windowsHide:true,
|
|
28
|
+
env:{...process.env,DEADBYTE_STATE_ROOT:scratch}
|
|
29
|
+
});
|
|
30
|
+
assert.equal(result.error,undefined,String(result.error));
|
|
31
|
+
assert.equal(result.status,0,result.stderr);
|
|
32
|
+
assert.match(result.stdout,/R108_CONTROLLER_EXECUTED root=C:\\Windows windir=C:\\Windows/);
|
|
33
|
+
}finally{await rm(scratch,{recursive:true,force:true});}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
winTest('guardian startup ignores poisoned caller Windows roots before marker lookup',async()=>{
|
|
37
|
+
const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r108-guardian-'));
|
|
38
|
+
try{
|
|
39
|
+
await mkdir(path.join(scratch,'bin'));
|
|
40
|
+
await writeFile(path.join(scratch,'bin','DEADBYTE-MCP.ps1'),"exit 0\n",{flag:'wx'});
|
|
41
|
+
const source=path.join(root,'src','remote-session-guardian.mjs');
|
|
42
|
+
const result=spawnSync(process.execPath,['--input-type=module','-e',
|
|
43
|
+
poisonedRuntimeImport(source,['--state-root',scratch,'--owner-pid',String(process.pid),'--session-nonce','a'.repeat(64)])],{
|
|
44
|
+
cwd:root,encoding:'utf8',timeout:15000,windowsHide:true,
|
|
45
|
+
env:process.env
|
|
46
|
+
});
|
|
47
|
+
assert.equal(result.error,undefined,String(result.error));
|
|
48
|
+
assert.equal(result.status,0,`guardian startup failed under poisoned roots; stderr=${result.stderr}`);
|
|
49
|
+
}finally{await rm(scratch,{recursive:true,force:true});}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
winTest('trusted Windows PowerShell binary exists for remote lifecycle tests',()=>{
|
|
53
|
+
const result=spawnSync(systemPowerShell,['-NoLogo','-NoProfile','-Command','exit 0'],{
|
|
54
|
+
encoding:'utf8',timeout:15000,windowsHide:true,
|
|
55
|
+
env:{...process.env,SystemRoot:'C:\\Windows',WINDIR:'C:\\Windows'}
|
|
56
|
+
});
|
|
57
|
+
assert.equal(result.error,undefined,String(result.error));
|
|
58
|
+
assert.equal(result.status,0);
|
|
59
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {copyFile, mkdir, mkdtemp, rm} from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {observeCurrentProcessIdentity} from '../src/autonomous-local-supervisor.mjs';
|
|
7
|
+
|
|
8
|
+
test('supervisor identity observation ignores an inherited untrusted Windows root', {skip:process.platform!=='win32'}, async () => {
|
|
9
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r110-poisoned-root-'));
|
|
10
|
+
const shellDir=path.join(root,'System32','WindowsPowerShell','v1.0');
|
|
11
|
+
const priorRoot=process.env.SystemRoot;
|
|
12
|
+
const priorWindir=process.env.WINDIR;
|
|
13
|
+
try {
|
|
14
|
+
await mkdir(shellDir,{recursive:true});
|
|
15
|
+
await copyFile('C:\\Windows\\System32\\where.exe',path.join(shellDir,'powershell.exe'));
|
|
16
|
+
process.env.SystemRoot=root;
|
|
17
|
+
process.env.WINDIR=root;
|
|
18
|
+
const identity=await observeCurrentProcessIdentity();
|
|
19
|
+
assert.ok(identity,'a caller-provided Windows root must not redirect the trusted identity probe');
|
|
20
|
+
assert.equal(identity.pid,process.pid);
|
|
21
|
+
} finally {
|
|
22
|
+
if(priorRoot===undefined) delete process.env.SystemRoot;
|
|
23
|
+
else process.env.SystemRoot=priorRoot;
|
|
24
|
+
if(priorWindir===undefined) delete process.env.WINDIR;
|
|
25
|
+
else process.env.WINDIR=priorWindir;
|
|
26
|
+
await rm(root,{recursive:true,force:true});
|
|
27
|
+
}
|
|
28
|
+
});
|