deadbyte-mcp 0.15.60 → 0.15.61
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 +29 -26
- package/PROVENANCE.json +8 -8
- package/README.txt +18 -18
- package/RELEASE.json +7 -7
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bridge/package.json +1 -1
- package/node_modules/which/CHANGELOG.md +166 -0
- package/package.json +1 -1
- package/scripts/bootstrap-uac-migration-r153.ps1 +17 -5
- package/scripts/deploy-r48.mjs +7 -6
- package/scripts/issue-uac-migration-r153.mjs +12 -5
- package/scripts/r153-prepublish-identity.mjs +3 -3
- package/scripts/request-uac-migration-r153.ps1 +17 -4
- package/scripts/verify-protected-stage-r153.mjs +1 -0
- package/src/clean-activation-recovery-r153.mjs +1 -1
- package/src/deadbyte-cli.mjs +13 -10
- package/src/host-command-pins-r154.mjs +34 -0
- package/src/uac-approval-contract-r153.mjs +6 -3
- package/src/version.mjs +1 -1
- package/test/r130-release-security-contract.test.mjs +8 -8
- package/test/r132-release-identity.test.mjs +7 -7
- package/test/r153-clean-activation-recovery.test.mjs +2 -2
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r153-r65-signed-precondition-recovery.test.mjs +2 -2
- package/test/r153-staging-leaf-acl.test.mjs +19 -8
- package/test/r153-uac-approval-contract.test.mjs +5 -3
- package/test/r153-uac-launch-parameter-contract.test.mjs +1 -1
- package/test/r154-multipin-recovery.test.mjs +45 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
package/scripts/deploy-r48.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {proveLegacyOfflineScopeRepair} from '../src/legacy-offline-policy-proof-
|
|
|
15
15
|
import {claimWithOneTimeMigration,verifyNarrowedPolicyGeneration} from '../src/clean-activation-recovery-r153.mjs';
|
|
16
16
|
import {verifyUacApproval,uacPointerSha256,protectedUacReceiptPath,requireTrustedUacInstallation} from '../src/uac-approval-contract-r153.mjs';
|
|
17
17
|
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
18
|
+
import {normalizeHostCommandPins,hostCommandPinArgs} from '../src/host-command-pins-r154.mjs';
|
|
18
19
|
|
|
19
20
|
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
20
21
|
const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
@@ -24,16 +25,14 @@ const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
|
24
25
|
function parseDeployArgs(argv){
|
|
25
26
|
const out={preflight:false,approvalPath:null,approvalSha256:null,uacLegacyMigration:false,
|
|
26
27
|
markerReceiptPath:null,markerReceiptSha256:null,migrationNonce:null,migrationIssuedAt:null,
|
|
27
|
-
migrationExpiresAt:null,uacApprovalPath:null,uacApprovalSha256:null,
|
|
28
|
+
migrationExpiresAt:null,uacApprovalPath:null,uacApprovalSha256:null,hostCommandPins:[]};
|
|
28
29
|
for(let index=0;index<argv.length;index++){
|
|
29
30
|
const arg=argv[index];
|
|
30
31
|
if(arg==='--preflight'){if(out.preflight)throw new Error('duplicate --preflight');out.preflight=true;continue;}
|
|
31
32
|
if(arg==='--uac-legacy-migration'){if(out.uacLegacyMigration)throw new Error('duplicate --uac-legacy-migration');out.uacLegacyMigration=true;continue;}
|
|
32
33
|
if(arg==='--host-command-pin'){
|
|
33
|
-
if(out.hostCommandPin!==null)throw new Error('duplicate --host-command-pin');
|
|
34
34
|
const value=String(argv[++index]??'');
|
|
35
|
-
|
|
36
|
-
out.hostCommandPin=value;continue;
|
|
35
|
+
out.hostCommandPins.push(value);continue;
|
|
37
36
|
}
|
|
38
37
|
if(arg==='--marker-reconciliation-receipt'){
|
|
39
38
|
if(out.markerReceiptPath)throw new Error('duplicate --marker-reconciliation-receipt');
|
|
@@ -76,7 +75,8 @@ function parseDeployArgs(argv){
|
|
|
76
75
|
const uacFields=[out.migrationNonce,out.migrationIssuedAt,out.migrationExpiresAt,out.uacApprovalPath,out.uacApprovalSha256];
|
|
77
76
|
if(out.uacLegacyMigration&&uacFields.some(value=>!value))throw new Error('UAC migration requires nonce, lifetime, and approval receipt');
|
|
78
77
|
if(!out.uacLegacyMigration&&uacFields.some(value=>value))throw new Error('UAC migration arguments require --uac-legacy-migration');
|
|
79
|
-
|
|
78
|
+
out.hostCommandPins=normalizeHostCommandPins(out.hostCommandPins);
|
|
79
|
+
if(out.hostCommandPins.length>0&&(!out.uacLegacyMigration||out.preflight))throw new Error('explicit host repin requires the elevated UAC migration');
|
|
80
80
|
return out;
|
|
81
81
|
}
|
|
82
82
|
const deployArgs=parseDeployArgs(process.argv.slice(2));
|
|
@@ -301,6 +301,7 @@ if(deployArgs.uacLegacyMigration){
|
|
|
301
301
|
predecessorPolicySha256:markerReceipt.predecessor_policy_sha256,
|
|
302
302
|
preflightSubjectSha256:preflight.subject_sha256,nodeSha256:await sha(process.execPath),
|
|
303
303
|
deployScriptSha256:await sha(fileURLToPath(import.meta.url)),
|
|
304
|
+
hostCommandPins:deployArgs.hostCommandPins,
|
|
304
305
|
nonce:deployArgs.migrationNonce,issuedAt:deployArgs.migrationIssuedAt,
|
|
305
306
|
expiresAt:deployArgs.migrationExpiresAt
|
|
306
307
|
});
|
|
@@ -320,7 +321,7 @@ const args=[
|
|
|
320
321
|
'--bridge-root',bridgeRoot,'--bridge-script-sha256',await sha(bridgeScript),
|
|
321
322
|
'--enable-machine-fs','--enable-observation','--enable-desktop','--enable-autonomous-supervisor','--recover-offline-predecessor'
|
|
322
323
|
];
|
|
323
|
-
|
|
324
|
+
args.push(...hostCommandPinArgs(deployArgs.hostCommandPins));
|
|
324
325
|
let result=null,verification=null,activationOutcomePath=null;
|
|
325
326
|
let continuityEngine=null,continuityUpdateId=null;
|
|
326
327
|
|
|
@@ -9,6 +9,7 @@ import {parseActivePointer} from '../src/release-slot.mjs';
|
|
|
9
9
|
import {inspectTrustedInstallation,privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
|
|
10
10
|
import {verifyMarkerReconciliationReceipt} from '../src/clean-activation-recovery-r153.mjs';
|
|
11
11
|
import {createUacApproval,uacPointerSha256,protectedUacReceiptPath} from '../src/uac-approval-contract-r153.mjs';
|
|
12
|
+
import {decodeHostCommandPins} from '../src/host-command-pins-r154.mjs';
|
|
12
13
|
|
|
13
14
|
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
14
15
|
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
@@ -17,11 +18,15 @@ const hash=async f=>sha(await read(f));
|
|
|
17
18
|
const fail=text=>{throw new Error('R153 UAC issuer rejected: '+text);};
|
|
18
19
|
function args(argv){
|
|
19
20
|
const expected=['--state-root','--marker-receipt','--marker-receipt-sha256','--preflight-subject-sha256'];
|
|
20
|
-
if(argv.length
|
|
21
|
+
if(![8,10].includes(argv.length)||expected.some((key,i)=>argv[2*i]!==key)||
|
|
22
|
+
(argv.length===10&&argv[8]!=='--host-command-pins-json'))fail('unexpected arguments');
|
|
21
23
|
const [state,receipt,receiptHash,preflight]=[argv[1],argv[3],argv[5],argv[7]];
|
|
22
24
|
if(!path.win32.isAbsolute(state)||!path.win32.isAbsolute(receipt)||
|
|
23
25
|
!/^[0-9a-f]{64}$/.test(receiptHash)||!/^[0-9a-f]{64}$/.test(preflight))fail('subject arguments invalid');
|
|
24
|
-
|
|
26
|
+
let hostCommandPins=[];
|
|
27
|
+
try{hostCommandPins=decodeHostCommandPins(argv.length===10?argv[9]:'[]');}
|
|
28
|
+
catch(error){fail(error.message);}
|
|
29
|
+
return {state:path.resolve(state),receipt:path.resolve(receipt),receiptHash,preflight,hostCommandPins};
|
|
25
30
|
}
|
|
26
31
|
async function regular(file,label){const s=await lstat(file);if(!s.isFile()||s.isSymbolicLink())fail(label+' must be regular');return s;}
|
|
27
32
|
async function absent(file,label){try{await lstat(file);fail(label+' present');}catch(e){if(e?.code!=='ENOENT')throw e;}}
|
|
@@ -33,7 +38,7 @@ async function main(){
|
|
|
33
38
|
{encoding:'utf8',shell:false,windowsHide:true,timeout:12000});
|
|
34
39
|
if(elevated.error||elevated.status!==0||String(elevated.stdout).trim().toLowerCase()!=='true')
|
|
35
40
|
fail('explicit Windows UAC elevation required');
|
|
36
|
-
const {state,receipt,receiptHash,preflight}=args(process.argv.slice(2));
|
|
41
|
+
const {state,receipt,receiptHash,preflight,hostCommandPins}=args(process.argv.slice(2));
|
|
37
42
|
if(path.dirname(receipt).toLowerCase()!==path.join(state,'evidence','legacy-migration').toLowerCase())
|
|
38
43
|
fail('marker receipt path not in state evidence root');
|
|
39
44
|
const issueFile=fileURLToPath(import.meta.url);
|
|
@@ -41,11 +46,12 @@ async function main(){
|
|
|
41
46
|
const helperFile=path.join(root,'scripts','request-uac-migration-r153.ps1');
|
|
42
47
|
const trust=await inspectTrustedInstallation([...privilegedEpochInstallFiles(root,process.execPath),
|
|
43
48
|
issueFile,deployFile,helperFile,path.join(root,'package.json'),path.join(root,'MANIFEST.SHA256'),path.join(root,'RELEASE.json'),path.join(root,'src','uac-approval-contract-r153.mjs'),
|
|
44
|
-
path.join(root,'src','clean-activation-recovery-r153.mjs'),path.join(root,'src','legacy-offline-policy-proof-r146.mjs')
|
|
49
|
+
path.join(root,'src','clean-activation-recovery-r153.mjs'),path.join(root,'src','legacy-offline-policy-proof-r146.mjs'),
|
|
50
|
+
path.join(root,'src','host-command-pins-r154.mjs')]);
|
|
45
51
|
if(trust.trusted!==true)fail('candidate not R89 protected');
|
|
46
52
|
const pkg=JSON.parse((await read(path.join(root,'package.json'))).toString('utf8'));
|
|
47
53
|
const rel=JSON.parse((await read(path.join(root,'RELEASE.json'))).toString('utf8'));
|
|
48
|
-
if(pkg.version!=='0.15.
|
|
54
|
+
if(pkg.version!=='0.15.61'||rel.version!==pkg.version)fail('candidate release identity mismatch');
|
|
49
55
|
const pointer=parseActivePointer((await read(path.join(state,'active-release.json'))).toString('utf8'));
|
|
50
56
|
if(pointer.version!=='0.15.45'||rel.parent?.release_id!==pointer.release_id||
|
|
51
57
|
rel.parent?.manifest_sha256!==pointer.manifest_sha256||
|
|
@@ -85,6 +91,7 @@ async function main(){
|
|
|
85
91
|
predecessorPointerSha256:uacPointerSha256(pointer),
|
|
86
92
|
predecessorPolicySha256:policies,preflightSubjectSha256:preflight,
|
|
87
93
|
nodeSha256:await hash(process.execPath),deployScriptSha256:await hash(deployFile),
|
|
94
|
+
hostCommandPins,
|
|
88
95
|
nonce,issuedAt,expiresAt});
|
|
89
96
|
// The strict installation manifest disallows unmanifested files. A receipt
|
|
90
97
|
// under root would turn an otherwise valid candidate into an invalid tree.
|
|
@@ -6,9 +6,9 @@ import {fileURLToPath} from 'node:url';
|
|
|
6
6
|
const defaultRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
7
7
|
const hex64=/^[0-9a-f]{64}$/;
|
|
8
8
|
const EXPECTED=Object.freeze({
|
|
9
|
-
candidateVersion:'0.15.
|
|
10
|
-
sourceVersion:'0.15.
|
|
11
|
-
sourceSha256:'
|
|
9
|
+
candidateVersion:'0.15.61', train:'R154',
|
|
10
|
+
sourceVersion:'0.15.60',
|
|
11
|
+
sourceSha256:'b85f735f4c8be56f97ea5c78f0cc48583e8d5708a704c903596f5720e36ace83',
|
|
12
12
|
runtimeVersion:'0.15.45',
|
|
13
13
|
runtimeReleaseId:'v0.15.45-34aa8d1241cfb8bf',
|
|
14
14
|
runtimeManifest:'34aa8d1241cfb8bf0b5ba303bf3c06e0e9e1bf01f64457bca659d6dd4d634fdc',
|
|
@@ -7,7 +7,7 @@ param(
|
|
|
7
7
|
[Parameter(Mandatory=$true)][string]$MarkerReceiptPath,
|
|
8
8
|
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$MarkerReceiptSha256,
|
|
9
9
|
[ValidatePattern('^[0-9a-f]{64}$')][string]$PreflightSubjectSha256,
|
|
10
|
-
[
|
|
10
|
+
[string]$HostCommandPinsJson = '[]',
|
|
11
11
|
[switch]$Elevated
|
|
12
12
|
)
|
|
13
13
|
$ErrorActionPreference = 'Stop'
|
|
@@ -21,6 +21,19 @@ function Require-AbsoluteFile([string]$Value,[string]$Label) {
|
|
|
21
21
|
return $item.FullName
|
|
22
22
|
}
|
|
23
23
|
function Full([string]$Value) { [IO.Path]::GetFullPath($Value).TrimEnd('\') }
|
|
24
|
+
function Validate-PinsJson([string]$Value) {
|
|
25
|
+
try { $pins = @($Value | ConvertFrom-Json) } catch { throw 'R154 host command pins JSON invalid' }
|
|
26
|
+
if ($pins.Count -gt 16) { throw 'R154 host command pin limit exceeded' }
|
|
27
|
+
$seen = @{}
|
|
28
|
+
foreach ($pin in $pins) {
|
|
29
|
+
if ([string]$pin -cnotmatch '^[A-Za-z0-9._-]{1,64}=[0-9a-f]{64}$') { throw 'R154 host command pin contract invalid' }
|
|
30
|
+
$id = ([string]$pin).Split('=')[0]
|
|
31
|
+
if ($seen.ContainsKey($id)) { throw 'R154 duplicate host command pin' }
|
|
32
|
+
$seen[$id] = $true
|
|
33
|
+
}
|
|
34
|
+
return $pins
|
|
35
|
+
}
|
|
36
|
+
$HostCommandPins = @(Validate-PinsJson $HostCommandPinsJson)
|
|
24
37
|
$NodePath = Require-AbsoluteFile $NodePath 'Node executable'
|
|
25
38
|
$DeployScript = Require-AbsoluteFile $DeployScript 'Deploy script'
|
|
26
39
|
$MarkerReceiptPath = Require-AbsoluteFile $MarkerReceiptPath 'Marker reconciliation receipt'
|
|
@@ -100,7 +113,7 @@ if (-not $Elevated) {
|
|
|
100
113
|
# -Verb RunAs is the UseShellExecute parameter set. PowerShell 5.1 rejects
|
|
101
114
|
# -RedirectStandardOutput/-RedirectStandardError in that same Start-Process.
|
|
102
115
|
# The elevated child writes bounded, no-clobber evidence instead.
|
|
103
|
-
if ($
|
|
116
|
+
if ($HostCommandPinsJson -cne '[]') { $arguments += @('-HostCommandPinsJson',('"' + $HostCommandPinsJson.Replace('"','\"') + '"')) }
|
|
104
117
|
$child = Start-Process -FilePath $powershell -ArgumentList $arguments -Verb RunAs -Wait -PassThru -WindowStyle Hidden
|
|
105
118
|
if (Test-Path -LiteralPath $StdoutPath) { [Console]::Out.Write([IO.File]::ReadAllText($StdoutPath)) }
|
|
106
119
|
if (Test-Path -LiteralPath $StderrPath) { [Console]::Error.Write([IO.File]::ReadAllText($StderrPath)) }
|
|
@@ -123,7 +136,7 @@ try {
|
|
|
123
136
|
try {
|
|
124
137
|
$issuerOutput = @(& $NodePath $issuerScript '--state-root' $env:DEADBYTE_STATE_ROOT `
|
|
125
138
|
'--marker-receipt' $MarkerReceiptPath '--marker-receipt-sha256' $MarkerReceiptSha256 `
|
|
126
|
-
'--preflight-subject-sha256' $PreflightSubjectSha256 2>&1)
|
|
139
|
+
'--preflight-subject-sha256' $PreflightSubjectSha256 '--host-command-pins-json' $HostCommandPinsJson 2>&1)
|
|
127
140
|
$issuerExit = $LASTEXITCODE
|
|
128
141
|
} finally { $ErrorActionPreference = $priorErrorAction }
|
|
129
142
|
$issuerText = (@($issuerOutput) | ForEach-Object { [string]$_ }) -join "`n"
|
|
@@ -145,7 +158,7 @@ try {
|
|
|
145
158
|
'--migration-expires-at',$issued.expires_at_utc,
|
|
146
159
|
'--uac-approval-receipt',$issued.receipt_path,
|
|
147
160
|
'--uac-approval-receipt-sha256',$issued.receipt_sha256)
|
|
148
|
-
|
|
161
|
+
foreach ($pin in $HostCommandPins) { $deployArgs += @('--host-command-pin',[string]$pin) }
|
|
149
162
|
$deployOutput = @(& $NodePath $DeployScript @deployArgs 2>&1)
|
|
150
163
|
$deployExit = $LASTEXITCODE
|
|
151
164
|
} finally { $ErrorActionPreference = $priorErrorAction }
|
|
@@ -14,6 +14,7 @@ const files=[...privilegedEpochInstallFiles(root,process.execPath),
|
|
|
14
14
|
path.join(root,'package.json'),path.join(root,'MANIFEST.SHA256'),path.join(root,'RELEASE.json'),
|
|
15
15
|
path.join(root,'src','uac-approval-contract-r153.mjs'),
|
|
16
16
|
path.join(root,'src','clean-activation-recovery-r153.mjs'),
|
|
17
|
+
path.join(root,'src','host-command-pins-r154.mjs'),
|
|
17
18
|
path.join(root,'src','legacy-offline-policy-proof-r146.mjs')];
|
|
18
19
|
const verdict=await inspectTrustedInstallation(files);
|
|
19
20
|
if(verdict.trusted!==true)throw new Error('R153 protected stage trust rejected');
|
|
@@ -32,7 +32,7 @@ export function classifyLegacyMigration(input){
|
|
|
32
32
|
if(!input.pointer||input.pointer.version!=='0.15.45'||
|
|
33
33
|
!HEX64.test(String(input.pointer.manifest_sha256??''))||!HEX64.test(String(input.pointer.generation_manifest_sha256??'')))
|
|
34
34
|
return reject('predecessor identity invalid',subject);
|
|
35
|
-
if(!input.candidate||input.candidate.version!=='0.15.
|
|
35
|
+
if(!input.candidate||input.candidate.version!=='0.15.61'||
|
|
36
36
|
!HEX64.test(String(input.candidate.manifest_sha256??''))||!HEX64.test(String(input.candidate.tarball_sha256??'')))
|
|
37
37
|
return reject('candidate identity invalid',subject);
|
|
38
38
|
if(PLANES.some(plane=>!HEX64.test(String(input.policySha256?.[plane]??''))))
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -27,6 +27,7 @@ import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
|
|
|
27
27
|
import { AUTHORITY_PLANES, authorityVector, authorityRestorePlan, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
|
|
28
28
|
import { decodeRemoteLogBytes, boundedRemoteDiagnostic, readRemoteLogAppend } from './remote-log-decode-r60.mjs';
|
|
29
29
|
import {reconcileStaleMarker,resumeMarkerReconciliation,recoverPublishedMarkerTerminal} from './clean-activation-recovery-r153.mjs';
|
|
30
|
+
import {normalizeHostCommandPins,encodeHostCommandPins} from './host-command-pins-r154.mjs';
|
|
30
31
|
|
|
31
32
|
const REMOTE_COMMANDS=new Set([
|
|
32
33
|
'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
|
|
@@ -114,7 +115,7 @@ async function reportVersionCoherence(root,{displayMode='ascii'}={}){
|
|
|
114
115
|
writeRemote(process.stderr,`${remoteTag('warn',displayMode)} VERSION SKEW: CLI package ${DEADBYTE_VERSION}; active runtime ${active.version} (${active.release_id})\n`,displayMode);
|
|
115
116
|
return active;
|
|
116
117
|
}
|
|
117
|
-
async function synchronizeForegroundRuntime(ctx,{displayMode='unicode',migrationContext=null,
|
|
118
|
+
async function synchronizeForegroundRuntime(ctx,{displayMode='unicode',migrationContext=null,hostCommandPins=[]}={}){
|
|
118
119
|
const packageManifestSha256=createHash('sha256').update(await readFile(path.join(CURRENT_PACKAGE_ROOT,'MANIFEST.SHA256'))).digest('hex');
|
|
119
120
|
return ensureRuntimeCoherence({
|
|
120
121
|
cliVersion:DEADBYTE_VERSION,
|
|
@@ -182,7 +183,7 @@ async function synchronizeForegroundRuntime(ctx,{displayMode='unicode',migration
|
|
|
182
183
|
'-ExpectedBootstrapSha256',bootstrapMatch[1],
|
|
183
184
|
'-StdoutPath',stdoutPath,'-StderrPath',stderrPath,
|
|
184
185
|
'-MarkerReceiptPath',migrationContext.receipt_path,'-MarkerReceiptSha256',migrationContext.receipt_sha256,
|
|
185
|
-
...(
|
|
186
|
+
...(hostCommandPins.length?['-HostCommandPinsJson',encodeHostCommandPins(hostCommandPins)]:[])],{
|
|
186
187
|
cwd:CURRENT_PACKAGE_ROOT,env:windowsPowerShellEnvironment(process.env),encoding:'utf8',windowsHide:true,shell:false,
|
|
187
188
|
timeout:REMOTE_UPDATE_TIMEOUT_MS,maxBuffer:16*1024*1024
|
|
188
189
|
});
|
|
@@ -744,7 +745,7 @@ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
|
|
|
744
745
|
if(deletePinnedRemoteMarker(remoteSessionMarker(ctx.root),snapshot.sha256)!=='REMOVED')
|
|
745
746
|
throw new Error('remote session ownership marker replaced before native deletion; marker retained');
|
|
746
747
|
}
|
|
747
|
-
async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default',
|
|
748
|
+
async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,filterExpression='',mode='default',hostCommandPins=[]}={}){
|
|
748
749
|
const display=prepareRemoteConsole();
|
|
749
750
|
const displayMode=display.mode;
|
|
750
751
|
const ctx=await remoteContext();
|
|
@@ -756,7 +757,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
756
757
|
startupLock.assertHeld();
|
|
757
758
|
const migrationContext=await recoverRemoteSession(ctx,{displayMode});
|
|
758
759
|
startupLock.assertHeld();
|
|
759
|
-
await synchronizeForegroundRuntime(ctx,{displayMode,migrationContext,
|
|
760
|
+
await synchronizeForegroundRuntime(ctx,{displayMode,migrationContext,hostCommandPins});
|
|
760
761
|
startupLock.assertHeld();
|
|
761
762
|
marker=await claimRemoteSession(ctx);
|
|
762
763
|
startupLock.assertHeld();
|
|
@@ -869,17 +870,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
869
870
|
}
|
|
870
871
|
}
|
|
871
872
|
async function runRemote(args){
|
|
872
|
-
let verbose=false,trace=false,filterExpression='',resumeCursor=null,mode='default'
|
|
873
|
+
let verbose=false,trace=false,filterExpression='',resumeCursor=null,mode='default';
|
|
874
|
+
const hostCommandPinValues=[];
|
|
873
875
|
const positional=[];
|
|
874
876
|
for(let index=0;index<args.length;index++){
|
|
875
877
|
const arg=args[index];
|
|
876
878
|
if(arg==='--verbose'||arg==='-v'){verbose=true;continue;}
|
|
877
879
|
if(arg==='--trace'){trace=true;continue;}
|
|
878
880
|
if(arg==='--host-command-pin'){
|
|
879
|
-
if(hostCommandPin!==null)throw new Error('remote --host-command-pin may be specified once');
|
|
880
881
|
const value=String(args[++index]??'');
|
|
881
|
-
|
|
882
|
-
hostCommandPin=value;continue;
|
|
882
|
+
hostCommandPinValues.push(value);continue;
|
|
883
883
|
}
|
|
884
884
|
if(['--operator','--desktop','--host','--full'].includes(arg)){
|
|
885
885
|
if(mode!=='default') throw new Error('remote authority modes are mutually exclusive');
|
|
@@ -897,8 +897,11 @@ async function runRemote(args){
|
|
|
897
897
|
}
|
|
898
898
|
positional.push(arg);
|
|
899
899
|
}
|
|
900
|
-
|
|
901
|
-
|
|
900
|
+
let hostCommandPins;
|
|
901
|
+
try{hostCommandPins=normalizeHostCommandPins(hostCommandPinValues);}
|
|
902
|
+
catch(error){throw new Error(`remote --host-command-pin ${error.message}`);}
|
|
903
|
+
const presentation=verbose||trace||Boolean(filterExpression)||resumeCursor!==null||mode!=='default'||hostCommandPins.length>0;
|
|
904
|
+
if(positional.length===0){await runRemoteSession({resumeCursor,verbose,trace,filterExpression,mode,hostCommandPins});return;}
|
|
902
905
|
if(presentation) throw new Error('remote presentation flags are valid only for foreground session');
|
|
903
906
|
if(positional.length>1) throw new Error('remote accepts one controller command');
|
|
904
907
|
const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const PIN=/^([A-Za-z0-9._-]{1,64})=([0-9a-f]{64})$/;
|
|
2
|
+
const LIMIT=16;
|
|
3
|
+
const REPINNABLE=new Set(['powershell_script','powershell_inline','cmd_script','cmd_inline','node_script','node_inline']);
|
|
4
|
+
|
|
5
|
+
export function normalizeHostCommandPins(values=[]){
|
|
6
|
+
if(!Array.isArray(values)||values.length>LIMIT)throw new Error(`host command pin limit is ${LIMIT}`);
|
|
7
|
+
const seen=new Set(),pins=[];
|
|
8
|
+
for(const value of values){
|
|
9
|
+
const match=PIN.exec(String(value??''));
|
|
10
|
+
if(!match)throw new Error('host command pin contract requires command_id=lowercase_sha256');
|
|
11
|
+
const [,command_id,sha256]=match;
|
|
12
|
+
if(!REPINNABLE.has(command_id))throw new Error(`host command '${command_id}' is not repinnable`);
|
|
13
|
+
if(seen.has(command_id))throw new Error(`duplicate host command pin '${command_id}'`);
|
|
14
|
+
seen.add(command_id);pins.push(Object.freeze({command_id,sha256}));
|
|
15
|
+
}
|
|
16
|
+
pins.sort((a,b)=>a.command_id<b.command_id?-1:a.command_id>b.command_id?1:0);
|
|
17
|
+
return Object.freeze(pins);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function encodeHostCommandPins(pins=[]){
|
|
21
|
+
return JSON.stringify(normalizeHostCommandPins(pins.map(pin=>`${pin.command_id}=${pin.sha256}`))
|
|
22
|
+
.map(pin=>`${pin.command_id}=${pin.sha256}`));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function decodeHostCommandPins(encoded='[]'){
|
|
26
|
+
let values;
|
|
27
|
+
try{values=JSON.parse(String(encoded));}catch{throw new Error('host command pins JSON invalid');}
|
|
28
|
+
return normalizeHostCommandPins(values);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function hostCommandPinArgs(pins=[]){
|
|
32
|
+
return normalizeHostCommandPins(pins.map(pin=>`${pin.command_id}=${pin.sha256}`))
|
|
33
|
+
.flatMap(pin=>['--host-command-pin',`${pin.command_id}=${pin.sha256}`]);
|
|
34
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Verify it only when running elevated from an independently R89-verified install.
|
|
4
4
|
import {createHash} from 'node:crypto';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
+
import {normalizeHostCommandPins} from './host-command-pins-r154.mjs';
|
|
6
7
|
|
|
7
8
|
const HEX64=/^[0-9a-f]{64}$/;
|
|
8
9
|
const PLANES=['process','coding','autonomous','desktop','host'];
|
|
@@ -10,7 +11,7 @@ const ALL_DISARMED=Object.freeze(Object.fromEntries(PLANES.map(p=>[p,false])));
|
|
|
10
11
|
const TIME=/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/;
|
|
11
12
|
const FIELDS=['schema','action','state_root','candidate_root','candidate_version','candidate_manifest_sha256',
|
|
12
13
|
'tarball_sha256','marker_receipt_sha256','predecessor_pointer_sha256','predecessor_policy_sha256',
|
|
13
|
-
'preflight_subject_sha256','node_sha256','deploy_script_sha256','nonce','issued_at_utc',
|
|
14
|
+
'preflight_subject_sha256','node_sha256','deploy_script_sha256','host_command_pins','nonce','issued_at_utc',
|
|
14
15
|
'expires_at_utc','allowed_authority_after','subject_sha256'];
|
|
15
16
|
function canonical(value){
|
|
16
17
|
if(value===null)return 'null';
|
|
@@ -58,7 +59,7 @@ export function uacReceiptSubject(input){
|
|
|
58
59
|
const candidate=windowsAbsolute(input.candidateRoot,'candidate root');
|
|
59
60
|
if(!candidate.startsWith('c:\\program files\\')||candidate==='c:\\program files\\')
|
|
60
61
|
throw new Error('UAC migration requires a protected candidate installation');
|
|
61
|
-
if(input.candidateVersion!=='0.15.
|
|
62
|
+
if(input.candidateVersion!=='0.15.61')throw new Error('UAC migration candidate identity unsupported');
|
|
62
63
|
const hashes=['candidateManifestSha256','tarballSha256','markerReceiptSha256','predecessorPointerSha256',
|
|
63
64
|
'preflightSubjectSha256','nodeSha256','deployScriptSha256','nonce'];
|
|
64
65
|
for(const key of hashes)if(!HEX64.test(String(input[key]??'')))throw new Error('UAC approval hash/nonce invalid: '+key);
|
|
@@ -68,6 +69,8 @@ export function uacReceiptSubject(input){
|
|
|
68
69
|
throw new Error('UAC predecessor policy identity incomplete');
|
|
69
70
|
const issued=time(input.issuedAt,'issue time'),expires=time(input.expiresAt,'expiry time');
|
|
70
71
|
if(expires<=issued||expires-issued>600_000)throw new Error('UAC consent lifetime invalid');
|
|
72
|
+
const hostCommandPins=normalizeHostCommandPins((input.hostCommandPins??[]).map(pin=>
|
|
73
|
+
typeof pin==='string'?pin:`${pin?.command_id??''}=${pin?.sha256??''}`));
|
|
71
74
|
return Object.freeze({schema:'deadbyte.r153-uac-approval.v1',action:'narrow_legacy_policy_and_activate',
|
|
72
75
|
state_root:root,candidate_root:candidate,candidate_version:input.candidateVersion,
|
|
73
76
|
candidate_manifest_sha256:input.candidateManifestSha256,tarball_sha256:input.tarballSha256,
|
|
@@ -75,7 +78,7 @@ export function uacReceiptSubject(input){
|
|
|
75
78
|
predecessor_pointer_sha256:input.predecessorPointerSha256,
|
|
76
79
|
predecessor_policy_sha256:Object.freeze(Object.fromEntries(PLANES.map(p=>[p,policies[p]]))),
|
|
77
80
|
preflight_subject_sha256:input.preflightSubjectSha256,node_sha256:input.nodeSha256,
|
|
78
|
-
deploy_script_sha256:input.deployScriptSha256,nonce:input.nonce,
|
|
81
|
+
deploy_script_sha256:input.deployScriptSha256,host_command_pins:hostCommandPins,nonce:input.nonce,
|
|
79
82
|
issued_at_utc:input.issuedAt,expires_at_utc:input.expiresAt,
|
|
80
83
|
allowed_authority_after:Object.freeze({...ALL_DISARMED})});
|
|
81
84
|
}
|
package/src/version.mjs
CHANGED
|
@@ -13,15 +13,15 @@ test('R130 security patch has a new immutable package identity and truthful sour
|
|
|
13
13
|
read('SBOM.cdx.json'),read('package-lock.json'),read('TEST-MATRIX.json')]);
|
|
14
14
|
assert.equal(pkg.name,'deadbyte-mcp');
|
|
15
15
|
for(const actual of [pkg.version,release.version,provenance.version,matrix.version,bom.metadata.component.version,lock.version,lock.packages[''].version,DEADBYTE_VERSION])
|
|
16
|
-
assert.equal(actual,'0.15.
|
|
17
|
-
assert.equal(release.train,'
|
|
18
|
-
assert.equal(matrix.train,'
|
|
19
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
20
|
-
assert.match(release.source_predecessor.source_path,/0\.15\.
|
|
16
|
+
assert.equal(actual,'0.15.61');
|
|
17
|
+
assert.equal(release.train,'R154');
|
|
18
|
+
assert.equal(matrix.train,'R154');
|
|
19
|
+
assert.equal(release.source_predecessor.version,'0.15.60');
|
|
20
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.60/);
|
|
21
21
|
assert.equal(release.runtime_predecessor.version,'0.15.45');
|
|
22
|
-
assert.equal(provenance.source_predecessor_tarball_sha256,'
|
|
23
|
-
assert.equal(provenance.source_predecessor_version,'0.15.
|
|
24
|
-
assert.equal(provenance.source_predecessor_manifest_file_sha256,'
|
|
22
|
+
assert.equal(provenance.source_predecessor_tarball_sha256,'b85f735f4c8be56f97ea5c78f0cc48583e8d5708a704c903596f5720e36ace83');
|
|
23
|
+
assert.equal(provenance.source_predecessor_version,'0.15.60');
|
|
24
|
+
assert.equal(provenance.source_predecessor_manifest_file_sha256,'2a19c9da7f11eb831dfb24ba6c6d9789e25a373cea2f3a25ff21d460e07fba36');
|
|
25
25
|
assert.equal(pkg.dependencies['@modelcontextprotocol/client'],'2.3.1');
|
|
26
26
|
assert.equal(pkg.dependencies['@modelcontextprotocol/server'],'2.3.1');
|
|
27
27
|
for(const dep of ['@modelcontextprotocol/client','@modelcontextprotocol/server'])
|
|
@@ -15,13 +15,13 @@ test('R144 package identities, R132 historic evidence and published predecessor
|
|
|
15
15
|
for(const value of [pkg.version,lock.version,lock.packages[''].version,release.version,prov.version,bom.metadata.component.version,matrix.version,DEADBYTE_VERSION]){
|
|
16
16
|
assert.equal(value,pkg.version);
|
|
17
17
|
}
|
|
18
|
-
assert.equal(release.train,'
|
|
19
|
-
assert.equal(matrix.train,'
|
|
20
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
21
|
-
assert.match(release.source_predecessor.source_path,/0\.15\.
|
|
22
|
-
assert.equal(prov.source_predecessor_version,'0.15.
|
|
23
|
-
assert.equal(prov.source_predecessor_manifest_file_sha256,'
|
|
24
|
-
assert.equal(prov.parent_tarball_sha256,'
|
|
18
|
+
assert.equal(release.train,'R154');
|
|
19
|
+
assert.equal(matrix.train,'R154');
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.60');
|
|
21
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.60/);
|
|
22
|
+
assert.equal(prov.source_predecessor_version,'0.15.60');
|
|
23
|
+
assert.equal(prov.source_predecessor_manifest_file_sha256,'2a19c9da7f11eb831dfb24ba6c6d9789e25a373cea2f3a25ff21d460e07fba36');
|
|
24
|
+
assert.equal(prov.parent_tarball_sha256,'b85f735f4c8be56f97ea5c78f0cc48583e8d5708a704c903596f5720e36ace83');
|
|
25
25
|
assert.equal(prov.parent_registry_tarball_sha1,'04cc1a1e80bcb7062ac39eee6b7bab70edd5d8c3');
|
|
26
26
|
assert.equal(release.runtime_predecessor.version,'0.15.45');
|
|
27
27
|
assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
|
|
@@ -22,7 +22,7 @@ const planes=['process','coding','autonomous','desktop','host'];
|
|
|
22
22
|
const base=()=>({
|
|
23
23
|
stateRoot:'C:\\Users\\owner\\.deadbyte-mcp',
|
|
24
24
|
pointer:{release_id:'v0.15.45-parent',version:'0.15.45',manifest_sha256:'1'.repeat(64),generation_manifest_sha256:'2'.repeat(64)},
|
|
25
|
-
candidate:{version:'0.15.
|
|
25
|
+
candidate:{version:'0.15.61',manifest_sha256:'3'.repeat(64),tarball_sha256:'4'.repeat(64)},
|
|
26
26
|
observation:{state:'OFFLINE',local_ready:false,bridge:{alive:false},core:{alive:false},cloud:{alive:false},supervisor:{running:false},
|
|
27
27
|
authority:Object.fromEntries(planes.map(plane=>[plane,{configured:true,armed:false,reason:plane==='autonomous'?'unverified':'disarmed'}]))},
|
|
28
28
|
policySha256:Object.fromEntries(planes.map((plane,index)=>[plane,String(index+5).repeat(64)])),
|
|
@@ -55,7 +55,7 @@ test('R153 RED: UAC subject binds candidate, predecessor, policy, marker, state
|
|
|
55
55
|
issuedAt:'2026-10-23T16:00:00.000Z',expiresAt:'2026-10-23T16:10:00.000Z'});
|
|
56
56
|
assert.equal(subject.action,'narrow_legacy_policy_and_activate');
|
|
57
57
|
assert.equal(subject.marker_sha256,HEX);
|
|
58
|
-
assert.equal(subject.candidate.version,'0.15.
|
|
58
|
+
assert.equal(subject.candidate.version,'0.15.61');
|
|
59
59
|
assert.equal(subject.nonce,'b'.repeat(64));
|
|
60
60
|
assert.deepEqual(subject.allowed_authority_after,Object.fromEntries(planes.map(plane=>[plane,false])));
|
|
61
61
|
});
|
|
@@ -29,7 +29,7 @@ test('R153 rejects runtime predecessor or parent drift',()=>{
|
|
|
29
29
|
test('R153 rejects version / train / bridge identity drift',()=>{
|
|
30
30
|
const state=copy();state.pkg.version='0.15.56';
|
|
31
31
|
assert.throws(()=>verifyR153Lineage(state),/package version/);
|
|
32
|
-
const state2=copy();state2.release.train='
|
|
32
|
+
const state2=copy();state2.release.train='R153';
|
|
33
33
|
assert.throws(()=>verifyR153Lineage(state2),/train/);
|
|
34
34
|
const state3=copy();state3.bridge.version='0.15.45';
|
|
35
35
|
assert.throws(()=>verifyR153Lineage(state3),/bridge version/);
|
|
@@ -32,13 +32,13 @@ test('signed R27 initial precondition reject safely reconciles R65 unknown claim
|
|
|
32
32
|
const dir=path.join(updates,'r27-'+'d'.repeat(16)+'-synthetic');
|
|
33
33
|
await mkdir(dir);
|
|
34
34
|
const failure=seal({schema:'deadbyte.r27-slot-update-failure.v1',update_id:path.basename(dir),
|
|
35
|
-
release_version:'0.15.
|
|
35
|
+
release_version:'0.15.61',candidate_manifest_sha256:'d'.repeat(64),failed_at_utc:now(),
|
|
36
36
|
result:'PRECONDITION_REJECTED',phase:'initial',error:{name:'Error',message:"host command 'powershell_script' executable pin drift detected; explicit approval required"},
|
|
37
37
|
prior_pointer:pointer,attempted_pointer:null,rollback:{attempted:false,errors:[]}},privateKey,publicKey);
|
|
38
38
|
await writeFile(path.join(dir,'failure-receipt.json'),JSON.stringify(failure));
|
|
39
39
|
await writeFile(path.join(updates,'r65-activation-outcome-test.json'),JSON.stringify({
|
|
40
40
|
schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:claim.operation_id,
|
|
41
|
-
preflight_subject_sha256:claim.subject_sha256,candidate_version:'0.15.
|
|
41
|
+
preflight_subject_sha256:claim.subject_sha256,candidate_version:'0.15.61',
|
|
42
42
|
candidate_manifest_sha256:'d'.repeat(64),predecessor_release_id:pointer.release_id,
|
|
43
43
|
outcome:{state:'ACTIVATION_OUTCOME_UNKNOWN'},update_id:null,continuity_update_id:null,state_path:null,observed_at_utc:now()
|
|
44
44
|
}));
|
|
@@ -20,28 +20,39 @@ test('R153 Windows ACL reproduction rejects stripped leaf ACE and restores direc
|
|
|
20
20
|
$root = Join-Path $env:TEMP ('r153-native-acl-test-' + [guid]::NewGuid().ToString('N'))
|
|
21
21
|
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
22
22
|
$leaf = Join-Path $root 'child\file.txt'
|
|
23
|
+
function Read-Digest([string]$Path) {
|
|
24
|
+
try {
|
|
25
|
+
$sha = [Security.Cryptography.SHA256]::Create()
|
|
26
|
+
try { return ([BitConverter]::ToString($sha.ComputeHash([IO.File]::ReadAllBytes($Path)))).Replace('-','') }
|
|
27
|
+
finally { $sha.Dispose() }
|
|
28
|
+
} catch [UnauthorizedAccessException] { return $null }
|
|
29
|
+
}
|
|
23
30
|
try {
|
|
24
31
|
New-Item -ItemType Directory -Path (Split-Path -Parent $leaf) -Force | Out-Null
|
|
25
32
|
[IO.File]::WriteAllText($leaf,'read-only regression fixture')
|
|
26
33
|
& icacls.exe $root '/inheritance:r' '/grant:r' ('*'+$sid+':(OI)(CI)(F)') '/T' '/C' | Out-Null
|
|
27
34
|
if($LASTEXITCODE -ne 0){throw 'REPRO_SETUP_FAILED'}
|
|
28
|
-
$blocked =
|
|
35
|
+
$blocked = Read-Digest $leaf
|
|
29
36
|
if($null -ne $blocked){throw 'REPRO_DID_NOT_STRIP_LEAF_ACE'}
|
|
30
37
|
& icacls.exe $root '/grant' ('*'+$sid+':(F)') '/T' '/C' | Out-Null
|
|
31
38
|
if($LASTEXITCODE -ne 0){throw 'LEAF_REPAIR_FAILED'}
|
|
32
|
-
$restored =
|
|
33
|
-
if($null -eq $restored -or $restored.
|
|
34
|
-
$acl =
|
|
35
|
-
$allow=@($acl.
|
|
39
|
+
$restored = Read-Digest $leaf
|
|
40
|
+
if($null -eq $restored -or $restored.Length -ne 64){throw 'LEAF_NOT_READABLE_AFTER_REPAIR'}
|
|
41
|
+
$acl = [IO.File]::GetAccessControl($leaf)
|
|
42
|
+
$allow=@($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]) | Where-Object {$_.AccessControlType -eq 'Allow'})
|
|
36
43
|
if($allow.Count -ne 1){throw 'LEAF_HAS_UNEXPECTED_ALLOW_ACE_COUNT'}
|
|
37
|
-
if($allow[0].IdentityReference.
|
|
44
|
+
if($allow[0].IdentityReference.Value -ne $sid){throw 'LEAF_HAS_UNEXPECTED_TRUSTEE'}
|
|
38
45
|
Write-Output 'R153_WIN32_LEAF_ACL_REPAIR_PASS'
|
|
39
46
|
} finally {
|
|
40
|
-
if(Test-Path -LiteralPath $root){
|
|
47
|
+
if(Test-Path -LiteralPath $root){
|
|
48
|
+
& icacls.exe $root '/reset' '/T' '/C' | Out-Null
|
|
49
|
+
& icacls.exe $root '/inheritance:e' '/grant:r' ('*'+$sid+':(OI)(CI)(F)') '/T' '/C' | Out-Null
|
|
50
|
+
Remove-Item -LiteralPath $root -Force -Recurse
|
|
51
|
+
}
|
|
41
52
|
}`;
|
|
42
53
|
const encoded=Buffer.from(ps,'utf16le').toString('base64');
|
|
43
54
|
const result=spawnSync('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',encoded],
|
|
44
55
|
{encoding:'utf8',timeout:30000,maxBuffer:256*1024});
|
|
45
56
|
assert.equal(result.status,0,result.stderr||result.stdout||String(result.error));
|
|
46
57
|
assert.match(result.stdout,/R153_WIN32_LEAF_ACL_REPAIR_PASS/);
|
|
47
|
-
});
|
|
58
|
+
});
|
|
@@ -4,12 +4,13 @@ import {createUacApproval,verifyUacApproval,validateApprovalWindow,requireTruste
|
|
|
4
4
|
import {readFileSync} from 'node:fs';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
const h=(n)=>n.repeat(64);
|
|
7
|
-
const base=()=>({stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp',candidateRoot:'C:\\Program Files\\DEADBYTE-R153-TEST',candidateVersion:'0.15.
|
|
7
|
+
const base=()=>({stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp',candidateRoot:'C:\\Program Files\\DEADBYTE-R153-TEST',candidateVersion:'0.15.61',candidateManifestSha256:h('a'),tarballSha256:h('b'),markerReceiptSha256:h('c'),predecessorPointerSha256:h('d'),predecessorPolicySha256:{process:h('1'),coding:h('2'),autonomous:h('3'),desktop:h('4'),host:h('5')},preflightSubjectSha256:h('e'),nodeSha256:h('f'),deployScriptSha256:h('0'),hostCommandPins:[],nonce:h('6'),issuedAt:'2026-10-24T00:00:00.000Z',expiresAt:'2026-10-24T00:05:00.000Z'});
|
|
8
8
|
test('R153 UAC receipt carries exact no-grants narrow migration subject',()=>{
|
|
9
|
-
const x=base(),receipt=createUacApproval(x);
|
|
9
|
+
const x={...base(),hostCommandPins:[{command_id:'powershell_script',sha256:h('a')},{command_id:'cmd_script',sha256:h('b')}]},receipt=createUacApproval(x);
|
|
10
10
|
assert.equal(receipt.schema,'deadbyte.r153-uac-approval.v1');
|
|
11
11
|
assert.equal(receipt.action,'narrow_legacy_policy_and_activate');
|
|
12
12
|
assert.deepEqual(receipt.allowed_authority_after,{process:false,coding:false,autonomous:false,desktop:false,host:false});
|
|
13
|
+
assert.deepEqual(receipt.host_command_pins,[{command_id:'cmd_script',sha256:h('b')},{command_id:'powershell_script',sha256:h('a')}]);
|
|
13
14
|
assert.match(receipt.subject_sha256,/^[0-9a-f]{64}$/);
|
|
14
15
|
assert.deepEqual(verifyUacApproval(receipt,x,{now:'2026-10-24T00:02:00.000Z'}),receipt);
|
|
15
16
|
});
|
|
@@ -19,6 +20,7 @@ test('R153 UAC receipt rejects drift across every authority/candidate/runtime in
|
|
|
19
20
|
assert.throws(()=>verifyUacApproval(receipt,{...x,[key]:value},{now:'2026-10-24T00:02:00.000Z'}),undefined,key);
|
|
20
21
|
}
|
|
21
22
|
assert.throws(()=>verifyUacApproval(receipt,{...x,predecessorPolicySha256:{...x.predecessorPolicySha256,host:h('a')}},{now:'2026-10-24T00:02:00.000Z'}));
|
|
23
|
+
assert.throws(()=>verifyUacApproval(receipt,{...x,hostCommandPins:[{command_id:'cmd_script',sha256:h('a')}]},{now:'2026-10-24T00:02:00.000Z'}));
|
|
22
24
|
});
|
|
23
25
|
test('R153 UAC receipt fails closed on expiration, malformed, replay-like tamper and grant widening',()=>{
|
|
24
26
|
const x=base(),receipt=createUacApproval(x);
|
|
@@ -31,7 +33,7 @@ test('R153 UAC receipt fails closed on expiration, malformed, replay-like tamper
|
|
|
31
33
|
assert.throws(()=>createUacApproval({...x,issuedAt:'not-a-date'}));
|
|
32
34
|
assert.throws(()=>createUacApproval({...x,nonce:'x'}));
|
|
33
35
|
});
|
|
34
|
-
test('
|
|
36
|
+
test('R154 UAC scope only accepts protected root and exact v0.15.61',()=>{
|
|
35
37
|
assert.throws(()=>createUacApproval({...base(),candidateRoot:'C:\\Users\\DEADBYTE\\npm-cache'}),/protected/i);
|
|
36
38
|
assert.throws(()=>createUacApproval({...base(),candidateVersion:'0.15.55'}),/candidate/i);
|
|
37
39
|
assert.throws(()=>validateApprovalWindow('2026-10-24T00:00:00.000Z','2026-10-24T00:00:00.000Z','2026-10-24T00:00:00.000Z'));
|
|
@@ -45,7 +45,7 @@ test('R153 RED: mutable npx entrypoint stages exact registry package before priv
|
|
|
45
45
|
assert.doesNotMatch(script,/npm-cli\.js|npm install/i);
|
|
46
46
|
assert.match(script,/Get-FileHash -LiteralPath \$file -Algorithm SHA256/);
|
|
47
47
|
assert.match(script,/NODE_OPTIONS/);
|
|
48
|
-
assert.match(script,/https:\/\/registry\.npmjs\.org\/deadbyte-mcp\/0\.15\.
|
|
48
|
+
assert.match(script,/https:\/\/registry\.npmjs\.org\/deadbyte-mcp\/0\.15\.61/);
|
|
49
49
|
assert.match(script,/live npm registry tarball SHA-256 mismatch/);
|
|
50
50
|
assert.match(script,/live npm registry tarball integrity mismatch/);
|
|
51
51
|
assert.match(script,/DEADBYTE-R153-Stage-/);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import {
|
|
6
|
+
normalizeHostCommandPins,
|
|
7
|
+
encodeHostCommandPins,
|
|
8
|
+
decodeHostCommandPins
|
|
9
|
+
} from '../src/host-command-pins-r154.mjs';
|
|
10
|
+
|
|
11
|
+
const h=value=>value.repeat(64);
|
|
12
|
+
|
|
13
|
+
test('R154 accepts a bounded set of distinct existing-command repins',()=>{
|
|
14
|
+
const pins=normalizeHostCommandPins([
|
|
15
|
+
`powershell_script=${h('a')}`,
|
|
16
|
+
`cmd_script=${h('b')}`,
|
|
17
|
+
`cmd_inline=${h('b')}`
|
|
18
|
+
]);
|
|
19
|
+
assert.deepEqual(pins,[
|
|
20
|
+
{command_id:'cmd_inline',sha256:h('b')},
|
|
21
|
+
{command_id:'cmd_script',sha256:h('b')},
|
|
22
|
+
{command_id:'powershell_script',sha256:h('a')}
|
|
23
|
+
]);
|
|
24
|
+
assert.deepEqual(decodeHostCommandPins(encodeHostCommandPins(pins)),pins);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('R154 rejects duplicate, malformed, oversized, and delimiter-injected repins',()=>{
|
|
28
|
+
assert.throws(()=>normalizeHostCommandPins([`cmd_script=${h('a')}`,`cmd_script=${h('a')}`]),/duplicate/i);
|
|
29
|
+
assert.throws(()=>normalizeHostCommandPins([`unrelated_command=${h('a')}`]),/not repinnable/i);
|
|
30
|
+
assert.throws(()=>normalizeHostCommandPins([`bad/name=${h('a')}`]),/contract/i);
|
|
31
|
+
assert.throws(()=>normalizeHostCommandPins([`cmd_script=${h('A')}`]),/contract/i);
|
|
32
|
+
assert.throws(()=>normalizeHostCommandPins(Array.from({length:17},(_,i)=>`cmd_${i}=${h('a')}`)),/limit/i);
|
|
33
|
+
assert.throws(()=>decodeHostCommandPins('["cmd_script='+h('a')+'","evil"]'),/contract/i);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('R154 CLI no longer rejects the second explicit command repin in parsing',()=>{
|
|
37
|
+
const cli=fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url));
|
|
38
|
+
const run=spawnSync(process.execPath,[cli,'remote',
|
|
39
|
+
'--host-command-pin',`powershell_script=${h('a')}`,
|
|
40
|
+
'--host-command-pin',`cmd_script=${h('b')}`,
|
|
41
|
+
'status'],{encoding:'utf8',timeout:8000});
|
|
42
|
+
assert.equal(run.status,1);
|
|
43
|
+
assert.doesNotMatch(run.stderr,/may be specified once|requires powershell_script/);
|
|
44
|
+
assert.match(run.stderr,/presentation flags are valid only for foreground session/);
|
|
45
|
+
});
|
|
@@ -83,7 +83,7 @@ test('R72 compact baseline permits only R64 read offset and authorized coding-on
|
|
|
83
83
|
assert.deepEqual(diff.added,[]);
|
|
84
84
|
assert.deepEqual(diff.removed,[]);
|
|
85
85
|
const packageVersion=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8')).version;
|
|
86
|
-
if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60'].includes(packageVersion)){
|
|
86
|
+
if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60','0.15.61'].includes(packageVersion)){
|
|
87
87
|
assert.deepEqual([...diff.schema_changed].sort(),['machine_fs','native_diff_preview']);
|
|
88
88
|
assert.equal(diff.unchanged.length,41);
|
|
89
89
|
const prior=baseline.tools.find(tool=>tool.name==='machine_fs');
|