deadbyte-mcp 0.15.59 → 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 +31 -26
- package/PROVENANCE.json +8 -8
- package/README.txt +18 -18
- package/RELEASE.json +9 -9
- 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 +21 -5
- package/scripts/deploy-r48.mjs +10 -1
- package/scripts/issue-uac-migration-r153.mjs +12 -5
- package/scripts/r153-prepublish-identity.mjs +3 -3
- package/scripts/request-uac-migration-r153.ps1 +26 -9
- package/scripts/verify-protected-stage-r153.mjs +1 -0
- package/src/activation-lock-r65.mjs +37 -1
- package/src/clean-activation-recovery-r153.mjs +1 -1
- package/src/deadbyte-cli.mjs +16 -6
- 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-r60-host-repin-e2e-contract.test.mjs +30 -0
- package/test/r153-r65-signed-precondition-recovery.test.mjs +58 -0
- 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 +3 -2
- package/test/r154-multipin-recovery.test.mjs +45 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
param(
|
|
2
|
-
[Parameter(Mandatory=$true)][ValidateSet('0.15.
|
|
2
|
+
[Parameter(Mandatory=$true)][ValidateSet('0.15.61')][string]$ExpectedVersion,
|
|
3
3
|
[Parameter(Mandatory=$true)][string]$StateRoot,
|
|
4
4
|
[Parameter(Mandatory=$true)][string]$TarballPath,
|
|
5
5
|
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedTarballSha256,
|
|
@@ -9,6 +9,7 @@ param(
|
|
|
9
9
|
[Parameter(Mandatory=$true)][string]$StderrPath,
|
|
10
10
|
[Parameter(Mandatory=$true)][string]$MarkerReceiptPath,
|
|
11
11
|
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$MarkerReceiptSha256,
|
|
12
|
+
[string]$HostCommandPinsJson = '[]',
|
|
12
13
|
[switch]$Bootstrap
|
|
13
14
|
)
|
|
14
15
|
$ErrorActionPreference = 'Stop'
|
|
@@ -20,6 +21,18 @@ function Require-Absolute([string]$Value,[string]$Label) {
|
|
|
20
21
|
return [IO.Path]::GetFullPath($Value)
|
|
21
22
|
}
|
|
22
23
|
function Quote([string]$Value) { '"' + $Value.Replace('"','\"') + '"' }
|
|
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
|
+
}
|
|
35
|
+
Validate-PinsJson $HostCommandPinsJson
|
|
23
36
|
|
|
24
37
|
$StateRoot = Require-Absolute $StateRoot 'State root'
|
|
25
38
|
$TarballPath = Require-Absolute $TarballPath 'Exact tarball'
|
|
@@ -65,6 +78,7 @@ if (-not $Bootstrap) {
|
|
|
65
78
|
'-ExpectedManifestSha256',$ExpectedManifestSha256,'-ExpectedBootstrapSha256',$ExpectedBootstrapSha256,
|
|
66
79
|
'-StdoutPath',(Quote $StdoutPath),'-StderrPath',(Quote $StderrPath),
|
|
67
80
|
'-MarkerReceiptPath',(Quote $MarkerReceiptPath),'-MarkerReceiptSha256',$MarkerReceiptSha256)
|
|
81
|
+
if ($HostCommandPinsJson -cne '[]') { $arguments += @('-HostCommandPinsJson',(Quote $HostCommandPinsJson)) }
|
|
68
82
|
$child = Start-Process -FilePath $powershell -ArgumentList $arguments -Verb RunAs -Wait -PassThru -WindowStyle Hidden
|
|
69
83
|
if (Test-Path -LiteralPath $StdoutPath) { [Console]::Out.Write([IO.File]::ReadAllText($StdoutPath)) }
|
|
70
84
|
if (Test-Path -LiteralPath $StderrPath) { [Console]::Error.Write([IO.File]::ReadAllText($StderrPath)) }
|
|
@@ -99,7 +113,7 @@ if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging initial ACL normalizati
|
|
|
99
113
|
& $icacls $prefix '/setowner' '*S-1-5-32-544' '/T' '/C' | Out-Null
|
|
100
114
|
if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging initial owner normalization failed' }
|
|
101
115
|
|
|
102
|
-
$metadataUri = 'https://registry.npmjs.org/deadbyte-mcp/0.15.
|
|
116
|
+
$metadataUri = 'https://registry.npmjs.org/deadbyte-mcp/0.15.61'
|
|
103
117
|
$tagsUri = 'https://registry.npmjs.org/-/package/deadbyte-mcp/dist-tags'
|
|
104
118
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
105
119
|
$metadata = Invoke-RestMethod -UseBasicParsing -Uri $metadataUri -Method Get -TimeoutSec 30
|
|
@@ -223,9 +237,11 @@ $preflight = ($preflightOutput -join "`n") | ConvertFrom-Json
|
|
|
223
237
|
if ($preflight.state -ne 'AUTHORITY_UNVERIFIED' -or $preflight.subject_sha256 -notmatch '^[0-9a-f]{64}$') {
|
|
224
238
|
throw "R153 protected migration not applicable: $($preflight.state)"
|
|
225
239
|
}
|
|
226
|
-
|
|
227
|
-
-StdoutPath
|
|
228
|
-
-MarkerReceiptSha256
|
|
240
|
+
$helperArgs = @('-Elevated','-NodePath',$node,'-DeployScript',$deploy,'-StateRoot',$StateRoot,
|
|
241
|
+
'-StdoutPath',$StdoutPath,'-StderrPath',$StderrPath,'-MarkerReceiptPath',$MarkerReceiptPath,
|
|
242
|
+
'-MarkerReceiptSha256',$MarkerReceiptSha256,'-PreflightSubjectSha256',$preflight.subject_sha256)
|
|
243
|
+
if ($HostCommandPinsJson -cne '[]') { $helperArgs += @('-HostCommandPinsJson',$HostCommandPinsJson) }
|
|
244
|
+
& $helper @helperArgs
|
|
229
245
|
$helperExit = $LASTEXITCODE
|
|
230
246
|
try { Remove-Item -LiteralPath $prefix -Recurse -Force -ErrorAction Stop }
|
|
231
247
|
catch { [Console]::Error.WriteLine('R153_STAGE_CLEANUP_WARNING: protected stage retained for operator reconciliation') }
|
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,11 +25,15 @@ 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;}
|
|
33
|
+
if(arg==='--host-command-pin'){
|
|
34
|
+
const value=String(argv[++index]??'');
|
|
35
|
+
out.hostCommandPins.push(value);continue;
|
|
36
|
+
}
|
|
32
37
|
if(arg==='--marker-reconciliation-receipt'){
|
|
33
38
|
if(out.markerReceiptPath)throw new Error('duplicate --marker-reconciliation-receipt');
|
|
34
39
|
const value=argv[++index];if(!value)throw new Error('--marker-reconciliation-receipt requires a path');
|
|
@@ -70,6 +75,8 @@ function parseDeployArgs(argv){
|
|
|
70
75
|
const uacFields=[out.migrationNonce,out.migrationIssuedAt,out.migrationExpiresAt,out.uacApprovalPath,out.uacApprovalSha256];
|
|
71
76
|
if(out.uacLegacyMigration&&uacFields.some(value=>!value))throw new Error('UAC migration requires nonce, lifetime, and approval receipt');
|
|
72
77
|
if(!out.uacLegacyMigration&&uacFields.some(value=>value))throw new Error('UAC migration arguments require --uac-legacy-migration');
|
|
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');
|
|
73
80
|
return out;
|
|
74
81
|
}
|
|
75
82
|
const deployArgs=parseDeployArgs(process.argv.slice(2));
|
|
@@ -294,6 +301,7 @@ if(deployArgs.uacLegacyMigration){
|
|
|
294
301
|
predecessorPolicySha256:markerReceipt.predecessor_policy_sha256,
|
|
295
302
|
preflightSubjectSha256:preflight.subject_sha256,nodeSha256:await sha(process.execPath),
|
|
296
303
|
deployScriptSha256:await sha(fileURLToPath(import.meta.url)),
|
|
304
|
+
hostCommandPins:deployArgs.hostCommandPins,
|
|
297
305
|
nonce:deployArgs.migrationNonce,issuedAt:deployArgs.migrationIssuedAt,
|
|
298
306
|
expiresAt:deployArgs.migrationExpiresAt
|
|
299
307
|
});
|
|
@@ -313,6 +321,7 @@ const args=[
|
|
|
313
321
|
'--bridge-root',bridgeRoot,'--bridge-script-sha256',await sha(bridgeScript),
|
|
314
322
|
'--enable-machine-fs','--enable-observation','--enable-desktop','--enable-autonomous-supervisor','--recover-offline-predecessor'
|
|
315
323
|
];
|
|
324
|
+
args.push(...hostCommandPinArgs(deployArgs.hostCommandPins));
|
|
316
325
|
let result=null,verification=null,activationOutcomePath=null;
|
|
317
326
|
let continuityEngine=null,continuityUpdateId=null;
|
|
318
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,6 +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
|
+
[string]$HostCommandPinsJson = '[]',
|
|
10
11
|
[switch]$Elevated
|
|
11
12
|
)
|
|
12
13
|
$ErrorActionPreference = 'Stop'
|
|
@@ -20,6 +21,19 @@ function Require-AbsoluteFile([string]$Value,[string]$Label) {
|
|
|
20
21
|
return $item.FullName
|
|
21
22
|
}
|
|
22
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)
|
|
23
37
|
$NodePath = Require-AbsoluteFile $NodePath 'Node executable'
|
|
24
38
|
$DeployScript = Require-AbsoluteFile $DeployScript 'Deploy script'
|
|
25
39
|
$MarkerReceiptPath = Require-AbsoluteFile $MarkerReceiptPath 'Marker reconciliation receipt'
|
|
@@ -99,6 +113,7 @@ if (-not $Elevated) {
|
|
|
99
113
|
# -Verb RunAs is the UseShellExecute parameter set. PowerShell 5.1 rejects
|
|
100
114
|
# -RedirectStandardOutput/-RedirectStandardError in that same Start-Process.
|
|
101
115
|
# The elevated child writes bounded, no-clobber evidence instead.
|
|
116
|
+
if ($HostCommandPinsJson -cne '[]') { $arguments += @('-HostCommandPinsJson',('"' + $HostCommandPinsJson.Replace('"','\"') + '"')) }
|
|
102
117
|
$child = Start-Process -FilePath $powershell -ArgumentList $arguments -Verb RunAs -Wait -PassThru -WindowStyle Hidden
|
|
103
118
|
if (Test-Path -LiteralPath $StdoutPath) { [Console]::Out.Write([IO.File]::ReadAllText($StdoutPath)) }
|
|
104
119
|
if (Test-Path -LiteralPath $StderrPath) { [Console]::Error.Write([IO.File]::ReadAllText($StderrPath)) }
|
|
@@ -121,7 +136,7 @@ try {
|
|
|
121
136
|
try {
|
|
122
137
|
$issuerOutput = @(& $NodePath $issuerScript '--state-root' $env:DEADBYTE_STATE_ROOT `
|
|
123
138
|
'--marker-receipt' $MarkerReceiptPath '--marker-receipt-sha256' $MarkerReceiptSha256 `
|
|
124
|
-
'--preflight-subject-sha256' $PreflightSubjectSha256 2>&1)
|
|
139
|
+
'--preflight-subject-sha256' $PreflightSubjectSha256 '--host-command-pins-json' $HostCommandPinsJson 2>&1)
|
|
125
140
|
$issuerExit = $LASTEXITCODE
|
|
126
141
|
} finally { $ErrorActionPreference = $priorErrorAction }
|
|
127
142
|
$issuerText = (@($issuerOutput) | ForEach-Object { [string]$_ }) -join "`n"
|
|
@@ -135,14 +150,16 @@ try {
|
|
|
135
150
|
$priorErrorAction = $ErrorActionPreference
|
|
136
151
|
$ErrorActionPreference = 'Continue'
|
|
137
152
|
try {
|
|
138
|
-
$
|
|
139
|
-
'--marker-reconciliation-receipt'
|
|
140
|
-
'--marker-reconciliation-receipt-sha256'
|
|
141
|
-
'--migration-nonce'
|
|
142
|
-
'--migration-issued-at'
|
|
143
|
-
'--migration-expires-at'
|
|
144
|
-
'--uac-approval-receipt'
|
|
145
|
-
'--uac-approval-receipt-sha256'
|
|
153
|
+
$deployArgs = @('--uac-legacy-migration',
|
|
154
|
+
'--marker-reconciliation-receipt',$MarkerReceiptPath,
|
|
155
|
+
'--marker-reconciliation-receipt-sha256',$MarkerReceiptSha256,
|
|
156
|
+
'--migration-nonce',$issued.nonce,
|
|
157
|
+
'--migration-issued-at',$issued.issued_at_utc,
|
|
158
|
+
'--migration-expires-at',$issued.expires_at_utc,
|
|
159
|
+
'--uac-approval-receipt',$issued.receipt_path,
|
|
160
|
+
'--uac-approval-receipt-sha256',$issued.receipt_sha256)
|
|
161
|
+
foreach ($pin in $HostCommandPins) { $deployArgs += @('--host-command-pin',[string]$pin) }
|
|
162
|
+
$deployOutput = @(& $NodePath $DeployScript @deployArgs 2>&1)
|
|
146
163
|
$deployExit = $LASTEXITCODE
|
|
147
164
|
} finally { $ErrorActionPreference = $priorErrorAction }
|
|
148
165
|
$deployText = (@($deployOutput) | ForEach-Object { [string]$_ }) -join "`n"
|
|
@@ -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');
|
|
@@ -85,6 +85,41 @@ async function isSafeTerminalActivation(outcome,lock,stateRoot){
|
|
|
85
85
|
return true;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
async function isSafeSignedPreconditionReject(outcome,lock,stateRoot){
|
|
89
|
+
if(!outcomeMatchesLock(outcome,lock)||outcome?.outcome?.state!=='ACTIVATION_OUTCOME_UNKNOWN'||
|
|
90
|
+
outcome.update_id!==null||outcome.state_path!==null||outcome.continuity_update_id!==null||
|
|
91
|
+
typeof outcome.candidate_version!=='string'||!HEX64.test(String(outcome.candidate_manifest_sha256??''))||
|
|
92
|
+
typeof outcome.predecessor_release_id!=='string')return false;
|
|
93
|
+
const lockTime=Date.parse(lock.created_at_utc),observedTime=Date.parse(outcome.observed_at_utc);
|
|
94
|
+
if(!Number.isFinite(observedTime)||observedTime<lockTime||observedTime-lockTime>120000)return false;
|
|
95
|
+
const updatesDir=path.join(path.resolve(stateRoot),'evidence','updates');
|
|
96
|
+
let current,publicKey,files;
|
|
97
|
+
try{
|
|
98
|
+
current=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
|
|
99
|
+
publicKey=createPublicKey(await readFile(path.join(stateRoot,'trust','ed25519-public.pem')));
|
|
100
|
+
files=await readdir(updatesDir,{withFileTypes:true});
|
|
101
|
+
}catch{return false;}
|
|
102
|
+
if(publicKey.asymmetricKeyType!=='ed25519'||current?.release_id!==outcome.predecessor_release_id)return false;
|
|
103
|
+
const prefix='r27-'+outcome.candidate_manifest_sha256.slice(0,16)+'-';
|
|
104
|
+
const candidates=files.filter(f=>f.name.startsWith(prefix)&&/^r27-[a-f0-9]{16}-[a-z0-9-]+$/.test(f.name));
|
|
105
|
+
if(candidates.length!==1||!candidates[0].isDirectory()||candidates[0].isSymbolicLink())return false;
|
|
106
|
+
let signed;
|
|
107
|
+
try{signed=JSON.parse(await readFile(path.join(updatesDir,candidates[0].name,'failure-receipt.json'),'utf8'));}
|
|
108
|
+
catch{return false;}
|
|
109
|
+
if(!verifySlotUpdateEnvelope(signed,publicKey))return false;
|
|
110
|
+
const body=signed.body,failedTime=Date.parse(body?.failed_at_utc??'');
|
|
111
|
+
if(body?.schema!=='deadbyte.r27-slot-update-failure.v1'||
|
|
112
|
+
body.update_id!==candidates[0].name||body.release_version!==outcome.candidate_version||
|
|
113
|
+
body.candidate_manifest_sha256!==outcome.candidate_manifest_sha256||
|
|
114
|
+
body.result!=='PRECONDITION_REJECTED'||body.phase!=='initial'||
|
|
115
|
+
body.attempted_pointer!==null||body.rollback?.attempted!==false||
|
|
116
|
+
!Array.isArray(body.rollback?.errors)||body.rollback.errors.length!==0||
|
|
117
|
+
!/^host command 'powershell_script' executable pin drift detected;/.test(String(body.error?.message??''))||
|
|
118
|
+
!Number.isFinite(failedTime)||failedTime<lockTime||failedTime>observedTime||
|
|
119
|
+
!samePointerIdentity(body.prior_pointer,current))return false;
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
88
123
|
async function findTerminalOutcome(updatesDir,lock,stateRoot){
|
|
89
124
|
const names=(await readdir(updatesDir)).filter(name=>/^r65-activation-outcome-.*\.json$/.test(name)).sort();
|
|
90
125
|
if(names.length>MAX_OUTCOME_SCAN)throw new Error('activation outcome inventory exceeds safe scan budget');
|
|
@@ -95,7 +130,8 @@ async function findTerminalOutcome(updatesDir,lock,stateRoot){
|
|
|
95
130
|
catch{continue;}
|
|
96
131
|
if(value?.claim_operation_id!==lock.operation_id)continue;
|
|
97
132
|
if(value?.preflight_subject_sha256!==lock.subject_sha256){foreign=true;continue;}
|
|
98
|
-
if(isSafePreMutationReject(value,lock)||await isSafeTerminalActivation(value,lock,stateRoot)
|
|
133
|
+
if(isSafePreMutationReject(value,lock)||await isSafeTerminalActivation(value,lock,stateRoot)||
|
|
134
|
+
await isSafeSignedPreconditionReject(value,lock,stateRoot))matched=true;
|
|
99
135
|
else unknown=true;
|
|
100
136
|
}
|
|
101
137
|
return {matched,foreign,unknown};
|
|
@@ -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,
|
|
@@ -181,7 +182,8 @@ async function synchronizeForegroundRuntime(ctx,{displayMode='unicode',migration
|
|
|
181
182
|
'-ExpectedTarballSha256',artifactSha256,'-ExpectedManifestSha256',packagedManifestSha256,
|
|
182
183
|
'-ExpectedBootstrapSha256',bootstrapMatch[1],
|
|
183
184
|
'-StdoutPath',stdoutPath,'-StderrPath',stderrPath,
|
|
184
|
-
'-MarkerReceiptPath',migrationContext.receipt_path,'-MarkerReceiptSha256',migrationContext.receipt_sha256
|
|
185
|
+
'-MarkerReceiptPath',migrationContext.receipt_path,'-MarkerReceiptSha256',migrationContext.receipt_sha256,
|
|
186
|
+
...(hostCommandPins.length?['-HostCommandPinsJson',encodeHostCommandPins(hostCommandPins)]:[])],{
|
|
185
187
|
cwd:CURRENT_PACKAGE_ROOT,env:windowsPowerShellEnvironment(process.env),encoding:'utf8',windowsHide:true,shell:false,
|
|
186
188
|
timeout:REMOTE_UPDATE_TIMEOUT_MS,maxBuffer:16*1024*1024
|
|
187
189
|
});
|
|
@@ -743,7 +745,7 @@ async function releaseRemoteSession(ctx,marker,{displayMode='unicode'}={}){
|
|
|
743
745
|
if(deletePinnedRemoteMarker(remoteSessionMarker(ctx.root),snapshot.sha256)!=='REMOVED')
|
|
744
746
|
throw new Error('remote session ownership marker replaced before native deletion; marker retained');
|
|
745
747
|
}
|
|
746
|
-
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=[]}={}){
|
|
747
749
|
const display=prepareRemoteConsole();
|
|
748
750
|
const displayMode=display.mode;
|
|
749
751
|
const ctx=await remoteContext();
|
|
@@ -755,7 +757,7 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
755
757
|
startupLock.assertHeld();
|
|
756
758
|
const migrationContext=await recoverRemoteSession(ctx,{displayMode});
|
|
757
759
|
startupLock.assertHeld();
|
|
758
|
-
await synchronizeForegroundRuntime(ctx,{displayMode,migrationContext});
|
|
760
|
+
await synchronizeForegroundRuntime(ctx,{displayMode,migrationContext,hostCommandPins});
|
|
759
761
|
startupLock.assertHeld();
|
|
760
762
|
marker=await claimRemoteSession(ctx);
|
|
761
763
|
startupLock.assertHeld();
|
|
@@ -869,11 +871,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
869
871
|
}
|
|
870
872
|
async function runRemote(args){
|
|
871
873
|
let verbose=false,trace=false,filterExpression='',resumeCursor=null,mode='default';
|
|
874
|
+
const hostCommandPinValues=[];
|
|
872
875
|
const positional=[];
|
|
873
876
|
for(let index=0;index<args.length;index++){
|
|
874
877
|
const arg=args[index];
|
|
875
878
|
if(arg==='--verbose'||arg==='-v'){verbose=true;continue;}
|
|
876
879
|
if(arg==='--trace'){trace=true;continue;}
|
|
880
|
+
if(arg==='--host-command-pin'){
|
|
881
|
+
const value=String(args[++index]??'');
|
|
882
|
+
hostCommandPinValues.push(value);continue;
|
|
883
|
+
}
|
|
877
884
|
if(['--operator','--desktop','--host','--full'].includes(arg)){
|
|
878
885
|
if(mode!=='default') throw new Error('remote authority modes are mutually exclusive');
|
|
879
886
|
mode=arg.slice(2);continue;
|
|
@@ -890,8 +897,11 @@ async function runRemote(args){
|
|
|
890
897
|
}
|
|
891
898
|
positional.push(arg);
|
|
892
899
|
}
|
|
893
|
-
|
|
894
|
-
|
|
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;}
|
|
895
905
|
if(presentation) throw new Error('remote presentation flags are valid only for foreground session');
|
|
896
906
|
if(positional.length>1) throw new Error('remote accepts one controller command');
|
|
897
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/);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
import {spawnSync} from 'node:child_process';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
const root=new URL('../',import.meta.url);
|
|
7
|
+
const source=async path=>readFile(new URL(path,root),'utf8');
|
|
8
|
+
|
|
9
|
+
test('R60 explicit host-command pin is owner supplied and forwarded end to end',async()=>{
|
|
10
|
+
const [cli,bootstrap,helper,deploy]=await Promise.all([
|
|
11
|
+
source('src/deadbyte-cli.mjs'),source('scripts/bootstrap-uac-migration-r153.ps1'),
|
|
12
|
+
source('scripts/request-uac-migration-r153.ps1'),source('scripts/deploy-r48.mjs')]);
|
|
13
|
+
assert.match(cli,/arg===['"]--host-command-pin['"]/, 'remote CLI must parse a bounded owner-supplied pin');
|
|
14
|
+
assert.match(cli,/hostCommandPin/, 'remote must preserve the parsed pin');
|
|
15
|
+
assert.match(bootstrap,/HostCommandPin/, 'protected UAC bootstrap must receive the owner pin');
|
|
16
|
+
assert.match(helper,/HostCommandPin/, 'protected helper must carry the pin without shell injection');
|
|
17
|
+
assert.match(deploy,/hostCommandPin/, 'deploy must pass pin to R27 before activation');
|
|
18
|
+
assert.match(deploy,/explicit host repin requires the elevated UAC migration/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('R60 malformed, unrelated, and duplicate pins fail before remote context or activation',()=>{
|
|
22
|
+
const cli=fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url));
|
|
23
|
+
const cases=[['powershell_script=notahash'],['other_command='+'a'.repeat(64)],
|
|
24
|
+
['powershell_script='+'a'.repeat(64),'--host-command-pin','powershell_script='+'b'.repeat(64)]];
|
|
25
|
+
for(const args of cases){
|
|
26
|
+
const p=spawnSync(process.execPath,[cli,'remote','--host-command-pin',...args],{encoding:'utf8',timeout:8000});
|
|
27
|
+
assert.equal(p.status,1,JSON.stringify(args));
|
|
28
|
+
assert.match(p.stderr,/remote --host-command-pin/);
|
|
29
|
+
}
|
|
30
|
+
});
|