deadbyte-mcp 0.15.55 → 0.15.59
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 +36 -23
- package/PROVENANCE.json +11 -11
- package/README.txt +18 -18
- package/RELEASE.json +24 -8
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bridge/package.json +1 -1
- package/package.json +1 -1
- package/scripts/bootstrap-uac-migration-r153.ps1 +232 -0
- package/scripts/deploy-r48.mjs +133 -11
- package/scripts/issue-uac-migration-r153.mjs +107 -0
- package/scripts/r153-prepublish-identity.mjs +57 -0
- package/scripts/request-uac-migration-r153.ps1 +165 -0
- package/scripts/verify-protected-stage-r153.mjs +20 -0
- package/src/activation-preflight-r65.mjs +2 -1
- package/src/clean-activation-recovery-r153.mjs +297 -0
- package/src/deadbyte-cli.mjs +925 -720
- package/src/foreground-process-identity.mjs +31 -0
- package/src/legacy-offline-policy-proof-r146.mjs +31 -10
- package/src/runtime-update.mjs +19 -4
- package/src/uac-approval-contract-r153.mjs +104 -0
- package/src/version.mjs +1 -1
- package/test/commander-protocol-r47.test.mjs +6 -1
- package/test/r130-release-security-contract.test.mjs +8 -7
- package/test/r132-release-identity.test.mjs +8 -7
- package/test/r146-legacy-offline-policy-proof.test.mjs +31 -1
- package/test/r153-clean-activation-recovery.test.mjs +213 -0
- package/test/r153-prepublish-identity.test.mjs +40 -0
- package/test/r153-restricted-reused-pid.test.mjs +31 -0
- package/test/r153-staging-leaf-acl.test.mjs +47 -0
- package/test/r153-uac-approval-contract.test.mjs +92 -0
- package/test/r153-uac-installed-manifest-contract.test.mjs +19 -0
- package/test/r153-uac-launch-parameter-contract.test.mjs +59 -0
- package/test/r58-concurrent-recovery.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r65-activation-orchestration.test.mjs +20 -0
- package/test/remote-session-cli.test.mjs +1 -1
- package/node_modules/which/CHANGELOG.md +0 -166
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory=$true)][ValidateSet('0.15.59')][string]$ExpectedVersion,
|
|
3
|
+
[Parameter(Mandatory=$true)][string]$StateRoot,
|
|
4
|
+
[Parameter(Mandatory=$true)][string]$TarballPath,
|
|
5
|
+
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedTarballSha256,
|
|
6
|
+
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedManifestSha256,
|
|
7
|
+
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedBootstrapSha256,
|
|
8
|
+
[Parameter(Mandatory=$true)][string]$StdoutPath,
|
|
9
|
+
[Parameter(Mandatory=$true)][string]$StderrPath,
|
|
10
|
+
[Parameter(Mandatory=$true)][string]$MarkerReceiptPath,
|
|
11
|
+
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$MarkerReceiptSha256,
|
|
12
|
+
[switch]$Bootstrap
|
|
13
|
+
)
|
|
14
|
+
$ErrorActionPreference = 'Stop'
|
|
15
|
+
Set-StrictMode -Version Latest
|
|
16
|
+
|
|
17
|
+
function Full([string]$Value) { [IO.Path]::GetFullPath($Value).TrimEnd('\') }
|
|
18
|
+
function Require-Absolute([string]$Value,[string]$Label) {
|
|
19
|
+
if (-not [IO.Path]::IsPathRooted($Value)) { throw "$Label must be absolute" }
|
|
20
|
+
return [IO.Path]::GetFullPath($Value)
|
|
21
|
+
}
|
|
22
|
+
function Quote([string]$Value) { '"' + $Value.Replace('"','\"') + '"' }
|
|
23
|
+
|
|
24
|
+
$StateRoot = Require-Absolute $StateRoot 'State root'
|
|
25
|
+
$TarballPath = Require-Absolute $TarballPath 'Exact tarball'
|
|
26
|
+
$StdoutPath = Require-Absolute $StdoutPath 'Stdout evidence'
|
|
27
|
+
$StderrPath = Require-Absolute $StderrPath 'Stderr evidence'
|
|
28
|
+
$MarkerReceiptPath = Require-Absolute $MarkerReceiptPath 'Marker receipt'
|
|
29
|
+
$expectedStateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'
|
|
30
|
+
if ((Full $StateRoot) -ine (Full $expectedStateRoot)) { throw 'R153 bootstrap requires the owning user state root' }
|
|
31
|
+
$evidenceDirectory = Join-Path $StateRoot 'evidence\legacy-migration'
|
|
32
|
+
foreach ($file in @($StdoutPath,$StderrPath,$MarkerReceiptPath)) {
|
|
33
|
+
if ((Full (Split-Path -Parent $file)) -ine (Full $evidenceDirectory)) {
|
|
34
|
+
throw 'R153 bootstrap evidence path escapes legacy-migration root'
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
$marker = Get-Item -LiteralPath $MarkerReceiptPath -Force
|
|
38
|
+
if ($marker.PSIsContainer -or (($marker.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
|
|
39
|
+
throw 'R153 bootstrap marker receipt must be a regular file'
|
|
40
|
+
}
|
|
41
|
+
$tarball = Get-Item -LiteralPath $TarballPath -Force
|
|
42
|
+
if ($tarball.PSIsContainer -or (($tarball.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
|
|
43
|
+
throw 'R153 bootstrap tarball must be a regular file'
|
|
44
|
+
}
|
|
45
|
+
if ((Get-FileHash -LiteralPath $TarballPath -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedTarballSha256) {
|
|
46
|
+
throw 'R153 bootstrap exact tarball digest mismatch'
|
|
47
|
+
}
|
|
48
|
+
if ((Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedBootstrapSha256) {
|
|
49
|
+
throw 'R153 bootstrap script differs from the approved tarball manifest'
|
|
50
|
+
}
|
|
51
|
+
if ((Get-FileHash -LiteralPath $MarkerReceiptPath -Algorithm SHA256).Hash.ToLowerInvariant() -cne $MarkerReceiptSha256) {
|
|
52
|
+
throw 'R153 bootstrap marker receipt digest mismatch'
|
|
53
|
+
}
|
|
54
|
+
if ((Test-Path -LiteralPath $StdoutPath) -or (Test-Path -LiteralPath $StderrPath)) {
|
|
55
|
+
throw 'R153 bootstrap evidence files must not pre-exist'
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
|
|
59
|
+
$isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
60
|
+
if (-not $Bootstrap) {
|
|
61
|
+
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
|
62
|
+
$arguments = @('-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',(Quote $PSCommandPath),
|
|
63
|
+
'-Bootstrap','-ExpectedVersion',$ExpectedVersion,'-StateRoot',(Quote $StateRoot),
|
|
64
|
+
'-TarballPath',(Quote $TarballPath),'-ExpectedTarballSha256',$ExpectedTarballSha256,
|
|
65
|
+
'-ExpectedManifestSha256',$ExpectedManifestSha256,'-ExpectedBootstrapSha256',$ExpectedBootstrapSha256,
|
|
66
|
+
'-StdoutPath',(Quote $StdoutPath),'-StderrPath',(Quote $StderrPath),
|
|
67
|
+
'-MarkerReceiptPath',(Quote $MarkerReceiptPath),'-MarkerReceiptSha256',$MarkerReceiptSha256)
|
|
68
|
+
$child = Start-Process -FilePath $powershell -ArgumentList $arguments -Verb RunAs -Wait -PassThru -WindowStyle Hidden
|
|
69
|
+
if (Test-Path -LiteralPath $StdoutPath) { [Console]::Out.Write([IO.File]::ReadAllText($StdoutPath)) }
|
|
70
|
+
if (Test-Path -LiteralPath $StderrPath) { [Console]::Error.Write([IO.File]::ReadAllText($StderrPath)) }
|
|
71
|
+
exit $child.ExitCode
|
|
72
|
+
}
|
|
73
|
+
if (-not $isAdmin) { throw 'R153 protected bootstrap requires Windows UAC elevation' }
|
|
74
|
+
|
|
75
|
+
# The elevated bootstrap has one fixed job: independently fetch the exact npm
|
|
76
|
+
# registry artifact, extract it into a fresh protected Program Files directory,
|
|
77
|
+
# verify every manifest byte with PowerShell/.NET (no candidate JavaScript yet),
|
|
78
|
+
# protect the ACL, obtain the R89 trust verdict, then transfer control to the
|
|
79
|
+
# protected sibling helper. It accepts no package name, registry, executable,
|
|
80
|
+
# command, destination, or authority vector.
|
|
81
|
+
$node = Join-Path $env:ProgramFiles 'nodejs\node.exe'
|
|
82
|
+
$systemTar = Join-Path $env:SystemRoot 'System32\tar.exe'
|
|
83
|
+
foreach ($file in @($node,$systemTar)) {
|
|
84
|
+
$item = Get-Item -LiteralPath $file -Force
|
|
85
|
+
if ($item.PSIsContainer -or (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
|
|
86
|
+
throw 'R153 protected bootstrap executable identity invalid'
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
$stageNonce = [Guid]::NewGuid().ToString('N')
|
|
90
|
+
$prefix = Join-Path $env:ProgramFiles ('DEADBYTE-R153-Stage-' + $stageNonce)
|
|
91
|
+
$candidate = Join-Path $prefix 'package'
|
|
92
|
+
if (Test-Path -LiteralPath $prefix) { throw 'R153 fresh protected staging path collision' }
|
|
93
|
+
$existingStages = @(Get-ChildItem -LiteralPath $env:ProgramFiles -Directory -Force -Filter 'DEADBYTE-R153-Stage-*' -ErrorAction Stop)
|
|
94
|
+
if ($existingStages.Count -ge 4) { throw 'R153 protected staging residue limit reached; operator reconciliation required' }
|
|
95
|
+
New-Item -ItemType Directory -Path $prefix -ErrorAction Stop | Out-Null
|
|
96
|
+
$icacls = Join-Path $env:SystemRoot 'System32\icacls.exe'
|
|
97
|
+
& $icacls $prefix '/inheritance:r' '/grant:r' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-18:(OI)(CI)(F)' '/T' '/C' | Out-Null
|
|
98
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging initial ACL normalization failed' }
|
|
99
|
+
& $icacls $prefix '/setowner' '*S-1-5-32-544' '/T' '/C' | Out-Null
|
|
100
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging initial owner normalization failed' }
|
|
101
|
+
|
|
102
|
+
$metadataUri = 'https://registry.npmjs.org/deadbyte-mcp/0.15.59'
|
|
103
|
+
$tagsUri = 'https://registry.npmjs.org/-/package/deadbyte-mcp/dist-tags'
|
|
104
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
105
|
+
$metadata = Invoke-RestMethod -UseBasicParsing -Uri $metadataUri -Method Get -TimeoutSec 30
|
|
106
|
+
$tags = Invoke-RestMethod -UseBasicParsing -Uri $tagsUri -Method Get -TimeoutSec 30
|
|
107
|
+
if ([string]$metadata.name -cne 'deadbyte-mcp' -or [string]$metadata.version -cne $ExpectedVersion) {
|
|
108
|
+
throw 'R153 npm registry package identity mismatch'
|
|
109
|
+
}
|
|
110
|
+
if ([string]$tags.latest -cne $ExpectedVersion) { throw 'R153 npm latest tag mismatch' }
|
|
111
|
+
$registryTarballUri = [Uri]([string]$metadata.dist.tarball)
|
|
112
|
+
if ($registryTarballUri.Scheme -cne 'https' -or $registryTarballUri.Host -cne 'registry.npmjs.org') {
|
|
113
|
+
throw 'R153 npm registry tarball origin invalid'
|
|
114
|
+
}
|
|
115
|
+
$registryTarball = Join-Path $prefix 'registry.tgz'
|
|
116
|
+
Invoke-WebRequest -UseBasicParsing -Uri $registryTarballUri.AbsoluteUri -OutFile $registryTarball -TimeoutSec 60
|
|
117
|
+
if ((Get-FileHash -LiteralPath $registryTarball -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedTarballSha256) {
|
|
118
|
+
throw 'R153 live npm registry tarball SHA-256 mismatch'
|
|
119
|
+
}
|
|
120
|
+
if ((Get-FileHash -LiteralPath $registryTarball -Algorithm SHA1).Hash.ToLowerInvariant() -cne ([string]$metadata.dist.shasum).ToLowerInvariant()) {
|
|
121
|
+
throw 'R153 live npm registry tarball shasum mismatch'
|
|
122
|
+
}
|
|
123
|
+
$integrity = [string]$metadata.dist.integrity
|
|
124
|
+
if ($integrity -cnotmatch '^sha512-(.+)$') { throw 'R153 npm registry integrity missing or unsupported' }
|
|
125
|
+
$expectedIntegrityBase64 = $integrity.Substring(7)
|
|
126
|
+
$sha512 = [Security.Cryptography.SHA512]::Create()
|
|
127
|
+
try { $sha512Base64 = [Convert]::ToBase64String($sha512.ComputeHash([IO.File]::ReadAllBytes($registryTarball))) }
|
|
128
|
+
finally { $sha512.Dispose() }
|
|
129
|
+
if ($sha512Base64 -cne $expectedIntegrityBase64) { throw 'R153 live npm registry tarball integrity mismatch' }
|
|
130
|
+
|
|
131
|
+
$members = @(& $systemTar '-tf' $registryTarball)
|
|
132
|
+
if ($LASTEXITCODE -ne 0 -or $members.Count -lt 1) { throw 'R153 exact tarball inventory failed' }
|
|
133
|
+
foreach ($member in $members) {
|
|
134
|
+
$normalized = ([string]$member).Replace('\','/')
|
|
135
|
+
if (-not $normalized.StartsWith('package/') -or $normalized.StartsWith('/') -or
|
|
136
|
+
$normalized.Contains([char]0) -or $normalized.Split('/') -contains '..') {
|
|
137
|
+
throw 'R153 exact tarball contains unsafe member path'
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
& $systemTar '-xzf' $registryTarball '-C' $prefix
|
|
141
|
+
if ($LASTEXITCODE -ne 0) { throw "R153 exact tarball extraction failed exit=$LASTEXITCODE" }
|
|
142
|
+
$manifestFile = Join-Path $candidate 'MANIFEST.SHA256'
|
|
143
|
+
$packageFile = Join-Path $candidate 'package.json'
|
|
144
|
+
$manifestItem = Get-Item -LiteralPath $manifestFile -Force
|
|
145
|
+
if (-not $manifestItem.PSIsContainer -and (($manifestItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -eq 0)) {
|
|
146
|
+
if ((Get-FileHash -LiteralPath $manifestFile -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedManifestSha256) {
|
|
147
|
+
throw 'R153 staged manifest identity mismatch'
|
|
148
|
+
}
|
|
149
|
+
} else { throw 'R153 staged manifest must be a regular file' }
|
|
150
|
+
$package = Get-Content -LiteralPath $packageFile -Raw | ConvertFrom-Json
|
|
151
|
+
if ($package.name -cne 'deadbyte-mcp' -or $package.version -cne $ExpectedVersion) {
|
|
152
|
+
throw 'R153 staged package identity mismatch'
|
|
153
|
+
}
|
|
154
|
+
foreach ($item in Get-ChildItem -LiteralPath $candidate -Recurse -Force) {
|
|
155
|
+
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'R153 staged package contains a reparse point' }
|
|
156
|
+
}
|
|
157
|
+
$expected = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::Ordinal)
|
|
158
|
+
$null = $expected.Add('MANIFEST.SHA256')
|
|
159
|
+
$lineNumber = 0
|
|
160
|
+
foreach ($line in [IO.File]::ReadAllLines($manifestFile)) {
|
|
161
|
+
$lineNumber++
|
|
162
|
+
if ($line.Length -eq 0) { continue }
|
|
163
|
+
if ($line -cnotmatch '^([0-9a-f]{64}) (.+)$') { throw "R153 staged manifest line $lineNumber malformed" }
|
|
164
|
+
$digest = $Matches[1]; $relative = $Matches[2]
|
|
165
|
+
if ($relative.Contains('\') -or $relative.StartsWith('/') -or $relative.Contains([char]0) -or $relative.Split('/') -contains '..') {
|
|
166
|
+
throw 'R153 staged manifest path unsafe'
|
|
167
|
+
}
|
|
168
|
+
if (-not $expected.Add($relative)) { throw 'R153 staged manifest duplicate path' }
|
|
169
|
+
$file = Join-Path $candidate ($relative.Replace('/','\'))
|
|
170
|
+
$entry = Get-Item -LiteralPath $file -Force
|
|
171
|
+
if ($entry.PSIsContainer -or (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
|
|
172
|
+
throw 'R153 staged manifest entry is not a regular file'
|
|
173
|
+
}
|
|
174
|
+
if ((Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash.ToLowerInvariant() -cne $digest) {
|
|
175
|
+
throw "R153 staged manifest byte mismatch: $relative"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
$actual = Get-ChildItem -LiteralPath $candidate -Recurse -Force -File | ForEach-Object {
|
|
179
|
+
$_.FullName.Substring($candidate.Length + 1).Replace('\','/')
|
|
180
|
+
}
|
|
181
|
+
foreach ($relative in $actual) { if (-not $expected.Contains($relative)) { throw "R153 staged package unexpected file: $relative" } }
|
|
182
|
+
if ($actual.Count -ne $expected.Count) { throw 'R153 staged package file-count mismatch' }
|
|
183
|
+
& $icacls $prefix '/inheritance:r' '/grant:r' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-18:(OI)(CI)(F)' '/T' '/C' | Out-Null
|
|
184
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging final ACL normalization failed' }
|
|
185
|
+
& $icacls $prefix '/setowner' '*S-1-5-32-544' '/T' '/C' | Out-Null
|
|
186
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging final owner normalization failed' }
|
|
187
|
+
# /inheritance:r with /T strips inherited Allow ACEs from leaf files.
|
|
188
|
+
# The directory OI/CI grant above does not become a direct leaf ACE on Windows.
|
|
189
|
+
# Restore direct access for Administrators and SYSTEM on every existing member;
|
|
190
|
+
# never grant Users/Everyone, and independently re-hash all package files below.
|
|
191
|
+
& $icacls $prefix '/grant' '*S-1-5-32-544:(F)' '*S-1-5-18:(F)' '/T' '/C' | Out-Null
|
|
192
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging leaf ACL restoration failed' }
|
|
193
|
+
|
|
194
|
+
# Re-read every manifest-bound byte after ACL normalization. The first
|
|
195
|
+
# candidate JavaScript below cannot run against bytes changed in the ACL gap.
|
|
196
|
+
foreach ($line in [IO.File]::ReadAllLines($manifestFile)) {
|
|
197
|
+
if ($line.Length -eq 0) { continue }
|
|
198
|
+
if ($line -cnotmatch '^([0-9a-f]{64}) (.+)$') { throw 'R153 staged manifest changed after ACL normalization' }
|
|
199
|
+
$digest = $Matches[1]; $relative = $Matches[2]
|
|
200
|
+
$file = Join-Path $candidate ($relative.Replace('/','\'))
|
|
201
|
+
$entry = Get-Item -LiteralPath $file -Force
|
|
202
|
+
if ($entry.PSIsContainer -or (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) -or
|
|
203
|
+
(Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash.ToLowerInvariant() -cne $digest) {
|
|
204
|
+
throw "R153 staged package post-ACL verification failed: $relative"
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
foreach ($name in @('NODE_OPTIONS','NODE_PATH','NODE_REPL_EXTERNAL_MODULE','NPM_CONFIG_USERCONFIG','npm_config_userconfig')) {
|
|
209
|
+
Remove-Item -LiteralPath ('Env:' + $name) -ErrorAction SilentlyContinue
|
|
210
|
+
}
|
|
211
|
+
$env:DEADBYTE_STATE_ROOT = $StateRoot
|
|
212
|
+
|
|
213
|
+
$helper = Join-Path $candidate 'scripts\request-uac-migration-r153.ps1'
|
|
214
|
+
$deploy = Join-Path $candidate 'scripts\deploy-r48.mjs'
|
|
215
|
+
$trustVerifier = Join-Path $candidate 'scripts\verify-protected-stage-r153.mjs'
|
|
216
|
+
$trustOutput = & $node $trustVerifier
|
|
217
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected stage R89 trust verification failed' }
|
|
218
|
+
$trust = ($trustOutput -join "`n") | ConvertFrom-Json
|
|
219
|
+
if ($trust.trusted -ne $true) { throw 'R153 protected stage R89 verdict not trusted' }
|
|
220
|
+
$preflightOutput = & $node $deploy '--preflight'
|
|
221
|
+
if ($LASTEXITCODE -ne 0) { throw 'R153 protected activation preflight failed' }
|
|
222
|
+
$preflight = ($preflightOutput -join "`n") | ConvertFrom-Json
|
|
223
|
+
if ($preflight.state -ne 'AUTHORITY_UNVERIFIED' -or $preflight.subject_sha256 -notmatch '^[0-9a-f]{64}$') {
|
|
224
|
+
throw "R153 protected migration not applicable: $($preflight.state)"
|
|
225
|
+
}
|
|
226
|
+
& $helper -Elevated -NodePath $node -DeployScript $deploy -StateRoot $StateRoot `
|
|
227
|
+
-StdoutPath $StdoutPath -StderrPath $StderrPath -MarkerReceiptPath $MarkerReceiptPath `
|
|
228
|
+
-MarkerReceiptSha256 $MarkerReceiptSha256 -PreflightSubjectSha256 $preflight.subject_sha256
|
|
229
|
+
$helperExit = $LASTEXITCODE
|
|
230
|
+
try { Remove-Item -LiteralPath $prefix -Recurse -Force -ErrorAction Stop }
|
|
231
|
+
catch { [Console]::Error.WriteLine('R153_STAGE_CLEANUP_WARNING: protected stage retained for operator reconciliation') }
|
|
232
|
+
exit $helperExit
|
package/scripts/deploy-r48.mjs
CHANGED
|
@@ -12,6 +12,9 @@ import {createEpochNativeContinuityEngine} from '../src/activation-continuity-en
|
|
|
12
12
|
import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
|
|
13
13
|
import {observeAuthorityEvidence} from '../src/activation-authority-proof-r75.mjs';
|
|
14
14
|
import {proveLegacyOfflineScopeRepair} from '../src/legacy-offline-policy-proof-r146.mjs';
|
|
15
|
+
import {claimWithOneTimeMigration,verifyNarrowedPolicyGeneration} from '../src/clean-activation-recovery-r153.mjs';
|
|
16
|
+
import {verifyUacApproval,uacPointerSha256,protectedUacReceiptPath,requireTrustedUacInstallation} from '../src/uac-approval-contract-r153.mjs';
|
|
17
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
15
18
|
|
|
16
19
|
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
17
20
|
const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
|
|
@@ -19,10 +22,35 @@ const sha=async f=>createHash('sha256').update(await readFile(f)).digest('hex');
|
|
|
19
22
|
const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
|
|
20
23
|
|
|
21
24
|
function parseDeployArgs(argv){
|
|
22
|
-
const out={preflight:false,approvalPath:null,approvalSha256:null
|
|
25
|
+
const out={preflight:false,approvalPath:null,approvalSha256:null,uacLegacyMigration:false,
|
|
26
|
+
markerReceiptPath:null,markerReceiptSha256:null,migrationNonce:null,migrationIssuedAt:null,
|
|
27
|
+
migrationExpiresAt:null,uacApprovalPath:null,uacApprovalSha256:null};
|
|
23
28
|
for(let index=0;index<argv.length;index++){
|
|
24
29
|
const arg=argv[index];
|
|
25
30
|
if(arg==='--preflight'){if(out.preflight)throw new Error('duplicate --preflight');out.preflight=true;continue;}
|
|
31
|
+
if(arg==='--uac-legacy-migration'){if(out.uacLegacyMigration)throw new Error('duplicate --uac-legacy-migration');out.uacLegacyMigration=true;continue;}
|
|
32
|
+
if(arg==='--marker-reconciliation-receipt'){
|
|
33
|
+
if(out.markerReceiptPath)throw new Error('duplicate --marker-reconciliation-receipt');
|
|
34
|
+
const value=argv[++index];if(!value)throw new Error('--marker-reconciliation-receipt requires a path');
|
|
35
|
+
out.markerReceiptPath=path.resolve(value);continue;
|
|
36
|
+
}
|
|
37
|
+
if(arg==='--marker-reconciliation-receipt-sha256'){
|
|
38
|
+
if(out.markerReceiptSha256)throw new Error('duplicate --marker-reconciliation-receipt-sha256');
|
|
39
|
+
const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--marker-reconciliation-receipt-sha256 requires lowercase SHA-256');
|
|
40
|
+
out.markerReceiptSha256=value;continue;
|
|
41
|
+
}
|
|
42
|
+
if(arg==='--migration-nonce'){
|
|
43
|
+
const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--migration-nonce requires lowercase 32-byte hex');
|
|
44
|
+
out.migrationNonce=value;continue;
|
|
45
|
+
}
|
|
46
|
+
if(arg==='--migration-issued-at'){out.migrationIssuedAt=String(argv[++index]??'');continue;}
|
|
47
|
+
if(arg==='--migration-expires-at'){out.migrationExpiresAt=String(argv[++index]??'');continue;}
|
|
48
|
+
if(arg==='--uac-approval-receipt'){
|
|
49
|
+
const value=argv[++index];if(!value)throw new Error('--uac-approval-receipt requires a path');out.uacApprovalPath=path.resolve(value);continue;
|
|
50
|
+
}
|
|
51
|
+
if(arg==='--uac-approval-receipt-sha256'){
|
|
52
|
+
const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--uac-approval-receipt-sha256 requires lowercase SHA-256');out.uacApprovalSha256=value;continue;
|
|
53
|
+
}
|
|
26
54
|
if(arg==='--policy-migration-approval'){
|
|
27
55
|
if(out.approvalPath)throw new Error('duplicate --policy-migration-approval');
|
|
28
56
|
const value=argv[++index];if(!value)throw new Error('--policy-migration-approval requires a path');
|
|
@@ -36,6 +64,12 @@ function parseDeployArgs(argv){
|
|
|
36
64
|
throw new Error(`unknown deploy argument '${arg}'`);
|
|
37
65
|
}
|
|
38
66
|
if(Boolean(out.approvalPath)!==Boolean(out.approvalSha256))throw new Error('policy migration approval path and SHA-256 are required together');
|
|
67
|
+
if(out.uacLegacyMigration&&out.approvalPath)throw new Error('UAC and owner-signed migration authorization are mutually exclusive');
|
|
68
|
+
if(Boolean(out.markerReceiptPath)!==Boolean(out.markerReceiptSha256))throw new Error('marker receipt path and SHA-256 are required together');
|
|
69
|
+
if(out.uacLegacyMigration!==Boolean(out.markerReceiptPath))throw new Error('UAC migration requires a marker reconciliation receipt');
|
|
70
|
+
const uacFields=[out.migrationNonce,out.migrationIssuedAt,out.migrationExpiresAt,out.uacApprovalPath,out.uacApprovalSha256];
|
|
71
|
+
if(out.uacLegacyMigration&&uacFields.some(value=>!value))throw new Error('UAC migration requires nonce, lifetime, and approval receipt');
|
|
72
|
+
if(!out.uacLegacyMigration&&uacFields.some(value=>value))throw new Error('UAC migration arguments require --uac-legacy-migration');
|
|
39
73
|
return out;
|
|
40
74
|
}
|
|
41
75
|
const deployArgs=parseDeployArgs(process.argv.slice(2));
|
|
@@ -49,10 +83,19 @@ async function writeExclusive(file,value){
|
|
|
49
83
|
const activationClaimManager=createActivationClaimManager({stateRoot:state});
|
|
50
84
|
let activationClaimOperationId=null;
|
|
51
85
|
|
|
86
|
+
let pendingUacConsumption=null;
|
|
52
87
|
async function claimActivation(subjectSha256){
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
const held=await claimWithOneTimeMigration({
|
|
89
|
+
claim:value=>activationClaimManager.claim(value),subjectSha256,
|
|
90
|
+
consume:pendingUacConsumption?async()=>{
|
|
91
|
+
const oneTime=pendingUacConsumption;
|
|
92
|
+
await mkdir(oneTime.consumeDir,{recursive:true});
|
|
93
|
+
await writeExclusive(path.join(oneTime.consumeDir,`${oneTime.nonce}.json`),oneTime.receipt);
|
|
94
|
+
pendingUacConsumption=null;
|
|
95
|
+
}:null
|
|
96
|
+
});
|
|
97
|
+
activationClaimOperationId=held.operation_id;
|
|
98
|
+
return held.release;
|
|
56
99
|
}
|
|
57
100
|
|
|
58
101
|
async function activePolicyPaths(){
|
|
@@ -95,13 +138,26 @@ function controllerCommand(command){
|
|
|
95
138
|
if(run.error||run.status!==0)throw new Error(`activation controller ${command} failed exit=${run.status} code=${run.error?.code??'none'}`);
|
|
96
139
|
}
|
|
97
140
|
|
|
98
|
-
function
|
|
99
|
-
if(
|
|
141
|
+
function isElevatedWindowsProcess(){
|
|
142
|
+
if(process.platform!=='win32')return false;
|
|
143
|
+
const powershell=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
|
|
144
|
+
const command="([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)";
|
|
145
|
+
const run=spawnSync(powershell,['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(command,'utf16le').toString('base64')],{
|
|
146
|
+
encoding:'utf8',windowsHide:true,shell:false,timeout:15000,env:windowsPowerShellEnvironment()});
|
|
147
|
+
return !run.error&&run.status===0&&/^true$/i.test(String(run.stdout).trim());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function legacyOfflineRecovery(approvalPath,approvalSha256,{uacLegacyMigration=false,tarballSha256=null,markerReceipt=null,
|
|
151
|
+
nonce=null,issuedAt=null,expiresAt=null}={}){
|
|
152
|
+
if(!approvalPath&&!uacLegacyMigration)return null;
|
|
153
|
+
const uacAuthorization=uacLegacyMigration?{
|
|
154
|
+
isElevated:isElevatedWindowsProcess(),nonce,issuedAt,expiresAt,tarballSha256,markerReceipt
|
|
155
|
+
}:null;
|
|
100
156
|
return async({observation,preflight_subject_sha256,pointer,expectedParent,candidate,expectedCandidate})=>
|
|
101
157
|
proveLegacyOfflineScopeRepair({stateRoot:state,pointer,expectedParent,candidate,expectedCandidate,
|
|
102
158
|
preflightSubjectSha256:preflight_subject_sha256,observation,policyPaths:observation.policy_paths,
|
|
103
159
|
policySha256:observation.policy_sha256,approvalPath,approvalSha256,
|
|
104
|
-
ownerPublicKeyPath:path.join(state,'trust','r70-owner-public.pem')});
|
|
160
|
+
ownerPublicKeyPath:path.join(state,'trust','r70-owner-public.pem'),uacAuthorization});
|
|
105
161
|
}
|
|
106
162
|
|
|
107
163
|
async function activationPreflight(manifest,pkg,pointer,recovery){
|
|
@@ -116,9 +172,11 @@ async function activationPreflight(manifest,pkg,pointer,recovery){
|
|
|
116
172
|
const proof=await recovery({phase:'preflight',observation:observed,preflight_subject_sha256:result.subject_sha256,
|
|
117
173
|
pointer,expectedParent:release.parent,candidate:{version:pkg.version,manifest_sha256:manifest},
|
|
118
174
|
expectedCandidate:{version:release.version,manifest_sha256:manifest}});
|
|
119
|
-
if(proof?.state==='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'&&proof.
|
|
175
|
+
if(proof?.state==='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'&&proof.migration_authorization_verified===true)
|
|
120
176
|
return {...result,state:'LEGACY_SCOPE_REPAIR_READY',safe_to_activate:true,authority_continuity:'LEGACY_SCOPE_REPAIRED_DISARMED',
|
|
121
|
-
legacy_scope_proof_sha256:proof.subject_sha256,
|
|
177
|
+
legacy_scope_proof_sha256:proof.subject_sha256,
|
|
178
|
+
migration_authorization:proof.migration_authorization,
|
|
179
|
+
migration_authorization_subject_sha256:proof.migration_authorization_subject_sha256,reauth_planes:[]};
|
|
122
180
|
}catch(error){
|
|
123
181
|
return {...result,state:'LEGACY_SCOPE_REPAIR_UNVERIFIED',safe_to_activate:false,
|
|
124
182
|
reason:String(error?.message??error).slice(0,256),authority_continuity:'UNVERIFIED',reauth_planes:[]};
|
|
@@ -178,14 +236,69 @@ async function waitForActivation({handoffPath,expectedVersion,expectedManifest,t
|
|
|
178
236
|
}
|
|
179
237
|
|
|
180
238
|
const manifest=await sha(path.join(root,'MANIFEST.SHA256'));
|
|
181
|
-
const pointer=
|
|
239
|
+
const pointer=parseActivePointer(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
182
240
|
const bridgeRoot=path.join(root,'bridge');
|
|
183
241
|
const bridgeScript=path.join(bridgeRoot,'src','bridge.mjs');
|
|
184
242
|
const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
|
|
185
243
|
const expectedVersion=pkg.version;
|
|
186
244
|
const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
|
|
187
|
-
|
|
245
|
+
let localTarballSha256=null;
|
|
246
|
+
let markerReceipt=null;
|
|
247
|
+
let pendingUacApproval=null;
|
|
248
|
+
if(deployArgs.uacLegacyMigration){
|
|
249
|
+
const localTarball=path.join(state,'evidence','npm-publication',pkg.version,`deadbyte-mcp-${pkg.version}.tgz`);
|
|
250
|
+
localTarballSha256=await sha(localTarball);
|
|
251
|
+
const expectedReceiptRoot=path.resolve(state,'evidence','legacy-migration');
|
|
252
|
+
if(path.resolve(path.dirname(deployArgs.markerReceiptPath)).toLowerCase()!==expectedReceiptRoot.toLowerCase())
|
|
253
|
+
throw new Error('marker reconciliation receipt outside state evidence root');
|
|
254
|
+
const receiptInfo=await lstat(deployArgs.markerReceiptPath);
|
|
255
|
+
if(!receiptInfo.isFile()||receiptInfo.isSymbolicLink())throw new Error('marker reconciliation receipt must be a regular file');
|
|
256
|
+
const receiptBytes=await readFile(deployArgs.markerReceiptPath);
|
|
257
|
+
if(createHash('sha256').update(receiptBytes).digest('hex')!==deployArgs.markerReceiptSha256)
|
|
258
|
+
throw new Error('marker reconciliation receipt hash mismatch');
|
|
259
|
+
markerReceipt=JSON.parse(receiptBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
260
|
+
const expectedApprovalPath=protectedUacReceiptPath(root,deployArgs.migrationNonce);
|
|
261
|
+
if(path.resolve(deployArgs.uacApprovalPath).toLowerCase()!==path.resolve(expectedApprovalPath).toLowerCase())
|
|
262
|
+
throw new Error('UAC approval receipt path not exact candidate sidecar');
|
|
263
|
+
requireTrustedUacInstallation(await inspectTrustedInstallation([expectedApprovalPath]));
|
|
264
|
+
const approvalInfo=await lstat(deployArgs.uacApprovalPath);
|
|
265
|
+
if(!approvalInfo.isFile()||approvalInfo.isSymbolicLink())throw new Error('UAC approval receipt must be a regular file');
|
|
266
|
+
const approvalBytes=await readFile(deployArgs.uacApprovalPath);
|
|
267
|
+
if(createHash('sha256').update(approvalBytes).digest('hex')!==deployArgs.uacApprovalSha256)
|
|
268
|
+
throw new Error('UAC approval receipt hash mismatch');
|
|
269
|
+
const approval=JSON.parse(approvalBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
270
|
+
// The full UAC subject is checked AFTER live preflight, since its hash
|
|
271
|
+
// binds the actual predecessor policies and observed preflight identity.
|
|
272
|
+
if(approval?.schema!=='deadbyte.r153-uac-approval.v1'||
|
|
273
|
+
approval.nonce!==deployArgs.migrationNonce||
|
|
274
|
+
approval.issued_at_utc!==deployArgs.migrationIssuedAt||
|
|
275
|
+
approval.expires_at_utc!==deployArgs.migrationExpiresAt)
|
|
276
|
+
throw new Error('UAC approval receipt command-line binding mismatch');
|
|
277
|
+
pendingUacApproval=approval;
|
|
278
|
+
pendingUacConsumption={consumeDir:path.join(state,'evidence','legacy-migration','consumed'),
|
|
279
|
+
nonce:deployArgs.migrationNonce,
|
|
280
|
+
receipt:{schema:'deadbyte.r153-uac-consumption.v1',nonce:deployArgs.migrationNonce,
|
|
281
|
+
approval_sha256:deployArgs.uacApprovalSha256,marker_receipt_sha256:deployArgs.markerReceiptSha256,
|
|
282
|
+
candidate_manifest_sha256:manifest,created_at_utc:new Date().toISOString()}};
|
|
283
|
+
}
|
|
284
|
+
const recovery=legacyOfflineRecovery(deployArgs.approvalPath,deployArgs.approvalSha256,
|
|
285
|
+
{uacLegacyMigration:deployArgs.uacLegacyMigration,tarballSha256:localTarballSha256,markerReceipt,
|
|
286
|
+
nonce:deployArgs.migrationNonce,issuedAt:deployArgs.migrationIssuedAt,expiresAt:deployArgs.migrationExpiresAt});
|
|
188
287
|
const preflight=await activationPreflight(manifest,pkg,pointer,recovery);
|
|
288
|
+
if(deployArgs.uacLegacyMigration){
|
|
289
|
+
verifyUacApproval(pendingUacApproval,{
|
|
290
|
+
stateRoot:state,candidateRoot:root,candidateVersion:pkg.version,
|
|
291
|
+
candidateManifestSha256:manifest,tarballSha256:localTarballSha256,
|
|
292
|
+
markerReceiptSha256:deployArgs.markerReceiptSha256,
|
|
293
|
+
predecessorPointerSha256:uacPointerSha256(pointer),
|
|
294
|
+
predecessorPolicySha256:markerReceipt.predecessor_policy_sha256,
|
|
295
|
+
preflightSubjectSha256:preflight.subject_sha256,nodeSha256:await sha(process.execPath),
|
|
296
|
+
deployScriptSha256:await sha(fileURLToPath(import.meta.url)),
|
|
297
|
+
nonce:deployArgs.migrationNonce,issuedAt:deployArgs.migrationIssuedAt,
|
|
298
|
+
expiresAt:deployArgs.migrationExpiresAt
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
189
302
|
if(deployArgs.preflight){
|
|
190
303
|
console.log(JSON.stringify(preflight));
|
|
191
304
|
process.exit(0);
|
|
@@ -229,6 +342,15 @@ const outcome=await orchestrateActivation({pointer,expectedParent:release.parent
|
|
|
229
342
|
result=await runSlotUpdateCli(args,{stateRoot:state});
|
|
230
343
|
if(!result.handed_off)throw new Error('release activation unexpectedly completed inline');
|
|
231
344
|
verification=await waitForActivation({handoffPath:result.handoff_path,expectedVersion,expectedManifest:manifest});
|
|
345
|
+
if(deployArgs.uacLegacyMigration){
|
|
346
|
+
const active=parseActivePointer(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
347
|
+
if(active.version!==expectedVersion||active.manifest_sha256!==manifest)
|
|
348
|
+
throw new Error('UAC migration active pointer drift after activation');
|
|
349
|
+
const codingBytes=await readFile(path.join(active.generation_root,'coding-policy.json'));
|
|
350
|
+
const autonomousBytes=await readFile(path.join(active.generation_root,'autonomous-policy.json'));
|
|
351
|
+
verifyNarrowedPolicyGeneration({codingPolicy:JSON.parse(codingBytes.toString('utf8').replace(/^\uFEFF/,'')),codingPolicyBytes:codingBytes,
|
|
352
|
+
autonomousPolicy:JSON.parse(autonomousBytes.toString('utf8').replace(/^\uFEFF/,''))});
|
|
353
|
+
}
|
|
232
354
|
return {verified:true,release_id:verification.release_id};
|
|
233
355
|
}});
|
|
234
356
|
if(outcome.state!=='ACTIVATED'){
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Issue a bounded no-grant UAC receipt only from a protected installed candidate.
|
|
2
|
+
// Never activate, change ACLs, remove markers, issue grants or publish artifacts.
|
|
3
|
+
import {createHash,randomBytes} from 'node:crypto';
|
|
4
|
+
import {readFile,lstat,open,access} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {fileURLToPath} from 'node:url';
|
|
7
|
+
import {spawnSync} from 'node:child_process';
|
|
8
|
+
import {parseActivePointer} from '../src/release-slot.mjs';
|
|
9
|
+
import {inspectTrustedInstallation,privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
|
|
10
|
+
import {verifyMarkerReconciliationReceipt} from '../src/clean-activation-recovery-r153.mjs';
|
|
11
|
+
import {createUacApproval,uacPointerSha256,protectedUacReceiptPath} from '../src/uac-approval-contract-r153.mjs';
|
|
12
|
+
|
|
13
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
14
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
15
|
+
const read=async f=>readFile(f);
|
|
16
|
+
const hash=async f=>sha(await read(f));
|
|
17
|
+
const fail=text=>{throw new Error('R153 UAC issuer rejected: '+text);};
|
|
18
|
+
function args(argv){
|
|
19
|
+
const expected=['--state-root','--marker-receipt','--marker-receipt-sha256','--preflight-subject-sha256'];
|
|
20
|
+
if(argv.length!==8||expected.some((key,i)=>argv[2*i]!==key))fail('unexpected arguments');
|
|
21
|
+
const [state,receipt,receiptHash,preflight]=[argv[1],argv[3],argv[5],argv[7]];
|
|
22
|
+
if(!path.win32.isAbsolute(state)||!path.win32.isAbsolute(receipt)||
|
|
23
|
+
!/^[0-9a-f]{64}$/.test(receiptHash)||!/^[0-9a-f]{64}$/.test(preflight))fail('subject arguments invalid');
|
|
24
|
+
return {state:path.resolve(state),receipt:path.resolve(receipt),receiptHash,preflight};
|
|
25
|
+
}
|
|
26
|
+
async function regular(file,label){const s=await lstat(file);if(!s.isFile()||s.isSymbolicLink())fail(label+' must be regular');return s;}
|
|
27
|
+
async function absent(file,label){try{await lstat(file);fail(label+' present');}catch(e){if(e?.code!=='ENOENT')throw e;}}
|
|
28
|
+
async function main(){
|
|
29
|
+
if(process.platform!=='win32'||process.arch!=='x64')fail('Windows x64 required');
|
|
30
|
+
const ps='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
31
|
+
const check="([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)";
|
|
32
|
+
const elevated=spawnSync(ps,['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(check,'utf16le').toString('base64')],
|
|
33
|
+
{encoding:'utf8',shell:false,windowsHide:true,timeout:12000});
|
|
34
|
+
if(elevated.error||elevated.status!==0||String(elevated.stdout).trim().toLowerCase()!=='true')
|
|
35
|
+
fail('explicit Windows UAC elevation required');
|
|
36
|
+
const {state,receipt,receiptHash,preflight}=args(process.argv.slice(2));
|
|
37
|
+
if(path.dirname(receipt).toLowerCase()!==path.join(state,'evidence','legacy-migration').toLowerCase())
|
|
38
|
+
fail('marker receipt path not in state evidence root');
|
|
39
|
+
const issueFile=fileURLToPath(import.meta.url);
|
|
40
|
+
const deployFile=path.join(root,'scripts','deploy-r48.mjs');
|
|
41
|
+
const helperFile=path.join(root,'scripts','request-uac-migration-r153.ps1');
|
|
42
|
+
const trust=await inspectTrustedInstallation([...privilegedEpochInstallFiles(root,process.execPath),
|
|
43
|
+
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')]);
|
|
45
|
+
if(trust.trusted!==true)fail('candidate not R89 protected');
|
|
46
|
+
const pkg=JSON.parse((await read(path.join(root,'package.json'))).toString('utf8'));
|
|
47
|
+
const rel=JSON.parse((await read(path.join(root,'RELEASE.json'))).toString('utf8'));
|
|
48
|
+
if(pkg.version!=='0.15.59'||rel.version!==pkg.version)fail('candidate release identity mismatch');
|
|
49
|
+
const pointer=parseActivePointer((await read(path.join(state,'active-release.json'))).toString('utf8'));
|
|
50
|
+
if(pointer.version!=='0.15.45'||rel.parent?.release_id!==pointer.release_id||
|
|
51
|
+
rel.parent?.manifest_sha256!==pointer.manifest_sha256||
|
|
52
|
+
rel.parent?.generation_manifest_sha256!==pointer.generation_manifest_sha256)
|
|
53
|
+
fail('predecessor pointer drift');
|
|
54
|
+
const manifest=await hash(path.join(root,'MANIFEST.SHA256'));
|
|
55
|
+
const verifyManifest=spawnSync(process.execPath,[path.join(root,'scripts','release-manifest.mjs'),'verify-installed'],
|
|
56
|
+
{cwd:root,encoding:'utf8',shell:false,windowsHide:true,timeout:30000});
|
|
57
|
+
if(verifyManifest.error||verifyManifest.status!==0)fail('candidate manifest verification failed');
|
|
58
|
+
await regular(receipt,'marker receipt');
|
|
59
|
+
const receiptBytes=await read(receipt);
|
|
60
|
+
if(sha(receiptBytes)!==receiptHash)fail('marker receipt digest mismatch');
|
|
61
|
+
const marker=JSON.parse(receiptBytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
62
|
+
const policies=marker.predecessor_policy_sha256;
|
|
63
|
+
verifyMarkerReconciliationReceipt(marker,{pointer,policySha256:policies,markerSha256:marker.marker_sha256});
|
|
64
|
+
await absent(path.join(state,'runtime','remote-session.json'),'foreground owner marker');
|
|
65
|
+
const grantField={process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'};
|
|
66
|
+
for(const plane of Object.keys(grantField)){
|
|
67
|
+
const filename=path.join(pointer.generation_root,`${plane}-policy.json`);
|
|
68
|
+
await regular(filename,plane+' policy');
|
|
69
|
+
const bytes=await read(filename);
|
|
70
|
+
if(sha(bytes)!==policies?.[plane])fail(plane+' policy drift');
|
|
71
|
+
const policy=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
|
|
72
|
+
const grant=policy[grantField[plane]];
|
|
73
|
+
if(typeof grant!=='string'||!path.isAbsolute(grant)||
|
|
74
|
+
path.dirname(grant).toLowerCase()!==path.join(state,'runtime').toLowerCase())fail(plane+' grant path invalid');
|
|
75
|
+
await absent(grant,plane+' grant');
|
|
76
|
+
await absent(path.join(state,'runtime','authority-epochs',plane,'.lock'),plane+' authority lock');
|
|
77
|
+
}
|
|
78
|
+
const tarball=path.join(state,'evidence','npm-publication',pkg.version,`deadbyte-mcp-${pkg.version}.tgz`);
|
|
79
|
+
await regular(tarball,'exact publication tarball');
|
|
80
|
+
const bytes=await read(tarball);
|
|
81
|
+
const issuedAt=new Date().toISOString(),expiresAt=new Date(Date.now()+300_000).toISOString();
|
|
82
|
+
const nonce=randomBytes(32).toString('hex');
|
|
83
|
+
const approval=createUacApproval({stateRoot:state,candidateRoot:root,candidateVersion:pkg.version,
|
|
84
|
+
candidateManifestSha256:manifest,tarballSha256:sha(bytes),markerReceiptSha256:receiptHash,
|
|
85
|
+
predecessorPointerSha256:uacPointerSha256(pointer),
|
|
86
|
+
predecessorPolicySha256:policies,preflightSubjectSha256:preflight,
|
|
87
|
+
nodeSha256:await hash(process.execPath),deployScriptSha256:await hash(deployFile),
|
|
88
|
+
nonce,issuedAt,expiresAt});
|
|
89
|
+
// The strict installation manifest disallows unmanifested files. A receipt
|
|
90
|
+
// under root would turn an otherwise valid candidate into an invalid tree.
|
|
91
|
+
const file=protectedUacReceiptPath(root,nonce);
|
|
92
|
+
const h=await open(file,'wx',0o600);
|
|
93
|
+
try{await h.writeFile(JSON.stringify(approval)+'\n','utf8');await h.sync();}finally{await h.close();}
|
|
94
|
+
// Newly created files can be owned by the interactive administrator's SID.
|
|
95
|
+
// Normalize the exact sidecar to the built-in Administrators owner and
|
|
96
|
+
// independently require the R89 ACL verdict. No arbitrary path argument.
|
|
97
|
+
const ownerFix=spawnSync('C:\\Windows\\System32\\icacls.exe',
|
|
98
|
+
[file,'/setowner','*S-1-5-32-544'],
|
|
99
|
+
{encoding:'utf8',shell:false,windowsHide:true,timeout:15000});
|
|
100
|
+
if(ownerFix.error||ownerFix.status!==0)fail('protected approval owner normalization failed');
|
|
101
|
+
const receiptTrust=await inspectTrustedInstallation([file]);
|
|
102
|
+
if(receiptTrust.trusted!==true)fail('approval sidecar not R89 protected');
|
|
103
|
+
process.stdout.write(JSON.stringify({schema:'deadbyte.r153-uac-issuer-result.v1',
|
|
104
|
+
receipt_path:file,receipt_sha256:await hash(file),nonce,issued_at_utc:issuedAt,
|
|
105
|
+
expires_at_utc:expiresAt})+'\n');
|
|
106
|
+
}
|
|
107
|
+
main().catch(error=>{process.stderr.write(String(error?.message||error)+'\n');process.exitCode=2;});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// R153 additive release-lineage gate. Read-only: no manifests rewritten, no publish/activation.
|
|
2
|
+
import {readFile} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
|
|
6
|
+
const defaultRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
7
|
+
const hex64=/^[0-9a-f]{64}$/;
|
|
8
|
+
const EXPECTED=Object.freeze({
|
|
9
|
+
candidateVersion:'0.15.59', train:'R153',
|
|
10
|
+
sourceVersion:'0.15.58',
|
|
11
|
+
sourceSha256:'4bc53cfa6b3890b5749066cb507e8773b36d6c255192a8973dadb8546a39822e',
|
|
12
|
+
runtimeVersion:'0.15.45',
|
|
13
|
+
runtimeReleaseId:'v0.15.45-34aa8d1241cfb8bf',
|
|
14
|
+
runtimeManifest:'34aa8d1241cfb8bf0b5ba303bf3c06e0e9e1bf01f64457bca659d6dd4d634fdc',
|
|
15
|
+
runtimeGeneration:'ff2b73410062688651abd7bc2dd7b2b8c87810a28e3808bfe1f4352a25b0e6e4'
|
|
16
|
+
});
|
|
17
|
+
const keys=['version','release_id','manifest_sha256','generation_manifest_sha256'];
|
|
18
|
+
const same=(a,b)=>keys.every(k=>a?.[k]===b?.[k]);
|
|
19
|
+
function requireEqual(actual,expected,label){
|
|
20
|
+
if(actual!==expected)throw new Error(`R153 identity ${label} mismatch: ${JSON.stringify(actual)}`);
|
|
21
|
+
}
|
|
22
|
+
export function verifyR153Lineage({pkg,release,bridge}){
|
|
23
|
+
if(pkg?.name!=='deadbyte-mcp'||release?.name!=='deadbyte-mcp')throw new Error('R153 package name mismatch');
|
|
24
|
+
requireEqual(pkg.version,EXPECTED.candidateVersion,'package version');
|
|
25
|
+
requireEqual(release.version,EXPECTED.candidateVersion,'release version');
|
|
26
|
+
requireEqual(release.train,EXPECTED.train,'train');
|
|
27
|
+
requireEqual(release.schema,'deadbyte.release.v1','release schema');
|
|
28
|
+
requireEqual(bridge?.name,'deadbyte-chatgpt-bridge','bridge name');
|
|
29
|
+
requireEqual(bridge.version,pkg.version,'bridge version');
|
|
30
|
+
requireEqual(release.source_predecessor?.version,EXPECTED.sourceVersion,'source predecessor version');
|
|
31
|
+
requireEqual(release.source_predecessor?.tarball_sha256,EXPECTED.sourceSha256,'source predecessor tarball');
|
|
32
|
+
requireEqual(release.source_predecessor?.release_status,'REGISTRY_PUBLISHED','source predecessor registry status');
|
|
33
|
+
const predecessor=release.runtime_predecessor;
|
|
34
|
+
requireEqual(predecessor?.version,EXPECTED.runtimeVersion,'runtime predecessor version');
|
|
35
|
+
requireEqual(predecessor?.release_id,EXPECTED.runtimeReleaseId,'runtime release ID');
|
|
36
|
+
requireEqual(predecessor?.manifest_sha256,EXPECTED.runtimeManifest,'runtime manifest SHA-256');
|
|
37
|
+
requireEqual(predecessor?.generation_manifest_sha256,EXPECTED.runtimeGeneration,'runtime generation SHA-256');
|
|
38
|
+
if(!same(release.parent,predecessor))throw new Error('R153 parent is not exact runtime predecessor');
|
|
39
|
+
if(release.source_predecessor.version===predecessor.version)throw new Error('R153 predecessor roles collapsed');
|
|
40
|
+
for(const key of ['tarball_sha256','tarball_integrity','npm_integrity'])
|
|
41
|
+
if(Object.hasOwn(release,key))throw new Error(`R153 release contains self-referential ${key}`);
|
|
42
|
+
for(const key of ['manifest_sha256','generation_manifest_sha256'])
|
|
43
|
+
if(!hex64.test(predecessor[key]))throw new Error(`R153 ${key} malformed`);
|
|
44
|
+
return Object.freeze({schema:'deadbyte.r153-release-lineage-gate.v1',status:'PASS',
|
|
45
|
+
candidate_version:pkg.version,source_predecessor:EXPECTED.sourceVersion,
|
|
46
|
+
source_tarball_sha256:EXPECTED.sourceSha256,runtime_predecessor:EXPECTED.runtimeVersion,
|
|
47
|
+
runtime_release_id:EXPECTED.runtimeReleaseId,scope:'METADATA_ONLY'});
|
|
48
|
+
}
|
|
49
|
+
export async function verifyR153IdentityTree(root=defaultRoot){
|
|
50
|
+
const regularJSON=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
|
|
51
|
+
return verifyR153Lineage({pkg:await regularJSON('package.json'),
|
|
52
|
+
release:await regularJSON('RELEASE.json'),bridge:await regularJSON('bridge/package.json')});
|
|
53
|
+
}
|
|
54
|
+
if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
|
|
55
|
+
try{console.log(JSON.stringify(await verifyR153IdentityTree(),null,2));}
|
|
56
|
+
catch(e){console.error('R153_LINEAGE_FAIL '+String(e?.message??e));process.exitCode=1;}
|
|
57
|
+
}
|