deadbyte-mcp 0.15.44 → 0.15.46
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 +86 -138
- package/PROVENANCE.json +35 -18
- package/README.txt +22 -21
- package/RELEASE.json +369 -334
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +27 -17
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +1871 -1769
- package/controller/deadbyte-desktop-policy.json +6 -6
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R68-COMPAT-HOST-EXEC-CANDIDATE.md +63 -0
- package/docs/R69-LIVE-OUTCOME-R70-POLICY-TRANSFER.md +136 -0
- package/docs/R70-CODING-DIGEST-SUPERVISOR-CHECKPOINT-20261013.md +42 -0
- package/docs/R70-FINITE-GRANT-OWNER-BOUNDARY-20261013.md +80 -0
- package/docs/R70-OWNER-BOOTSTRAP-CHECKPOINT-20261013.md +60 -0
- package/docs/R70-REFERENCE-MECHANISMS.json +95 -0
- package/docs/R70-TRUST-GUARD-EXPIRY-CHECKPOINT-20261013.md +47 -0
- package/package.json +2 -2
- package/scripts/benchmark-l0-r71.mjs +76 -0
- package/scripts/gate-windows-r71.ps1 +64 -0
- package/scripts/gate-windows-r72.ps1 +76 -0
- package/scripts/npm-release-r48.mjs +4 -0
- package/scripts/npm-release-readiness-r71.mjs +21 -0
- package/scripts/npm-release-readiness-r72.mjs +76 -0
- package/scripts/verify-r70-full-regression.ps1 +72 -0
- package/src/authority-continuity-r65.mjs +28 -8
- package/src/autonomous-mcp-tools.mjs +1 -1
- package/src/autonomous-output-contracts.mjs +4 -1
- package/src/autonomous-policy.mjs +54 -7
- package/src/capability-ledger.mjs +1 -1
- package/src/capability-resolver.mjs +2 -1
- package/src/commander-compat-runtime.mjs +30 -2
- package/src/deadbyte-cli.mjs +722 -719
- package/src/launcher-isolation.mjs +58 -0
- package/src/native-diff-v2.mjs +18 -5
- package/src/native-diff.mjs +21 -5
- package/src/r70-owner-approval.mjs +78 -0
- package/src/remote-authority.mjs +75 -75
- package/src/remote-live-log.mjs +48 -14
- package/src/remote-mcp-machine-smoke-client.mjs +84 -84
- package/src/remote-result-journal.mjs +1 -1
- package/src/runtime-update.mjs +232 -24
- package/src/staging-input-isolation.mjs +79 -0
- package/src/tool-audit.mjs +2 -0
- package/src/version.mjs +1 -1
- package/test/autonomous-local-supervisor.test.mjs +2 -1
- package/test/autonomous-output-contract.test.mjs +17 -1
- package/test/autonomous-supervisor-integration.test.mjs +2 -1
- package/test/capability-resolver.test.mjs +17 -0
- package/test/codex-autonomous-r44.test.mjs +1 -1
- package/test/commander-compat-runtime.test.mjs +42 -0
- package/test/controller-policy-preflight-r59.test.mjs +1 -0
- package/test/deferred-slot-operation-behavior.test.mjs +2 -2
- package/test/native-diff-topology-r36.test.mjs +35 -2
- package/test/native-diff.test.mjs +83 -3
- package/test/r58-cleanup-retention.test.mjs +3 -3
- package/test/r58-concurrent-recovery.test.mjs +3 -3
- package/test/r58-stale-owner.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +9 -5
- package/test/r60-authority-coherence.test.mjs +10 -2
- package/test/r65-authority-runtime-epoch.test.mjs +9 -1
- package/test/r67-isolated-slot-rollback-restore.test.mjs +2 -1
- package/test/r67-npm-publish-gate.test.mjs +4 -2
- package/test/r67-release-contract.test.mjs +8 -7
- package/test/r69-live-outcome.test.mjs +137 -0
- package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
- package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
- package/test/r70-controller-autonomous-root.test.mjs +104 -0
- package/test/r70-controller-grant-subset.test.mjs +28 -0
- package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
- package/test/r70-controller-policy-snapshot.test.mjs +66 -0
- package/test/r70-owner-approval.test.mjs +67 -0
- package/test/r71-full-verifier-artifact.test.mjs +28 -0
- package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
- package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
- package/test/r71-publish-fence.test.mjs +38 -0
- package/test/r71-release-identity.test.mjs +33 -0
- package/test/r72-launcher-isolation.test.mjs +99 -0
- package/test/r72-publication-fence.test.mjs +56 -0
- package/test/r72-publication-output-integrity.test.mjs +110 -0
- package/test/r72-remote-safe-contract.test.mjs +101 -0
- package/test/r72-staging-input-isolation.test.mjs +87 -0
- package/test/r72-staging-workspace-isolation.test.mjs +114 -0
- package/test/remote-result-journal.test.mjs +12 -0
- package/test/remote-session-cli.test.mjs +112 -65
- package/test/standing-grants.test.mjs +2 -2
- package/test/tool-audit.test.mjs +14 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-native-inspect-r67.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/conpty-input-observer.obj +0 -0
- package/miniconpty.obj +0 -0
- package/proof/CONTAINMENT-BUILD.txt +0 -163
- package/proof/MCP-SMOKE-STATUS.txt +0 -9
- package/proof/NODE-SYNTAX.txt +0 -4
- package/proof/PROOF.txt +0 -6
- package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
- package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
- package/proof/SECRET-SCAN.txt +0 -4
- package/proof/SOURCE-GATES.txt +0 -16
- package/proof/STATUS.txt +0 -11
- package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
- package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
- package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
- package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
- package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
- package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
- package/proof/TESTS.txt +0 -686
- package/proof/TRUST-EPHEMERAL.txt +0 -4
- package/proof/V03-FREEZE.txt +0 -27
- package/proof/V041-IPC-HOTFIX.txt +0 -16
- package/proof/V042-ENV-HOTFIX.txt +0 -15
- package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
- package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
- package/proof/V045-OPERATION-REGISTRY.txt +0 -7
- package/proof/V046-WORKER-REGISTRY.txt +0 -12
- package/proof/V047-WORKER-MANIFEST.txt +0 -18
- package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
- package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
- package/proof/V070-NODE-SYNTAX.txt +0 -36
- package/proof/V070-NODE-TESTS.txt +0 -752
- package/proof/V070-PACK-DRY-RUN.txt +0 -126
- package/proof/V070-SOURCE-GATE.txt +0 -30
- package/proof/V070-STATIC-SCAN.txt +0 -4
- package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
- package/proof/V071-NODE-SYNTAX.txt +0 -1
- package/proof/V071-NODE-TESTS.txt +0 -796
- package/proof/V071-PACK-DRY-RUN.json +0 -656
- package/proof/V071-SOURCE-GATE.txt +0 -16
- package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
- package/proof/V073-NODE-SYNTAX.txt +0 -4
- package/proof/V073-SOURCE-GATE.txt +0 -21
- package/proof/V073-STATIC-SCAN.txt +0 -10
- package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
- package/proof/VERIFY.txt +0 -7
- package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
- package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
- package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
- package/proof/commander-closure-20260927/RESULT.json +0 -30
- package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
- package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
- package/proof/r59-full-regression-current.json +0 -13
- package/proof/r59-full-regression-current.stderr.log +0 -0
- package/proof/r59-full-regression-current.stdout.log +0 -1268
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {test} from 'node:test';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,writeFile,readFile,rm,symlink,rename} from 'node:fs/promises';
|
|
5
|
+
import {tmpdir} from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {assertR72NpmPublishReady} from '../scripts/npm-release-readiness-r72.mjs';
|
|
8
|
+
|
|
9
|
+
const sha = bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
const gates = ['full_regression','windows_lifecycle','package_acceptance','independent_judge'];
|
|
11
|
+
const parent = {
|
|
12
|
+
version:'0.15.45',release_id:'v0.15.45-34aa8d1241cfb8bf',
|
|
13
|
+
manifest_sha256:'34aa8d1241cfb8bf0b5ba303bf3c06e0e9e1bf01f64457bca659d6dd4d634fdc',
|
|
14
|
+
generation_manifest_sha256:'ff2b73410062688651abd7bc2dd7b2b8c87810a28e3808bfe1f4352a25b0e6e4'
|
|
15
|
+
};
|
|
16
|
+
const release={version:'0.15.46',name:'deadbyte-mcp',train:'R72',parent,runtime_predecessor:parent,r72_hotfix:{
|
|
17
|
+
status:'CANDIDATE_SEALED',windows_regression:'PASS',package_acceptance:'PASS',manifest:'PASS',
|
|
18
|
+
remote_lifecycle:'PASS',security_gate:'PASS',independent_judge:'PASS'
|
|
19
|
+
}};
|
|
20
|
+
|
|
21
|
+
async function fixture(){
|
|
22
|
+
const root=await mkdtemp(path.join(tmpdir(),'r72-fence-integrity-'));
|
|
23
|
+
const evidence=path.join(root,'testing','r72-release-evidence');
|
|
24
|
+
const outputs=path.join(evidence,'outputs');
|
|
25
|
+
await mkdir(outputs,{recursive:true});
|
|
26
|
+
const manifest=Buffer.from('synthetic test-only manifest, not a real release\n');
|
|
27
|
+
await writeFile(path.join(root,'MANIFEST.SHA256'),manifest);
|
|
28
|
+
const index={schema:'deadbyte.r72-release-evidence-index.v1',version:'0.15.46',parent,
|
|
29
|
+
manifest_sha256:sha(manifest),gate_sha256:{}};
|
|
30
|
+
const outputPaths={};
|
|
31
|
+
for(const gate of gates){
|
|
32
|
+
const output=Buffer.from(`SYNTHETIC-TEST-ONLY\nGATE=${gate}\nEXIT=0\n`);
|
|
33
|
+
const outputPath=path.join(outputs,`${gate}.log`);
|
|
34
|
+
outputPaths[gate]=outputPath;
|
|
35
|
+
await writeFile(outputPath,output);
|
|
36
|
+
const receipt={schema:'deadbyte.r72-gate-result.v1',gate,version:'0.15.46',
|
|
37
|
+
manifest_sha256:index.manifest_sha256,status:'PASS',exit_code:0,
|
|
38
|
+
output_file:`outputs/${gate}.log`,output_sha256:sha(output),
|
|
39
|
+
...(gate==='full_regression'?{pass_count:1,fail_count:0}:{}),
|
|
40
|
+
...(gate==='windows_lifecycle'?{safe_default_arm_count:0,operator_owner_consent_enforced:true,
|
|
41
|
+
stale_owner_recovery_verified:true,signed_epoch_fail_closed:true}:{}),
|
|
42
|
+
...(gate==='independent_judge'?{independent_verification:'PASS'}:{})};
|
|
43
|
+
const bytes=Buffer.from(JSON.stringify(receipt)+'\n');
|
|
44
|
+
index.gate_sha256[gate]=sha(bytes);
|
|
45
|
+
await writeFile(path.join(evidence,`${gate}.json`),bytes);
|
|
46
|
+
}
|
|
47
|
+
await writeFile(path.join(evidence,'ready.json'),JSON.stringify(index)+'\n');
|
|
48
|
+
return {root,evidence,outputPaths,dispose:()=>rm(root,{recursive:true,force:true})};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test('synthetic complete fixture validates receipt/output bytes linkage (not real release evidence)',async()=>{
|
|
52
|
+
const f=await fixture();try{
|
|
53
|
+
const result=await assertR72NpmPublishReady(release,{root:f.root});
|
|
54
|
+
assert.equal(result.status,'R72_RELEASE_READY_EVIDENCE_BOUND');
|
|
55
|
+
} finally {await f.dispose();}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('mutating full regression output, without changing receipt, rejects',async()=>{
|
|
59
|
+
const f=await fixture();try{
|
|
60
|
+
await writeFile(f.outputPaths.full_regression,'tampered output\n');
|
|
61
|
+
await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/output.*(mismatch|tamper|hash)/i);
|
|
62
|
+
} finally {await f.dispose();}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('missing Windows lifecycle output rejects even with intact receipt',async()=>{
|
|
66
|
+
const f=await fixture();try{
|
|
67
|
+
await rm(f.outputPaths.windows_lifecycle);
|
|
68
|
+
await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/output.*(missing|invalid|unavailable)/i);
|
|
69
|
+
} finally {await f.dispose();}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('receipt pointing to other output file rejects even with recomputed index SHA',async()=>{
|
|
73
|
+
const f=await fixture();try{
|
|
74
|
+
const gate='package_acceptance',receiptPath=path.join(f.evidence,`${gate}.json`);
|
|
75
|
+
const receipt=JSON.parse(await readFile(receiptPath,'utf8'));
|
|
76
|
+
receipt.output_file='outputs/full_regression.log';
|
|
77
|
+
const content=Buffer.from(JSON.stringify(receipt)+'\n');
|
|
78
|
+
await writeFile(receiptPath,content);
|
|
79
|
+
const indexPath=path.join(f.evidence,'ready.json');
|
|
80
|
+
const index=JSON.parse(await readFile(indexPath,'utf8'));
|
|
81
|
+
index.gate_sha256[gate]=sha(content);
|
|
82
|
+
await writeFile(indexPath,JSON.stringify(index)+'\n');
|
|
83
|
+
await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/output.*path/i);
|
|
84
|
+
} finally {await f.dispose();}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('symlinked evidence output or junction directory must not be trusted',async()=>{
|
|
88
|
+
const f=await fixture();try{
|
|
89
|
+
const target=f.outputPaths.independent_judge,other=path.join(f.root,'external.log');
|
|
90
|
+
await writeFile(other,await readFile(target));
|
|
91
|
+
await rm(target);
|
|
92
|
+
try {await symlink(other,target,'file');}
|
|
93
|
+
catch(e){
|
|
94
|
+
if(!['EPERM','EACCES','ENOTSUP'].includes(e.code))throw e;
|
|
95
|
+
// Windows can deny file symlinks while permitting unprivileged directory junctions.
|
|
96
|
+
const outputs=path.join(f.evidence,'outputs');
|
|
97
|
+
const externalOutputs=path.join(f.root,'external-outputs');
|
|
98
|
+
await rename(outputs,externalOutputs);
|
|
99
|
+
await symlink(externalOutputs,outputs,'junction');
|
|
100
|
+
}
|
|
101
|
+
await assert.rejects(assertR72NpmPublishReady(release,{root:f.root}),/(output.*(regular|symlink)|evidence\/output directory is not a real directory)/i);
|
|
102
|
+
} finally {await f.dispose();}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('missing status seal cannot be bypassed by synthetic receipts',async()=>{
|
|
106
|
+
const f=await fixture();try{
|
|
107
|
+
const uns={...release,r72_hotfix:{...release.r72_hotfix,status:'CANDIDATE_UNSEALED'}};
|
|
108
|
+
await assert.rejects(assertR72NpmPublishReady(uns,{root:f.root}),/CANDIDATE_SEALED/);
|
|
109
|
+
} finally {await f.dispose();}
|
|
110
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,readFile,rm,writeFile,access} 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 {authorityVector} from '../src/remote-authority.mjs';
|
|
8
|
+
|
|
9
|
+
const controllerPath=new URL('../controller/deadbyte-controller.ps1',import.meta.url);
|
|
10
|
+
const quote=value=>`'${String(value).replaceAll("'","''")}'`;
|
|
11
|
+
const winTest=process.platform==='win32'?test:test.skip;
|
|
12
|
+
|
|
13
|
+
// The legacy elevated contract is still available, but must be explicitly selected.
|
|
14
|
+
test('R72 default remote asks for no standing authority',()=>{
|
|
15
|
+
assert.deepEqual(authorityVector('default'),{
|
|
16
|
+
process:false,coding:false,autonomous:false,desktop:false,host:false
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
test('R72 operator retains explicit process/coding/autonomous and no host/desktop',()=>{
|
|
20
|
+
assert.deepEqual(authorityVector('operator'),{
|
|
21
|
+
process:true,coding:true,autonomous:true,desktop:false,host:false
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
test('R72 desktop/host/full remain explicit elevated choices',()=>{
|
|
25
|
+
for(const [mode,desktop,host] of [['desktop',true,false],['host',false,true],['full',true,true]]){
|
|
26
|
+
assert.deepEqual(authorityVector(mode),{
|
|
27
|
+
process:true,coding:true,autonomous:true,desktop,host
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
assert.throws(()=>authorityVector('invalid'),/mode invalid/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
async function controllerScenario({grant=false,escape=false,epochFails=false}={}){
|
|
34
|
+
const temp=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r72-revocation-'));
|
|
35
|
+
const runtime=path.join(temp,'runtime');
|
|
36
|
+
const file=path.join(temp,'autonomous.json');
|
|
37
|
+
const grantFile=path.join(escape?temp:runtime,'autonomous-grant.json');
|
|
38
|
+
const epochTrace=path.join(temp,'epoch-trace.txt');
|
|
39
|
+
await mkdir(runtime,{recursive:true});
|
|
40
|
+
await writeFile(file,JSON.stringify({
|
|
41
|
+
schema:'deadbyte.autonomous-policy.v1',enabled:true,
|
|
42
|
+
lease_file:grantFile,coding_policy_sha256:'a'.repeat(64),root_ids:['core']
|
|
43
|
+
}));
|
|
44
|
+
if(grant)await writeFile(grantFile,'fixture-grant');
|
|
45
|
+
const source=await readFile(controllerPath,'utf8');
|
|
46
|
+
const begin=source.indexOf('function Get-AutonomousRevocationLeaseFile(');
|
|
47
|
+
const end=source.indexOf('function Show-AutonomousStatus(',begin);
|
|
48
|
+
const readBegin=source.indexOf('function Read-ControllerPolicySnapshot(');
|
|
49
|
+
const readEnd=source.indexOf('function Get-CodingPolicyMetadata(',readBegin);
|
|
50
|
+
assert.ok(begin>=0&&end>begin&&readBegin>=0&&readEnd>readBegin);
|
|
51
|
+
const script=`$ErrorActionPreference='Stop';
|
|
52
|
+
$RuntimeDir=${quote(runtime)};
|
|
53
|
+
function Fail([string]$m){throw $m};
|
|
54
|
+
function Get-AutonomousPolicyMetadata($Cfg){throw 'ISSUANCE_PATH_MUST_NOT_RUN'};
|
|
55
|
+
function Invoke-AuthorityEpochCommand($Cfg,[string]$Plane,[string]$Action,[string]$GrantFile){
|
|
56
|
+
[IO.File]::AppendAllText(${quote(epochTrace)},("$Plane/$Action"+[Environment]::NewLine));
|
|
57
|
+
${epochFails?"throw 'FIXTURE_EPOCH_FAILED'":"return 'FIXTURE_SIGNED_REVOKE'"}
|
|
58
|
+
};
|
|
59
|
+
${source.slice(readBegin,readEnd)};
|
|
60
|
+
${source.slice(begin,end)};
|
|
61
|
+
try { Disarm-Autonomous @{AutonomousPolicyFile=${quote(file)}};Write-Output 'REVOKE_OK' }
|
|
62
|
+
catch {Write-Output ('REVOKE_FAILED: '+$_.Exception.Message);exit 7}
|
|
63
|
+
`;
|
|
64
|
+
const result=spawnSync('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-Command',script],{
|
|
65
|
+
encoding:'utf8',timeout:15000,maxBuffer:1024*1024
|
|
66
|
+
});
|
|
67
|
+
const remaining=await access(grantFile).then(()=>true,()=>false);
|
|
68
|
+
const epoch=await readFile(epochTrace,'utf8').catch(error=>{
|
|
69
|
+
if(error?.code==='ENOENT')return '';throw error;
|
|
70
|
+
});
|
|
71
|
+
await rm(temp,{recursive:true,force:true});
|
|
72
|
+
return {result,remaining,epoch};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
winTest('R72 disarm with broad Coding roots and no Autonomous grant succeeds without grant issuance',async()=>{
|
|
76
|
+
const {result,remaining,epoch}=await controllerScenario();
|
|
77
|
+
assert.equal(result.status,0,result.stderr+'\n'+result.stdout);
|
|
78
|
+
assert.match(result.stdout,/REVOKE_OK/);
|
|
79
|
+
assert.equal(remaining,false);
|
|
80
|
+
assert.equal(epoch,'');
|
|
81
|
+
});
|
|
82
|
+
winTest('R72 present Autonomous grant is sent to epoch revoker before removal',async()=>{
|
|
83
|
+
const {result,remaining,epoch}=await controllerScenario({grant:true});
|
|
84
|
+
assert.equal(result.status,0,result.stderr+'\n'+result.stdout);
|
|
85
|
+
assert.match(epoch,/autonomous\/user-revoke/);
|
|
86
|
+
assert.equal(remaining,false);
|
|
87
|
+
});
|
|
88
|
+
winTest('R72 external grant path is rejected without touching the file',async()=>{
|
|
89
|
+
const {result,remaining,epoch}=await controllerScenario({grant:true,escape:true});
|
|
90
|
+
assert.notEqual(result.status,0);
|
|
91
|
+
assert.match(result.stdout,/must live under controller runtime directory/);
|
|
92
|
+
assert.equal(remaining,true);
|
|
93
|
+
assert.equal(epoch,'');
|
|
94
|
+
});
|
|
95
|
+
winTest('R72 epoch revocation error remains a failure even after file removal',async()=>{
|
|
96
|
+
const {result,remaining,epoch}=await controllerScenario({grant:true,epochFails:true});
|
|
97
|
+
assert.notEqual(result.status,0);
|
|
98
|
+
assert.match(result.stdout,/FIXTURE_EPOCH_FAILED/);
|
|
99
|
+
assert.match(epoch,/autonomous\/user-revoke/);
|
|
100
|
+
assert.equal(remaining,false);
|
|
101
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {test} from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,rm,writeFile,symlink,readFile} from 'node:fs/promises';
|
|
4
|
+
import {tmpdir,homedir} from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {assertStageOwnedPath,assertStageNoProductionPath,assertStagingBootstrapInputs,assertStagingControlConfig} from '../src/staging-input-isolation.mjs';
|
|
7
|
+
const usingStage=async fn=>{const dir=await mkdtemp(path.join(tmpdir(),'r72-g2-w2-'));try{const state=path.join(dir,'isolated-state');await mkdir(state);await fn({dir,state});}finally{await rm(dir,{recursive:true,force:true});}};
|
|
8
|
+
const cfg=state=>({
|
|
9
|
+
CoreRoot:path.join(state,'releases','predecessor'),WorkspaceRoot:path.join(state,'workspaces'),
|
|
10
|
+
CodingPolicyFile:path.join(state,'policies','coding.json'),AutonomousPolicyFile:path.join(state,'policies','autonomous.json'),
|
|
11
|
+
HostPolicyFile:path.join(state,'policies','host.json'),ProcessPolicyFile:path.join(state,'policies','process.json'),
|
|
12
|
+
DesktopPolicyFile:path.join(state,'policies','desktop.json'),
|
|
13
|
+
Bridge:{FilePath:path.join(state,'bin','node.exe'),WorkingDirectory:path.join(state,'bridge'),ScriptPath:path.join(state,'bridge','src','bridge.mjs'),Dependencies:[{Name:'bridge-local',Path:path.join(state,'bridge','src','helper.mjs')}]},
|
|
14
|
+
Cloud:{FilePath:path.join(state,'bin','tunnel.exe'),WorkingDirectory:path.join(state,'releases','predecessor'),Dependencies:[{Name:'cloudflared',Path:path.join(path.parse(state).root,'opt','cloudflared.exe')},{Name:'tunnel-token',Path:path.join(state,'trust','cloud-token.txt')}]}
|
|
15
|
+
});
|
|
16
|
+
test('G2-W2 accepts stage-owned policy, Bridge and Cloud config paths',async()=>usingStage(async({state})=>{
|
|
17
|
+
assert.doesNotThrow(()=>assertStagingControlConfig(cfg(state),state,{configPath:path.join(state,'generations','config.psd1')}));
|
|
18
|
+
}));
|
|
19
|
+
test('G2-W2 rejects each external controller policy path before policy read',async()=>usingStage(async({dir,state})=>{
|
|
20
|
+
for(const key of ['CodingPolicyFile','AutonomousPolicyFile','HostPolicyFile','ProcessPolicyFile','DesktopPolicyFile']){
|
|
21
|
+
const c=cfg(state);c[key]=path.join(dir,'outside.json');
|
|
22
|
+
assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),new RegExp('STAGING_EXTERNAL_INPUT: '+key));
|
|
23
|
+
}
|
|
24
|
+
}));
|
|
25
|
+
test('G2-W2 rejects external controller config before PowerShell Import-PowerShellDataFile',async()=>usingStage(async({dir,state})=>{
|
|
26
|
+
assert.throws(()=>assertStageOwnedPath(path.join(dir,'production','config.psd1'),state,'controller config file'),/STAGING_EXTERNAL_INPUT/);
|
|
27
|
+
}));
|
|
28
|
+
test('G2-W2 rejects bridge and Cloud external working directories, dependencies and executables',async()=>usingStage(async({dir,state})=>{
|
|
29
|
+
for(const [component,key] of [['Bridge','WorkingDirectory'],['Bridge','ScriptPath'],['Cloud','WorkingDirectory']]){
|
|
30
|
+
const c=cfg(state);c[component][key]=path.join(dir,'foreign','escape.exe');
|
|
31
|
+
assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_EXTERNAL_INPUT/);
|
|
32
|
+
}
|
|
33
|
+
const c=cfg(state);c.Bridge.Dependencies[0].Path=path.join(dir,'foreign.js');
|
|
34
|
+
assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_EXTERNAL_INPUT/);
|
|
35
|
+
}));
|
|
36
|
+
test('G2-W2 permits a shared non-production executable but forbids a production-state executable',async()=>usingStage(async({dir,state})=>{
|
|
37
|
+
const c=cfg(state);c.Bridge.FilePath=path.join(dir,'system-node.exe');
|
|
38
|
+
assert.doesNotThrow(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}));
|
|
39
|
+
c.Bridge.FilePath=path.join(homedir(),'.deadbyte-mcp','bin','node.exe');
|
|
40
|
+
assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_PRODUCTION_INPUT/);
|
|
41
|
+
}));
|
|
42
|
+
test('G2-W2 rejects external cloud credential in existing config',async()=>usingStage(async({dir,state})=>{
|
|
43
|
+
const c=cfg(state);c.Cloud.Dependencies[1].Path=path.join(dir,'production','token.txt');
|
|
44
|
+
assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_EXTERNAL_INPUT/);
|
|
45
|
+
}));
|
|
46
|
+
test('G2-W2 rejects external policy override before state mutation',async()=>usingStage(async({dir,state})=>{
|
|
47
|
+
for(const key of ['codingPolicyBootstrap','autonomousPolicyBootstrap','hostPolicyBootstrap','processPolicyBootstrap','desktopPolicyBootstrap','ownerApprovalBootstrap','semanticLspBootstrap']){
|
|
48
|
+
assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,[key]:{path:path.join(dir,'outside.json'),sha256:'a'.repeat(64)}}),/STAGING_EXTERNAL_INPUT/);
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
test('G2-W2 rejects production legacy --source-root and Bridge bootstrap before state mutation',async()=>usingStage(async({dir,state})=>{
|
|
52
|
+
assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,sourceRoot:path.join(dir,'legacy-core')}),/STAGING_EXTERNAL_INPUT/);
|
|
53
|
+
assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,bridgeBootstrap:{root:path.join(dir,'bridge'),scriptSha256:'a'.repeat(64)}}),/STAGING_EXTERNAL_INPUT/);
|
|
54
|
+
}));
|
|
55
|
+
test('G2-W2 requires named-tunnel token and working directory under stage state',async()=>usingStage(async({dir,state})=>{
|
|
56
|
+
const c={cloudflared:{path:path.join(dir,'nonprod-cloudflared.exe')},tokenFile:{path:path.join(state,'trust','token.txt')},childWorkingDirectory:path.join(state,'cloud')};
|
|
57
|
+
assert.doesNotThrow(()=>assertStagingBootstrapInputs({stateRoot:state,cloudTransport:c}));
|
|
58
|
+
assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,cloudTransport:{...c,tokenFile:{path:path.join(dir,'external-token.txt')}}}),/STAGING_EXTERNAL_INPUT/);
|
|
59
|
+
assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,cloudTransport:{...c,childWorkingDirectory:path.join(dir,'foreign')}}),/STAGING_EXTERNAL_INPUT/);
|
|
60
|
+
}));
|
|
61
|
+
test('G2-W2 prohibits production config, token and cloudflared even when explicitly pinned',async()=>usingStage(async({state})=>{
|
|
62
|
+
const prod=path.join(homedir(),'.deadbyte-mcp');
|
|
63
|
+
assert.throws(()=>assertStageOwnedPath(path.join(prod,'trust','update-key.json'),state,'production key'),/STAGING_EXTERNAL_INPUT/);
|
|
64
|
+
assert.throws(()=>assertStageNoProductionPath(path.join(prod,'bin','cloudflared.exe'),state,'cloudflared'),/STAGING_PRODUCTION_INPUT/);
|
|
65
|
+
}));
|
|
66
|
+
test('G2-W2 rejects staging nested in production root',()=>{
|
|
67
|
+
const prod=path.join(homedir(),'.deadbyte-mcp');
|
|
68
|
+
assert.throws(()=>assertStageOwnedPath(path.join(prod,'stage','policies','coding.json'),path.join(prod,'stage'),'policy'),/STAGING_STATE_OVERLAPS_PRODUCTION/);
|
|
69
|
+
});
|
|
70
|
+
test('G2-W2 allows production legacy policy paths without modifying production data',async()=>usingStage(async({dir})=>{
|
|
71
|
+
const production=path.join(homedir(),'.deadbyte-mcp');
|
|
72
|
+
const c=cfg(path.join(dir,'separate-stage'));
|
|
73
|
+
assert.doesNotThrow(()=>assertStagingControlConfig(c,production,{configPath:path.join(dir,'external.psd1')}));
|
|
74
|
+
assert.doesNotThrow(()=>assertStagingBootstrapInputs({stateRoot:production,sourceRoot:dir,cloudTransport:{tokenFile:{path:dir}}}));
|
|
75
|
+
}));
|
|
76
|
+
test('G2-W2 rejects a symlink path component in an otherwise owned policy path',async t=>usingStage(async({dir,state})=>{
|
|
77
|
+
const external=path.join(dir,'external');await mkdir(external);
|
|
78
|
+
try{await symlink(external,path.join(state,'linked'),process.platform==='win32'?'junction':'dir');}
|
|
79
|
+
catch(error){if(['EPERM','EACCES','ENOSYS'].includes(error.code)){t.diagnostic('symlink privilege not present');return;}throw error;}
|
|
80
|
+
const c=cfg(state);c.CodingPolicyFile=path.join(state,'linked','coding.json');
|
|
81
|
+
assert.throws(()=>assertStagingControlConfig(c,state,{configPath:path.join(state,'config.psd1')}),/STAGING_REPARSE_INPUT/);
|
|
82
|
+
}));
|
|
83
|
+
test('G2-W2 rejection never modifies foreign source bytes',async()=>usingStage(async({dir,state})=>{
|
|
84
|
+
const foreign=path.join(dir,'foreign.json');await writeFile(foreign,'private-boundary-marker');
|
|
85
|
+
assert.throws(()=>assertStagingBootstrapInputs({stateRoot:state,codingPolicyBootstrap:{path:foreign}}),/STAGING_EXTERNAL_INPUT/);
|
|
86
|
+
assert.equal(await readFile(foreign,'utf8'),'private-boundary-marker');
|
|
87
|
+
}));
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import {test} from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,rm,symlink} from 'node:fs/promises';
|
|
4
|
+
import {tmpdir,homedir} from 'node:os';
|
|
5
|
+
import {existsSync, lstatSync} from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {workspacePlan} from '../src/runtime-update.mjs';
|
|
8
|
+
|
|
9
|
+
const stage = async fn => {
|
|
10
|
+
const dir=await mkdtemp(path.join(tmpdir(),'r72-g2-workspace-'));
|
|
11
|
+
try {await fn(dir,path.join(dir,'stage-state'));}
|
|
12
|
+
finally {await rm(dir,{recursive:true,force:true});}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
test('staging rejects external existing workspace before any rename can be planned', async()=>{
|
|
16
|
+
await stage(async(base,state)=>{
|
|
17
|
+
const foreign=path.join(base,'production-workspaces');
|
|
18
|
+
await mkdir(foreign);
|
|
19
|
+
await assert.rejects(workspacePlan(foreign,state), /STAGING_WORKSPACE_OUTSIDE_STATE/);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('staging rejects production-managed workspace path even if not present', async()=>{
|
|
24
|
+
await stage(async(base,state)=>{
|
|
25
|
+
const prod=path.join(homedir(),'.deadbyte-mcp','workspaces');
|
|
26
|
+
await assert.rejects(workspacePlan(prod,state), /STAGING_WORKSPACE_OUTSIDE_STATE/);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('staging permits a stage-owned legacy workspace and preserves plan-only semantics', async()=>{
|
|
31
|
+
await stage(async(base,state)=>{
|
|
32
|
+
const old=path.join(state,'legacy-workspaces');
|
|
33
|
+
await mkdir(old,{recursive:true});
|
|
34
|
+
const planned=await workspacePlan(old,state);
|
|
35
|
+
assert.equal(planned.mode,'rename');
|
|
36
|
+
assert.equal(planned.target,path.join(state,'workspaces'));
|
|
37
|
+
assert.equal(planned.moved,false);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('staging rejects a workspace equal to state root', async()=>{
|
|
42
|
+
await stage(async(base,state)=>{
|
|
43
|
+
await mkdir(state);
|
|
44
|
+
await assert.rejects(workspacePlan(state,state), /STAGING_WORKSPACE_STATE_ROOT/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('production keeps its established external-workspace migration behavior', async()=>{
|
|
49
|
+
await stage(async(base)=>{
|
|
50
|
+
const prod=path.join(homedir(),'.deadbyte-mcp');
|
|
51
|
+
const stable=path.join(prod,'workspaces');
|
|
52
|
+
const external=path.join(base,'legacy-workspaces');
|
|
53
|
+
await mkdir(external);
|
|
54
|
+
// Observe real target state read-only: production might already contain workspaces.
|
|
55
|
+
if(existsSync(stable)){
|
|
56
|
+
const info=lstatSync(stable);
|
|
57
|
+
if(!info.isDirectory()||info.isSymbolicLink()){
|
|
58
|
+
await assert.rejects(workspacePlan(external,prod),/stable workspace root must be a real directory/);
|
|
59
|
+
}else{
|
|
60
|
+
await assert.rejects(workspacePlan(external,prod),/workspace migration conflict: both source and stable target exist/);
|
|
61
|
+
}
|
|
62
|
+
}else{
|
|
63
|
+
const result=await workspacePlan(external,prod);
|
|
64
|
+
assert.equal(result.source,external);
|
|
65
|
+
assert.equal(result.target,stable);
|
|
66
|
+
assert.equal(result.mode,'rename');
|
|
67
|
+
assert.equal(result.moved,false);
|
|
68
|
+
}
|
|
69
|
+
// Planning must never rename or remove the legacy source.
|
|
70
|
+
assert.equal(existsSync(external),true);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('staging refuses workspace path through symlink or junction', async(t)=>{
|
|
75
|
+
await stage(async(base,state)=>{
|
|
76
|
+
const real=path.join(base,'foreign');await mkdir(real);
|
|
77
|
+
const link=path.join(state,'link');await mkdir(state);
|
|
78
|
+
try{await symlink(real,link,process.platform==='win32'?'junction':'dir');}
|
|
79
|
+
catch(e){if(['EPERM','EACCES','ENOSYS'].includes(e.code)){t.diagnostic('symlink creation not permitted');return;}throw e;}
|
|
80
|
+
await assert.rejects(workspacePlan(link,state), /STAGING_WORKSPACE_REPARSE/);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('staging refuses reserved trust, release and runtime trees as workspace sources',async()=>{
|
|
85
|
+
await stage(async(base,state)=>{
|
|
86
|
+
for(const child of ['trust','releases','runtime','bin','evidence']){
|
|
87
|
+
const source=path.join(state,child,'workspace');
|
|
88
|
+
await assert.rejects(workspacePlan(source,state),/STAGING_WORKSPACE_RESERVED_STATE/);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('workspace apply rejects a newly introduced target junction/symlink',async(t)=>{
|
|
94
|
+
await stage(async(base,state)=>{
|
|
95
|
+
const old=path.join(state,'old-workspaces');await mkdir(old,{recursive:true});
|
|
96
|
+
const plan=await workspacePlan(old,state);
|
|
97
|
+
const external=path.join(base,'foreign');await mkdir(external);
|
|
98
|
+
try{await symlink(external,path.join(state,'workspaces'),process.platform==='win32'?'junction':'dir');}
|
|
99
|
+
catch(e){if(['EPERM','EACCES','ENOSYS'].includes(e.code)){t.diagnostic('symlink creation not permitted');return;}throw e;}
|
|
100
|
+
const {applyWorkspacePlan}=await import('../src/runtime-update.mjs');
|
|
101
|
+
await assert.rejects(applyWorkspacePlan(plan),/STAGING_WORKSPACE_REPARSE/);
|
|
102
|
+
assert.equal((await import('node:fs')).existsSync(old),true);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('workspace rollback rejects a foreign workspace source without touching it',async()=>{
|
|
107
|
+
await stage(async(base,state)=>{
|
|
108
|
+
const foreign=path.join(base,'foreign');await mkdir(foreign);
|
|
109
|
+
const {rollbackWorkspacePlan}=await import('../src/runtime-update.mjs');
|
|
110
|
+
const fake={source:foreign,target:path.join(state,'workspaces'),mode:'rename',moved:true};
|
|
111
|
+
await assert.rejects(rollbackWorkspacePlan(fake),/STAGING_WORKSPACE_OUTSIDE_STATE/);
|
|
112
|
+
assert.equal((await import('node:fs')).existsSync(foreign),true);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -71,3 +71,15 @@ test('remote result journal preserves explicit unknown side-effect state across
|
|
|
71
71
|
await assert.rejects(journal.recordTerminal({...item('req-invalid',H('5'),'error'),side_effect_state:'committed'}),/side_effect_state invalid/);
|
|
72
72
|
}finally{await rm(root,{recursive:true,force:true});}
|
|
73
73
|
});
|
|
74
|
+
|
|
75
|
+
test('R69 replay retains an input-required result as pending, never ok',async()=>{
|
|
76
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r69-remote-pending-'));
|
|
77
|
+
try{
|
|
78
|
+
const file=path.join(root,'journal.json');
|
|
79
|
+
let journal=await openRemoteResultJournal({filePath:file,sessionId:session,maxEntries:8});
|
|
80
|
+
await journal.recordTerminal(item('req-input',H('6'),'pending'));
|
|
81
|
+
journal=await openRemoteResultJournal({filePath:file,sessionId:session,maxEntries:8});
|
|
82
|
+
const replay=await journal.replayAfter('r1:'+session+':0',{limit:8});
|
|
83
|
+
assert.equal(replay.events[0].status,'pending');
|
|
84
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
85
|
+
});
|