deadbyte-mcp 0.15.72 → 0.15.74
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 +75 -27
- package/PROVENANCE.json +42 -14
- package/README.txt +24 -18
- package/RELEASE.json +52 -18
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +30 -2
- package/bin/deadbyte-owner-token-r193.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +2 -2
- package/native/deadbyte-owner-token-r193.cpp +156 -0
- package/package.json +2 -2
- package/scripts/gate-windows-r193.ps1 +108 -0
- package/scripts/gate-windows-r194.ps1 +112 -0
- package/scripts/npm-interactive-r194.mjs +13 -0
- package/scripts/npm-publication-r65.mjs +64 -4
- package/scripts/npm-release-r48.mjs +58 -11
- package/scripts/npm-release-readiness-r193.mjs +80 -0
- package/scripts/npm-release-readiness-r194.mjs +80 -0
- package/scripts/r188-owner-uac-review.ps1 +91 -0
- package/scripts/r188-uac-review-preview.mjs +65 -0
- package/scripts/r193-compile-token.ps1 +22 -0
- package/scripts/r193-evidence-relocate.ps1 +12 -0
- package/scripts/r193-focused-gate.mjs +20 -0
- package/scripts/r193-import-r188-scripts.ps1 +14 -0
- package/scripts/r193-limited-subject-probe.ps1 +25 -0
- package/scripts/r193-migrate-runtime-limited.ps1 +103 -0
- package/scripts/r193-owner-keygen.mjs +43 -0
- package/scripts/r193-owner-request-live.mjs +50 -0
- package/scripts/r193-owner-uac-approve.ps1 +42 -0
- package/scripts/r193-owner-uac-broker.mjs +139 -0
- package/scripts/r193-owner-uac-enroll-and-approve.ps1 +62 -0
- package/scripts/r193-test-limited-token.ps1 +49 -0
- package/scripts/r193-toolchain-inventory.ps1 +7 -0
- package/scripts/r193-uac-bridge-focused.mjs +21 -0
- package/src/r188-owner-uac.mjs +105 -0
- package/src/r188-uac-boundary.mjs +21 -0
- package/src/r193-live-owner-subject.mjs +110 -0
- package/src/r193-owner-secret-acl.mjs +67 -0
- package/src/r193-owner-uac-broker.mjs +85 -0
- package/src/r193-uac-r70-bridge.mjs +54 -0
- package/src/version.mjs +1 -1
- package/test/process-policy-refresh.test.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-prepublish-identity.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +3 -3
- package/test/r165-live-cli-skew.test.mjs +1 -1
- package/test/r167-release-contract.test.mjs +11 -11
- package/test/r168-release-contract.test.mjs +4 -4
- package/test/r172-release-contract.test.mjs +6 -6
- package/test/r173-release-contract.test.mjs +16 -16
- package/test/r179-release-contract.test.mjs +11 -11
- package/test/r188-owner-uac.test.mjs +48 -0
- package/test/r188-uac-boundary.test.mjs +42 -0
- package/test/r190-release-contract.test.mjs +10 -10
- package/test/r191-release-contract.test.mjs +12 -12
- package/test/r192-release-identity.test.mjs +10 -10
- package/test/r193-limited-migration-contract.test.mjs +26 -0
- package/test/r193-live-owner-subject.test.mjs +61 -0
- package/test/r193-native-process-observer.test.mjs +36 -0
- package/test/r193-native-token.test.mjs +43 -0
- package/test/r193-owner-enrollment-entrypoint.test.mjs +38 -0
- package/test/r193-owner-secret-acl.test.mjs +23 -0
- package/test/r193-owner-uac-broker.test.mjs +82 -0
- package/test/r193-owner-uac-entrypoint.test.mjs +36 -0
- package/test/r193-release-identity.test.mjs +56 -0
- package/test/r193-release-readiness.test.mjs +87 -0
- package/test/r193-uac-r70-bridge.test.mjs +88 -0
- package/test/r193-windows-gate.test.mjs +30 -0
- package/test/r194-npm-auth-recovery.test.mjs +77 -0
- package/test/r194-npm-preauth.test.mjs +57 -0
- package/test/r194-release-identity.test.mjs +43 -0
- package/test/r194-release-readiness.test.mjs +87 -0
- package/test/r194-windows-gate.test.mjs +21 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// One-time protected owner key enrollment. No caller-selected paths.
|
|
2
|
+
import {generateKeyPairSync} from 'node:crypto';
|
|
3
|
+
import {lstat,open,readFile} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
7
|
+
import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
|
|
8
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
9
|
+
import {inspectR193OwnerSecretAcl} from '../src/r193-owner-secret-acl.mjs';
|
|
10
|
+
import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
11
|
+
const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
|
|
12
|
+
const OWNER='C:\\Program Files\\DEADBYTE-OwnerAuthority';
|
|
13
|
+
const PRIVATE=path.win32.join(OWNER,'owner-private.der');
|
|
14
|
+
const PUBLIC=path.win32.join(OWNER,'owner-public.pem');
|
|
15
|
+
const ANCHOR=path.win32.join(OWNER,'owner-anchor.json');
|
|
16
|
+
async function exclusive(file,bytes){
|
|
17
|
+
const handle=await open(file,'wx',0o600);
|
|
18
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
19
|
+
}
|
|
20
|
+
async function main(){
|
|
21
|
+
if(process.platform!=='win32'||process.arch!=='x64')throw Error('R193 owner keygen requires Windows x64');
|
|
22
|
+
if(process.argv.length!==2)throw Error('R193 owner keygen accepts no arguments');
|
|
23
|
+
await inspectTrustedInstallation(['C:\\Program Files\\nodejs\\node.exe',THIS,
|
|
24
|
+
path.join(ROOT,'MANIFEST.SHA256'),path.join(ROOT,'src','r186-owner-anchor.mjs'),
|
|
25
|
+
path.join(ROOT,'src','r193-owner-secret-acl.mjs'),
|
|
26
|
+
path.join(ROOT,'src','privileged-install-trust-r89.mjs'),
|
|
27
|
+
path.join(ROOT,'src','release-slot.mjs')]);
|
|
28
|
+
const manifest=await readFile(path.join(ROOT,'MANIFEST.SHA256'),'utf8');
|
|
29
|
+
await verifyManifestTreeExact(ROOT,parseManifest(manifest));
|
|
30
|
+
const owner=await lstat(OWNER);
|
|
31
|
+
if(!owner.isDirectory()||owner.isSymbolicLink())throw Error('R193 owner directory is not a real directory');
|
|
32
|
+
const pair=generateKeyPairSync('ed25519');
|
|
33
|
+
const privateDer=pair.privateKey.export({format:'der',type:'pkcs8'});
|
|
34
|
+
const material=createR186OwnerAnchor(pair.publicKey.export({format:'pem',type:'spki'}).toString());
|
|
35
|
+
await exclusive(PRIVATE,privateDer);
|
|
36
|
+
await exclusive(PUBLIC,Buffer.from(material.public_key_pem,'utf8'));
|
|
37
|
+
await exclusive(ANCHOR,Buffer.from(canonicalJson(material.anchor)+'\n','utf8'));
|
|
38
|
+
const secret=await inspectR193OwnerSecretAcl([OWNER,PRIVATE]);
|
|
39
|
+
process.stdout.write(JSON.stringify({status:'R193_OWNER_AUTHORITY_ENROLLED',
|
|
40
|
+
owner_key_id:material.anchor.owner_key_id,secret_acl:secret.reason,
|
|
41
|
+
private_key_exported:false,authority_granted:false})+'\n');
|
|
42
|
+
}
|
|
43
|
+
main().catch(error=>{console.error(String(error?.message??error).slice(0,500));process.exitCode=2;});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Protected, fixed-scope request generation after Limited migration.
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
import {execFile} from 'node:child_process';
|
|
4
|
+
import {promisify} from 'node:util';
|
|
5
|
+
import {lstat,open,readFile} from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
9
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
10
|
+
import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
11
|
+
import {createR188OwnerUacRequest} from '../src/r188-owner-uac.mjs';
|
|
12
|
+
import {createR193LiveMachineEpoch} from '../src/r193-live-owner-subject.mjs';
|
|
13
|
+
const execFileAsync=promisify(execFile);
|
|
14
|
+
const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
|
|
15
|
+
const STATE='C:\\Users\\DEADBYTE\\.deadbyte-mcp',RUNTIME=path.win32.join(STATE,'runtime');
|
|
16
|
+
const PS='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
17
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
18
|
+
async function bounded(file,limit){const s=await lstat(file);if(!s.isFile()||s.isSymbolicLink()||s.size<1||s.size>limit)throw Error('R193 request input unsafe');return readFile(file);}
|
|
19
|
+
async function main(){
|
|
20
|
+
if(process.platform!=='win32'||process.arch!=='x64'||process.argv.length!==2)throw Error('R193 live request accepts no arguments');
|
|
21
|
+
const manifestPath=path.join(ROOT,'MANIFEST.SHA256');
|
|
22
|
+
await inspectTrustedInstallation(['C:\\Program Files\\nodejs\\node.exe',THIS,manifestPath,
|
|
23
|
+
path.join(ROOT,'src','r188-owner-uac.mjs'),path.join(ROOT,'src','r193-live-owner-subject.mjs'),
|
|
24
|
+
path.join(ROOT,'src','privileged-install-trust-r89.mjs')]);
|
|
25
|
+
const manifestBytes=await bounded(manifestPath,1024*1024);
|
|
26
|
+
await verifyManifestTreeExact(ROOT,parseManifest(manifestBytes.toString('utf8')));
|
|
27
|
+
const pointer=JSON.parse((await bounded(path.win32.join(STATE,'active-release.json'),64*1024)).toString('utf8'));
|
|
28
|
+
if(pointer.manifest_sha256!==sha(manifestBytes))throw Error('R193 request package is not exact active release');
|
|
29
|
+
const codingBytes=await bounded(path.win32.join(pointer.generation_root,'coding-policy.json'),1024*1024);
|
|
30
|
+
const autonomousBytes=await bounded(path.win32.join(pointer.generation_root,'autonomous-policy.json'),1024*1024);
|
|
31
|
+
const coding=JSON.parse(codingBytes),autonomous=JSON.parse(autonomousBytes);
|
|
32
|
+
if(!coding.roots?.core?.path||!Array.isArray(autonomous.root_ids)||autonomous.root_ids.length!==1||autonomous.root_ids[0]!=='core')
|
|
33
|
+
throw Error('R193 request policy root is not the bounded core project');
|
|
34
|
+
const env={...process.env,DEADBYTE_CONTROLLER_CONFIG:pointer.config_path,DEADBYTE_CONTROLLER_RUNTIME_ROOT:RUNTIME};
|
|
35
|
+
const controller=path.join(ROOT,'controller','deadbyte-controller.ps1');
|
|
36
|
+
const {stdout}=await execFileAsync(PS,['-NoLogo','-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File',controller,'status','-Json'],
|
|
37
|
+
{env,windowsHide:true,timeout:20000,maxBuffer:1024*1024,encoding:'utf8'});
|
|
38
|
+
const observation=JSON.parse(stdout);
|
|
39
|
+
if(observation.state!=='ONLINE'||!observation.supervisor?.running)throw Error('R193 request requires ONLINE Limited runtime');
|
|
40
|
+
const machineEpoch=createR193LiveMachineEpoch({activePointer:pointer,controllerObservation:observation});
|
|
41
|
+
const request=createR188OwnerUacRequest({machineEpoch,releaseManifestSha256:sha(manifestBytes),
|
|
42
|
+
codingPolicySha256:sha(codingBytes),autonomousPolicySha256:sha(autonomousBytes),
|
|
43
|
+
rootId:'core',projectRoot:coding.roots.core.path,maxSeconds:600,now:new Date(),expiresInMs:300000});
|
|
44
|
+
const out=path.win32.join(RUNTIME,'owner-consent','r193-request-'+request.request_sha256+'.json');
|
|
45
|
+
const handle=await open(out,'wx',0o600);try{await handle.writeFile(canonicalJson(request)+'\n');await handle.sync();}finally{await handle.close();}
|
|
46
|
+
process.stdout.write(JSON.stringify({status:'R193_OWNER_REQUEST_CREATED',request_path:out,
|
|
47
|
+
request_sha256:request.request_sha256,review_phrase:request.review_phrase,
|
|
48
|
+
expires_at_utc:request.subject.expires_at_utc,authority_granted:false})+'\n');
|
|
49
|
+
}
|
|
50
|
+
main().catch(error=>{console.error(String(error?.message??error).slice(0,500));process.exitCode=2;});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
#requires -RunAsAdministrator
|
|
3
|
+
[CmdletBinding()]
|
|
4
|
+
param([Parameter(Mandatory=$true)][string]$RequestPath)
|
|
5
|
+
$ErrorActionPreference='Stop';Set-StrictMode -Version Latest
|
|
6
|
+
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
7
|
+
$program=[IO.Path]::GetFullPath($env:ProgramFiles).TrimEnd('\')+'\'
|
|
8
|
+
if(-not ($root.TrimEnd('\')+'\').StartsWith($program,[StringComparison]::OrdinalIgnoreCase)){
|
|
9
|
+
throw 'R193 owner broker must run from an R89-protected Program Files package'
|
|
10
|
+
}
|
|
11
|
+
if(-not [Environment]::UserInteractive){throw 'R193 owner approval requires an interactive desktop'}
|
|
12
|
+
$node='C:\Program Files\nodejs\node.exe'
|
|
13
|
+
$broker=Join-Path $root 'scripts\r193-owner-uac-broker.mjs'
|
|
14
|
+
$request=[IO.Path]::GetFullPath($RequestPath)
|
|
15
|
+
$raw=@(& $node $broker preflight $request 2>&1) -join [Environment]::NewLine
|
|
16
|
+
if($LASTEXITCODE -ne 0){throw ('R193 preflight blocked: '+$raw.Substring(0,[Math]::Min(500,$raw.Length)))}
|
|
17
|
+
$preview=$raw|ConvertFrom-Json
|
|
18
|
+
if([string]$preview.status -cne 'READY_FOR_OWNER_GESTURE' -or $preview.authority_granted -or $preview.owner_approved){
|
|
19
|
+
throw 'R193 owner preflight returned an invalid state'
|
|
20
|
+
}
|
|
21
|
+
Write-Host '';Write-Host 'DEADBYTE R193 / OWNER AUTONOMOUS APPROVAL' -ForegroundColor Yellow
|
|
22
|
+
Write-Host 'This signs one short-lived exact request. It does not itself arm Autonomous.'
|
|
23
|
+
Write-Host ('Request SHA-256: '+[string]$preview.request_sha256)
|
|
24
|
+
Write-Host ('Project: '+[string]$preview.project_root)
|
|
25
|
+
Write-Host ('Operation: '+[string]$preview.operation)
|
|
26
|
+
Write-Host ('Duration cap seconds: '+[string]$preview.max_seconds)
|
|
27
|
+
Write-Host ('Expires UTC: '+[string]$preview.expires_at_utc)
|
|
28
|
+
$phrase=[string]$preview.review_phrase
|
|
29
|
+
$entered=Read-Host ('Type '+$phrase+' to approve this exact request')
|
|
30
|
+
if($entered -cne $phrase){throw 'R193 owner rejected request'}
|
|
31
|
+
$owner='C:\Program Files\DEADBYTE-OwnerAuthority'
|
|
32
|
+
if(-not(Test-Path -LiteralPath $owner -PathType Container)){throw 'R193 owner authority is not enrolled'}
|
|
33
|
+
$gesture=[ordered]@{schema='deadbyte.r193-owner-gesture.v1';status='APPROVED';
|
|
34
|
+
request_sha256=[string]$preview.request_sha256;review_phrase=$phrase;
|
|
35
|
+
approved_at_utc=[DateTime]::UtcNow.ToString('o')}
|
|
36
|
+
$gesturePath=Join-Path $owner ('gesture-'+[string]$preview.request_sha256+'.json')
|
|
37
|
+
$bytes=(New-Object Text.UTF8Encoding($false)).GetBytes(($gesture|ConvertTo-Json -Compress)+[Environment]::NewLine)
|
|
38
|
+
$stream=[IO.File]::Open($gesturePath,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
|
|
39
|
+
try{$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)}finally{$stream.Dispose()}
|
|
40
|
+
$out=@(& $node $broker approve $request 2>&1) -join [Environment]::NewLine
|
|
41
|
+
if($LASTEXITCODE -ne 0){throw ('R193 signing blocked: '+$out.Substring(0,[Math]::Min(500,$out.Length)))}
|
|
42
|
+
Write-Output $out
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Protected R193 owner broker. This entrypoint is intentionally fixed to the
|
|
2
|
+
// local DEADBYTE state and owner-authority roots; callers choose only an exact
|
|
3
|
+
// request file. It never issues authority. The R186 transaction remains the
|
|
4
|
+
// sole grant/epoch mutation boundary.
|
|
5
|
+
import {createHash,createPrivateKey,createPublicKey} from 'node:crypto';
|
|
6
|
+
import {execFile} from 'node:child_process';
|
|
7
|
+
import {promisify} from 'node:util';
|
|
8
|
+
import {lstat,mkdir,open,readFile} from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import {fileURLToPath} from 'node:url';
|
|
11
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
12
|
+
import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
|
|
13
|
+
import {parseManifest,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
14
|
+
import {buildR193LiveOwnerBoundary} from '../src/r193-live-owner-subject.mjs';
|
|
15
|
+
import {approveR193OwnerRequest} from '../src/r193-owner-uac-broker.mjs';
|
|
16
|
+
import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
|
|
17
|
+
import {inspectR193OwnerSecretAcl} from '../src/r193-owner-secret-acl.mjs';
|
|
18
|
+
|
|
19
|
+
const execFileAsync=promisify(execFile);
|
|
20
|
+
const THIS=fileURLToPath(import.meta.url),ROOT=path.resolve(path.dirname(THIS),'..');
|
|
21
|
+
const STATE='C:\\Users\\DEADBYTE\\.deadbyte-mcp';
|
|
22
|
+
const RUNTIME=path.win32.join(STATE,'runtime');
|
|
23
|
+
const OWNER='C:\\Program Files\\DEADBYTE-OwnerAuthority';
|
|
24
|
+
const PRIVATE=path.win32.join(OWNER,'owner-private.der');
|
|
25
|
+
const PUBLIC=path.win32.join(OWNER,'owner-public.pem');
|
|
26
|
+
const ANCHOR=path.win32.join(OWNER,'owner-anchor.json');
|
|
27
|
+
const NATIVE=path.join(ROOT,'bin','deadbyte-owner-token-r193.exe');
|
|
28
|
+
const PS='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
29
|
+
const NODE='C:\\Program Files\\nodejs\\node.exe';
|
|
30
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
31
|
+
const block=reason=>{throw new Error('R193_OWNER_UAC_BROKER_BLOCKED: '+reason);};
|
|
32
|
+
async function bounded(file,limit){
|
|
33
|
+
const stat=await lstat(file);
|
|
34
|
+
if(!stat.isFile()||stat.isSymbolicLink()||stat.size<1||stat.size>limit)block('unsafe input: '+path.basename(file));
|
|
35
|
+
return readFile(file);
|
|
36
|
+
}
|
|
37
|
+
async function exclusive(file,bytes){
|
|
38
|
+
await mkdir(path.dirname(file),{recursive:true});
|
|
39
|
+
const handle=await open(file,'wx',0o600);
|
|
40
|
+
try{await handle.writeFile(bytes);await handle.sync();}finally{await handle.close();}
|
|
41
|
+
}
|
|
42
|
+
async function exactOrWrite(file,bytes){
|
|
43
|
+
try{await exclusive(file,bytes);return 'CREATED';}
|
|
44
|
+
catch(error){
|
|
45
|
+
if(error?.code!=='EEXIST')throw error;
|
|
46
|
+
if(!(await readFile(file)).equals(bytes))block('existing output differs: '+path.basename(file));
|
|
47
|
+
return 'ALREADY_EXACT';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function jsonExec(file,args,options={}){
|
|
51
|
+
const {stdout}=await execFileAsync(file,args,{windowsHide:true,timeout:20000,
|
|
52
|
+
maxBuffer:1024*1024,encoding:'utf8',...options});
|
|
53
|
+
try{return JSON.parse(stdout);}catch{block('invalid JSON from protected child');}
|
|
54
|
+
}
|
|
55
|
+
async function uacPolicy(){
|
|
56
|
+
const command=`$p=Get-ItemProperty -LiteralPath 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System';[pscustomobject]@{enable_uac=([int]$p.EnableLUA -eq 1);secure_desktop=([int]$p.PromptOnSecureDesktop -eq 1);prompt_mode=$(if([int]$p.ConsentPromptBehaviorAdmin -eq 0){'silent'}else{'explicit_secure_desktop'})}|ConvertTo-Json -Compress`;
|
|
57
|
+
return jsonExec(PS,['-NoLogo','-NoProfile','-NonInteractive','-Command',command]);
|
|
58
|
+
}
|
|
59
|
+
async function live(request){
|
|
60
|
+
const manifestPath=path.join(ROOT,'MANIFEST.SHA256');
|
|
61
|
+
const protectedFiles=[NODE,THIS,manifestPath,NATIVE,
|
|
62
|
+
path.join(ROOT,'src','r193-live-owner-subject.mjs'),
|
|
63
|
+
path.join(ROOT,'src','r193-owner-uac-broker.mjs'),
|
|
64
|
+
path.join(ROOT,'src','r193-uac-r70-bridge.mjs'),
|
|
65
|
+
path.join(ROOT,'src','r193-owner-secret-acl.mjs'),
|
|
66
|
+
path.join(ROOT,'src','r188-owner-uac.mjs'),
|
|
67
|
+
path.join(ROOT,'src','r70-owner-approval.mjs'),
|
|
68
|
+
path.join(ROOT,'src','canonical-json.mjs'),
|
|
69
|
+
path.join(ROOT,'src','privileged-install-trust-r89.mjs'),
|
|
70
|
+
path.join(ROOT,'src','release-slot.mjs')];
|
|
71
|
+
const installation=await inspectTrustedInstallation(protectedFiles);
|
|
72
|
+
const manifestBytes=await bounded(manifestPath,1024*1024);
|
|
73
|
+
await verifyManifestTreeExact(ROOT,parseManifest(manifestBytes.toString('utf8')));
|
|
74
|
+
const pointer=JSON.parse((await bounded(path.win32.join(STATE,'active-release.json'),64*1024)).toString('utf8'));
|
|
75
|
+
if(pointer.manifest_sha256!==sha(manifestBytes))block('protected package is not exact active release');
|
|
76
|
+
const generation=pointer.generation_root;
|
|
77
|
+
const codingPath=path.win32.join(generation,'coding-policy.json');
|
|
78
|
+
const autonomousPath=path.win32.join(generation,'autonomous-policy.json');
|
|
79
|
+
const configPath=pointer.config_path;
|
|
80
|
+
const controller=path.join(ROOT,'controller','deadbyte-controller.ps1');
|
|
81
|
+
const env={...process.env,DEADBYTE_CONTROLLER_CONFIG:configPath,
|
|
82
|
+
DEADBYTE_CONTROLLER_RUNTIME_ROOT:RUNTIME};
|
|
83
|
+
const observation=await jsonExec(PS,['-NoLogo','-NoProfile','-NonInteractive',
|
|
84
|
+
'-ExecutionPolicy','Bypass','-File',controller,'status','-Json'],{env});
|
|
85
|
+
const supervisor=JSON.parse((await bounded(path.win32.join(RUNTIME,'supervisor.instance.json'),64*1024)).toString('utf8'));
|
|
86
|
+
const sources={bridge:observation.bridge,core:observation.core,cloud:observation.cloud,supervisor};
|
|
87
|
+
const processReports={};
|
|
88
|
+
for(const component of ['bridge','core','cloud','supervisor'])
|
|
89
|
+
processReports[component]=await jsonExec(NATIVE,['--inspect-pid',String(sources[component]?.pid)]);
|
|
90
|
+
const [codingPolicyBytes,autonomousPolicyBytes,uac]=await Promise.all([
|
|
91
|
+
bounded(codingPath,1024*1024),bounded(autonomousPath,1024*1024),uacPolicy()]);
|
|
92
|
+
const built=buildR193LiveOwnerBoundary({request,activePointer:pointer,installedManifestBytes:manifestBytes,
|
|
93
|
+
codingPolicyBytes,autonomousPolicyBytes,controllerObservation:observation,
|
|
94
|
+
supervisorLedger:supervisor,processReports,brokerR89Trusted:installation.trusted,
|
|
95
|
+
keyAclTrusted:false,uac,ownerGesture:'pending',now:new Date()});
|
|
96
|
+
return {...built,pointer,manifestBytes,codingPolicyBytes,autonomousPolicyBytes,uac};
|
|
97
|
+
}
|
|
98
|
+
async function main(){
|
|
99
|
+
if(process.platform!=='win32'||process.arch!=='x64')block('Windows x64 required');
|
|
100
|
+
const [mode,requestPath]=process.argv.slice(2);
|
|
101
|
+
if(!['preflight','approve'].includes(mode)||process.argv.slice(2).length!==2||
|
|
102
|
+
!path.win32.isAbsolute(requestPath))block('usage: <preflight|approve> <absolute-request-json>');
|
|
103
|
+
await inspectTrustedInstallation([NODE,THIS,path.join(ROOT,'MANIFEST.SHA256'),NATIVE]);
|
|
104
|
+
const request=JSON.parse((await bounded(requestPath,64*1024)).toString('utf8'));
|
|
105
|
+
const state=await live(request);
|
|
106
|
+
if(mode==='preflight'){
|
|
107
|
+
process.stdout.write(JSON.stringify({status:'READY_FOR_OWNER_GESTURE',
|
|
108
|
+
request_sha256:request.request_sha256,review_phrase:request.review_phrase,
|
|
109
|
+
project_root:request.subject.project_root,operation:request.subject.operation,
|
|
110
|
+
max_seconds:request.subject.max_seconds,expires_at_utc:request.subject.expires_at_utc,
|
|
111
|
+
authority_granted:false,owner_approved:false})+'\n');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const gesturePath=path.win32.join(OWNER,'gesture-'+request.request_sha256+'.json');
|
|
115
|
+
const gesture=JSON.parse((await bounded(gesturePath,16*1024)).toString('utf8'));
|
|
116
|
+
if(gesture?.schema!=='deadbyte.r193-owner-gesture.v1'||gesture?.status!=='APPROVED'||
|
|
117
|
+
gesture?.request_sha256!==request.request_sha256||gesture?.review_phrase!==request.review_phrase||
|
|
118
|
+
typeof gesture?.approved_at_utc!=='string'||Date.now()-Date.parse(gesture.approved_at_utc)<0||
|
|
119
|
+
Date.now()-Date.parse(gesture.approved_at_utc)>60000)block('owner gesture missing, stale or mismatched');
|
|
120
|
+
const secret=await inspectR193OwnerSecretAcl([OWNER,PRIVATE]);
|
|
121
|
+
const [privateKeyDer,publicPem,anchorBytes]=await Promise.all([
|
|
122
|
+
bounded(PRIVATE,8192),bounded(PUBLIC,8192),bounded(ANCHOR,4096)]);
|
|
123
|
+
const privateKey=createPrivateKey({key:privateKeyDer,format:'der',type:'pkcs8'});
|
|
124
|
+
const derived=createR186OwnerAnchor(createPublicKey(privateKey).export({format:'pem',type:'spki'}).toString());
|
|
125
|
+
const anchor=JSON.parse(anchorBytes.toString('utf8'));
|
|
126
|
+
if(derived.public_key_pem!==publicPem.toString('utf8')||
|
|
127
|
+
canonicalJson(derived.anchor)!==canonicalJson(anchor))block('owner key enrollment mismatch');
|
|
128
|
+
const result=approveR193OwnerRequest({request,expected:state.expected,privateKeyDer,
|
|
129
|
+
boundary:{...state.boundary,key_acl_trusted:secret.trusted,owner_gesture:'approved'},now:new Date()});
|
|
130
|
+
const bytes=Buffer.from(canonicalJson(result.approval)+'\n','utf8');
|
|
131
|
+
const protectedApproval=path.win32.join(OWNER,'approval-'+request.request_sha256+'.json');
|
|
132
|
+
const runtimeApproval=path.win32.join(RUNTIME,'owner-consent','autonomous-approval.json');
|
|
133
|
+
const protected_state=await exactOrWrite(protectedApproval,bytes);
|
|
134
|
+
const runtime_state=await exactOrWrite(runtimeApproval,bytes);
|
|
135
|
+
process.stdout.write(JSON.stringify({...result,approval:undefined,protected_state,runtime_state,
|
|
136
|
+
protected_approval_sha256:sha(bytes),protected_approval_path:protectedApproval,
|
|
137
|
+
runtime_approval_path:runtimeApproval})+'\n');
|
|
138
|
+
}
|
|
139
|
+
main().catch(error=>{console.error(String(error?.message??error).slice(0,600));process.exitCode=2;});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
#requires -RunAsAdministrator
|
|
3
|
+
[CmdletBinding()]
|
|
4
|
+
param([Parameter(Mandatory=$true)][string]$RequestPath)
|
|
5
|
+
$ErrorActionPreference='Stop';Set-StrictMode -Version Latest
|
|
6
|
+
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
7
|
+
$program=[IO.Path]::GetFullPath($env:ProgramFiles).TrimEnd('\')+'\'
|
|
8
|
+
if(-not ($root.TrimEnd('\')+'\').StartsWith($program,[StringComparison]::OrdinalIgnoreCase)){
|
|
9
|
+
throw 'R193 enrollment must run from an R89-protected Program Files package'
|
|
10
|
+
}
|
|
11
|
+
if(-not [Environment]::UserInteractive){throw 'R193 enrollment requires an interactive owner desktop'}
|
|
12
|
+
$node='C:\Program Files\nodejs\node.exe'
|
|
13
|
+
$broker=Join-Path $root 'scripts\r193-owner-uac-broker.mjs'
|
|
14
|
+
$keygen=Join-Path $root 'scripts\r193-owner-keygen.mjs'
|
|
15
|
+
$request=[IO.Path]::GetFullPath($RequestPath)
|
|
16
|
+
$raw=@(& $node $broker preflight $request 2>&1) -join [Environment]::NewLine
|
|
17
|
+
if($LASTEXITCODE -ne 0){throw ('R193 enrollment preflight blocked: '+$raw.Substring(0,[Math]::Min(500,$raw.Length)))}
|
|
18
|
+
$preview=$raw|ConvertFrom-Json
|
|
19
|
+
if([string]$preview.status -cne 'READY_FOR_OWNER_GESTURE' -or $preview.authority_granted -or $preview.owner_approved){
|
|
20
|
+
throw 'R193 enrollment preflight returned an invalid state'
|
|
21
|
+
}
|
|
22
|
+
Write-Host '';Write-Host 'DEADBYTE R193 / ENROLL OWNER AUTHORITY' -ForegroundColor Yellow
|
|
23
|
+
Write-Host 'A new owner-only Ed25519 identity will be created under Program Files.'
|
|
24
|
+
Write-Host 'Limited DEADBYTE processes will not be able to read or use the private key.'
|
|
25
|
+
Write-Host ('Request SHA-256: '+[string]$preview.request_sha256)
|
|
26
|
+
Write-Host ('Project: '+[string]$preview.project_root)
|
|
27
|
+
Write-Host ('Duration cap seconds: '+[string]$preview.max_seconds)
|
|
28
|
+
$phrase=[string]$preview.review_phrase
|
|
29
|
+
$entered=Read-Host ('Type '+$phrase+' to enroll and approve this exact request')
|
|
30
|
+
if($entered -cne $phrase){throw 'R193 owner rejected enrollment'}
|
|
31
|
+
$owner='C:\Program Files\DEADBYTE-OwnerAuthority'
|
|
32
|
+
if(Test-Path -LiteralPath $owner){throw 'R193 owner authority already exists; use the approval-only workflow'}
|
|
33
|
+
$admins=New-Object Security.Principal.SecurityIdentifier('S-1-5-32-544')
|
|
34
|
+
$system=New-Object Security.Principal.SecurityIdentifier('S-1-5-18')
|
|
35
|
+
$inherit=[Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
|
|
36
|
+
$none=[Security.AccessControl.PropagationFlags]::None
|
|
37
|
+
$allow=[Security.AccessControl.AccessControlType]::Allow
|
|
38
|
+
New-Item -ItemType Directory -Path $owner -ErrorAction Stop | Out-Null
|
|
39
|
+
$acl=New-Object Security.AccessControl.DirectorySecurity
|
|
40
|
+
$acl.SetAccessRuleProtection($true,$false);$acl.SetOwner($admins)
|
|
41
|
+
$acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($admins,'FullControl',$inherit,$none,$allow)))
|
|
42
|
+
$acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($system,'FullControl',$inherit,$none,$allow)))
|
|
43
|
+
Set-Acl -LiteralPath $owner -AclObject $acl
|
|
44
|
+
# Keygen requires the directory to exist with its final ACL. It never accepts a
|
|
45
|
+
# target path and creates every key file with CreateNew/no-clobber semantics.
|
|
46
|
+
$keyRaw=@(& $node $keygen 2>&1) -join [Environment]::NewLine
|
|
47
|
+
if($LASTEXITCODE -ne 0){throw ('R193 protected key enrollment blocked: '+$keyRaw.Substring(0,[Math]::Min(500,$keyRaw.Length)))}
|
|
48
|
+
$keyResult=$keyRaw|ConvertFrom-Json
|
|
49
|
+
if([string]$keyResult.status -cne 'R193_OWNER_AUTHORITY_ENROLLED' -or $keyResult.private_key_exported -or $keyResult.authority_granted){
|
|
50
|
+
throw 'R193 key enrollment returned an invalid state'
|
|
51
|
+
}
|
|
52
|
+
Write-Host ('Owner key fingerprint: '+[string]$keyResult.owner_key_id) -ForegroundColor Yellow
|
|
53
|
+
$gesture=[ordered]@{schema='deadbyte.r193-owner-gesture.v1';status='APPROVED';
|
|
54
|
+
request_sha256=[string]$preview.request_sha256;review_phrase=$phrase;
|
|
55
|
+
approved_at_utc=[DateTime]::UtcNow.ToString('o')}
|
|
56
|
+
$gesturePath=Join-Path $owner ('gesture-'+[string]$preview.request_sha256+'.json')
|
|
57
|
+
$bytes=(New-Object Text.UTF8Encoding($false)).GetBytes(($gesture|ConvertTo-Json -Compress)+[Environment]::NewLine)
|
|
58
|
+
$stream=[IO.File]::Open($gesturePath,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
|
|
59
|
+
try{$stream.Write($bytes,0,$bytes.Length);$stream.Flush($true)}finally{$stream.Dispose()}
|
|
60
|
+
$out=@(& $node $broker approve $request 2>&1) -join [Environment]::NewLine
|
|
61
|
+
if($LASTEXITCODE -ne 0){throw ('R193 signing blocked after protected enrollment: '+$out.Substring(0,[Math]::Min(500,$out.Length)))}
|
|
62
|
+
Write-Output $out
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
$root=[IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
3
|
+
$probe=Join-Path $root 'scripts\r193-limited-subject-probe.ps1'
|
|
4
|
+
$expectedSha='16317bffb0765e3856a6f64353ef728754e346636161cf2214ec0d0d3e5c7826'
|
|
5
|
+
if((Get-FileHash $probe).Hash.ToLowerInvariant() -cne $expectedSha){throw 'R193_PROBE_SCRIPT_CHANGED'}
|
|
6
|
+
$output=Join-Path $root 'R193-STANDARD-TOKEN-PROBE.json'
|
|
7
|
+
if(Test-Path $output){throw 'R193_EXISTING_OUTPUT - read before retest'}
|
|
8
|
+
$name='DEADBYTE-R193-Limited-Token-Diagnostic'
|
|
9
|
+
if(Get-ScheduledTask -TaskName $name -TaskPath '\' -ErrorAction SilentlyContinue){throw 'R193_TASK_ALREADY_EXISTS'}
|
|
10
|
+
$identity=[Security.Principal.WindowsIdentity]::GetCurrent()
|
|
11
|
+
$account=$identity.Name
|
|
12
|
+
$ps=Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
|
13
|
+
$arg='-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "'+$probe+'"'
|
|
14
|
+
$action=New-ScheduledTaskAction -Execute $ps -Argument $arg -WorkingDirectory $root
|
|
15
|
+
$trigger=New-ScheduledTaskTrigger -Once -At ((Get-Date).AddDays(1))
|
|
16
|
+
$principal=New-ScheduledTaskPrincipal -UserId $account -LogonType Interactive -RunLevel Limited
|
|
17
|
+
$settings=New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 1)
|
|
18
|
+
$registered=$false
|
|
19
|
+
try {
|
|
20
|
+
$null=Register-ScheduledTask -TaskName $name -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'One-shot R193 native Windows token diagnostic. No grants.' -ErrorAction Stop
|
|
21
|
+
$registered=$true
|
|
22
|
+
$task=Get-ScheduledTask -TaskName $name -ErrorAction Stop
|
|
23
|
+
if([string]$task.Principal.RunLevel -ne 'Limited' -or [string]$task.Principal.LogonType -ne 'Interactive'){throw 'R193_TASK_NOT_LIMITED'}
|
|
24
|
+
Start-ScheduledTask -TaskName $name -ErrorAction Stop
|
|
25
|
+
$done=$false
|
|
26
|
+
for($i=0;$i -lt 28;$i++){
|
|
27
|
+
Start-Sleep -Milliseconds 700
|
|
28
|
+
if(Test-Path -LiteralPath $output -PathType Leaf){$done=$true;break}
|
|
29
|
+
}
|
|
30
|
+
if(-not $done){throw 'R193_LIMITED_TASK_NO_REPORT'}
|
|
31
|
+
$report=Get-Content -LiteralPath $output -Raw | ConvertFrom-Json
|
|
32
|
+
$info=Get-ScheduledTaskInfo -TaskName $name -ErrorAction Stop
|
|
33
|
+
$pass=($report.expected_standard -eq $true -and $report.process_report.elevated -eq $false -and $report.process_report.admin_enabled -eq $false -and $report.authority_granted -eq $false -and $info.LastTaskResult -eq 0)
|
|
34
|
+
[pscustomobject]@{
|
|
35
|
+
schema='deadbyte.r193-limited-token-scheduler-smoke.v1'
|
|
36
|
+
state=$(if($pass){'PASS'}else{'FAIL'})
|
|
37
|
+
limited_task=$name
|
|
38
|
+
scheduled_runlevel=[string]$task.Principal.RunLevel
|
|
39
|
+
native_subject=$report.process_report
|
|
40
|
+
task_exit=$info.LastTaskResult
|
|
41
|
+
source_sha256=$expectedSha
|
|
42
|
+
no_grant=$true
|
|
43
|
+
production_untouched=$true
|
|
44
|
+
}|ConvertTo-Json -Depth 8 -Compress
|
|
45
|
+
if(-not $pass){throw 'R193_EXPECTED_STANDARD_TOKEN_MISSING'}
|
|
46
|
+
} finally {
|
|
47
|
+
if($registered){Unregister-ScheduledTask -TaskName $name -TaskPath '\' -Confirm:$false -ErrorAction Stop}
|
|
48
|
+
}
|
|
49
|
+
if(Get-ScheduledTask -TaskName $name -TaskPath '\' -ErrorAction SilentlyContinue){throw 'R193_PROBE_TASK_NOT_REMOVED'}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
$ErrorActionPreference='Stop'
|
|
2
|
+
$o=[ordered]@{schema='deadbyte.r193-toolchain.v1';toolchain=@{};disk_free_bytes=[int64](Get-PSDrive D).Free}
|
|
3
|
+
foreach($name in @('cl.exe','clang++.exe','g++.exe','cmake.exe','node.exe','powershell.exe')){
|
|
4
|
+
$q=Get-Command $name -ErrorAction SilentlyContinue
|
|
5
|
+
$o.toolchain[$name]=if($null -eq $q){'MISSING'}else{[string]$q.Source}
|
|
6
|
+
}
|
|
7
|
+
$o|ConvertTo-Json -Depth 4 -Compress
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {spawnSync} from 'node:child_process';
|
|
2
|
+
import {fileURLToPath} from 'node:url';
|
|
3
|
+
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
4
|
+
const files=[
|
|
5
|
+
'test/r188-owner-uac.test.mjs',
|
|
6
|
+
'test/r188-uac-boundary.test.mjs',
|
|
7
|
+
'test/r186-owner-grant-broker.test.mjs',
|
|
8
|
+
'test/r193-uac-r70-bridge.test.mjs'
|
|
9
|
+
];
|
|
10
|
+
const r=spawnSync(process.execPath,['--test','--test-concurrency=1',...files],{
|
|
11
|
+
cwd:root,encoding:'utf8',timeout:150000,maxBuffer:1024*1024,windowsHide:true,shell:false
|
|
12
|
+
});
|
|
13
|
+
const output=(r.stdout??'')+'\n'+(r.stderr??'');
|
|
14
|
+
const get=n=>{const m=[...output.matchAll(new RegExp('(?:^|\\n)[ℹ#]\\s*'+n+'\\s+(\\d+)','g'))];return m.length?Number(m.at(-1)[1]):null;};
|
|
15
|
+
const summary={tests:get('tests'),pass:get('pass'),fail:get('fail'),skipped:get('skipped')};
|
|
16
|
+
const pass=r.status===0&&summary.tests>=12&&summary.fail===0&&summary.pass===summary.tests-summary.skipped;
|
|
17
|
+
console.log(JSON.stringify({schema:'deadbyte.r193-uac-bridge-focused.v1',
|
|
18
|
+
status:pass?'PASS':'FAIL',exit:r.status,summary,files,
|
|
19
|
+
no_real_owner_enrollment:true,authority_granted:false,
|
|
20
|
+
production_untouched:true,tail:pass?null:output.slice(-12000)}));
|
|
21
|
+
if(!pass)process.exitCode=1;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// R188 - no Windows Hello, no PEM owner key, no grant issuance.
|
|
2
|
+
// Unprivileged challenge creation and read-only owner-review validation.
|
|
3
|
+
// Only an independently launched, R89-protected native elevated broker
|
|
4
|
+
// may record an actual approval. Do not turn this into a grant primitive.
|
|
5
|
+
import {createHash,randomBytes} from 'node:crypto';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
8
|
+
const SHA=/^[a-f0-9]{64}$/;
|
|
9
|
+
const CHALLENGE=/^[A-Za-z0-9_-]{43}$/;
|
|
10
|
+
const ID=/^[A-Za-z0-9._-]{1,64}$/;
|
|
11
|
+
const digest=x=>createHash('sha256').update(Buffer.from(canonicalJson(x),'utf8')).digest('hex');
|
|
12
|
+
function invalid(msg){throw Error('R188 '+msg);}
|
|
13
|
+
function exact(value,fields,label){
|
|
14
|
+
if(!value||typeof value!=='object'||Array.isArray(value)||
|
|
15
|
+
Object.keys(value).length!==fields.length||
|
|
16
|
+
fields.some(f=>!Object.hasOwn(value,f)))invalid(label+' schema mismatch');
|
|
17
|
+
}
|
|
18
|
+
function project(p){
|
|
19
|
+
if(typeof p!=='string'||!path.win32.isAbsolute(p)||p.startsWith('\\\\')||
|
|
20
|
+
path.win32.normalize(p).toLowerCase()===path.win32.parse(p).root.toLowerCase())
|
|
21
|
+
invalid('project root must be a bounded local Windows path');
|
|
22
|
+
return path.win32.normalize(p);
|
|
23
|
+
}
|
|
24
|
+
function fields(o){
|
|
25
|
+
for(const k of ['machineEpoch','releaseManifestSha256','codingPolicySha256','autonomousPolicySha256']){
|
|
26
|
+
if(typeof o?.[k]!=='string'||!SHA.test(o[k]))invalid('invalid '+k);
|
|
27
|
+
}
|
|
28
|
+
if(o.rootId!=='core'||!ID.test(o.rootId))invalid('root scope must be core');
|
|
29
|
+
const root=project(o.projectRoot);
|
|
30
|
+
if(!Number.isSafeInteger(o.maxSeconds)||o.maxSeconds<1||o.maxSeconds>3600)
|
|
31
|
+
invalid('maxSeconds outside owner-reviewed window');
|
|
32
|
+
return root;
|
|
33
|
+
}
|
|
34
|
+
const scopeKeys=['schema','machine_epoch','release_manifest_sha256',
|
|
35
|
+
'coding_policy_sha256','autonomous_policy_sha256','root_id','project_root',
|
|
36
|
+
'operation','max_seconds','issued_at_utc','expires_at_utc'];
|
|
37
|
+
const requestKeys=['schema','challenge','subject','request_sha256','review_phrase',
|
|
38
|
+
'owner_approved','authority_granted','require_interactive_uac'];
|
|
39
|
+
function iso(v){
|
|
40
|
+
if(typeof v!=='string'||!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(v)||
|
|
41
|
+
!Number.isFinite(Date.parse(v))||new Date(v).toISOString()!==v)
|
|
42
|
+
invalid('UTC time not canonical');
|
|
43
|
+
return Date.parse(v);
|
|
44
|
+
}
|
|
45
|
+
function hashFor(challenge,subject){return digest({schema:'deadbyte.r188-owner-uac-request.v1',challenge,subject});}
|
|
46
|
+
function phrase(hash){return 'APPROVE-'+hash.slice(0,12).toUpperCase();}
|
|
47
|
+
export function createR188OwnerUacRequest({
|
|
48
|
+
machineEpoch,releaseManifestSha256,codingPolicySha256,autonomousPolicySha256,
|
|
49
|
+
rootId,projectRoot,maxSeconds,now=new Date(),expiresInMs=120000
|
|
50
|
+
}={}){
|
|
51
|
+
const input={machineEpoch,releaseManifestSha256,codingPolicySha256,
|
|
52
|
+
autonomousPolicySha256,rootId,projectRoot,maxSeconds};
|
|
53
|
+
const normalized=fields(input);
|
|
54
|
+
if(!(now instanceof Date)||!Number.isFinite(now.getTime())||
|
|
55
|
+
!Number.isSafeInteger(expiresInMs)||expiresInMs<1000||expiresInMs>300000)
|
|
56
|
+
invalid('request lifetime invalid');
|
|
57
|
+
const subject=Object.freeze({
|
|
58
|
+
schema:'deadbyte.r188-owner-uac-subject.v1',machine_epoch:machineEpoch,
|
|
59
|
+
release_manifest_sha256:releaseManifestSha256,
|
|
60
|
+
coding_policy_sha256:codingPolicySha256,
|
|
61
|
+
autonomous_policy_sha256:autonomousPolicySha256,
|
|
62
|
+
root_id:rootId,project_root:normalized,
|
|
63
|
+
operation:'autonomous_goal_run',max_seconds:maxSeconds,
|
|
64
|
+
issued_at_utc:now.toISOString(),
|
|
65
|
+
expires_at_utc:new Date(now.getTime()+expiresInMs).toISOString()
|
|
66
|
+
});
|
|
67
|
+
const challenge=randomBytes(32).toString('base64url');
|
|
68
|
+
const request_sha256=hashFor(challenge,subject);
|
|
69
|
+
return Object.freeze({schema:'deadbyte.r188-owner-uac-request.v1',
|
|
70
|
+
challenge,subject,request_sha256,review_phrase:phrase(request_sha256),
|
|
71
|
+
owner_approved:false,authority_granted:false,require_interactive_uac:true});
|
|
72
|
+
}
|
|
73
|
+
export function verifyR188OwnerUacRequest({request,expected,now=new Date()}={}){
|
|
74
|
+
exact(request,requestKeys,'request');exact(request.subject,scopeKeys,'subject');
|
|
75
|
+
const subject=request.subject;
|
|
76
|
+
const root=fields(expected);
|
|
77
|
+
if(request.schema!=='deadbyte.r188-owner-uac-request.v1'||
|
|
78
|
+
subject.schema!=='deadbyte.r188-owner-uac-subject.v1'||
|
|
79
|
+
request.owner_approved!==false||request.authority_granted!==false||
|
|
80
|
+
request.require_interactive_uac!==true||
|
|
81
|
+
typeof request.challenge!=='string'||!CHALLENGE.test(request.challenge)||
|
|
82
|
+
Buffer.from(request.challenge,'base64url').length!==32||
|
|
83
|
+
Buffer.from(request.challenge,'base64url').toString('base64url')!==request.challenge)
|
|
84
|
+
invalid('request shape or challenge invalid');
|
|
85
|
+
const expectedBindings={
|
|
86
|
+
machine_epoch:expected.machineEpoch,release_manifest_sha256:expected.releaseManifestSha256,
|
|
87
|
+
coding_policy_sha256:expected.codingPolicySha256,
|
|
88
|
+
autonomous_policy_sha256:expected.autonomousPolicySha256,root_id:expected.rootId,
|
|
89
|
+
project_root:root,operation:'autonomous_goal_run',max_seconds:expected.maxSeconds
|
|
90
|
+
};
|
|
91
|
+
for(const [k,v] of Object.entries(expectedBindings)){
|
|
92
|
+
if(subject[k]!==v)invalid('request scope mismatch: '+k);
|
|
93
|
+
}
|
|
94
|
+
if(!(now instanceof Date)||!Number.isFinite(now.getTime()))invalid('current time invalid');
|
|
95
|
+
const issued=iso(subject.issued_at_utc),expires=iso(subject.expires_at_utc);
|
|
96
|
+
if(expires<=issued||expires-issued>300000||
|
|
97
|
+
now.getTime()<issued||now.getTime()>=expires)invalid('request expired or not yet valid');
|
|
98
|
+
if(typeof request.request_sha256!=='string'||!SHA.test(request.request_sha256)||
|
|
99
|
+
request.request_sha256!==hashFor(request.challenge,subject)||
|
|
100
|
+
request.review_phrase!==phrase(request.request_sha256))
|
|
101
|
+
invalid('request digest or owner phrase altered');
|
|
102
|
+
return Object.freeze({status:'VERIFIED_FOR_REVIEW_ONLY',request_sha256:request.request_sha256,
|
|
103
|
+
review_phrase:request.review_phrase,authority_granted:false,owner_approved:false,
|
|
104
|
+
next_gate:'PROTECTED_INTERACTIVE_UAC_WITH_EXACT_REQUEST_AND_EPOCH'});
|
|
105
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// R188: pure fail-closed decision gate, NOT proof of a Windows UAC action.
|
|
2
|
+
// A privileged native broker MUST determine these facts independently using
|
|
3
|
+
// token and ACL inspection. Never accept a caller's self-reported booleans.
|
|
4
|
+
export function assessR188UacBoundary(e={}){
|
|
5
|
+
const finish=(reason)=>Object.freeze({schema:'deadbyte.r188-uac-boundary.v1',
|
|
6
|
+
status:'BLOCKED',reason,authority_granted:false,grant_issued:false});
|
|
7
|
+
if(e.codingElevation!=='standard')return finish('CODING_ALREADY_ELEVATED');
|
|
8
|
+
if(e.privilegedParentReachable!==false)return finish('ELEVATED_PARENT_STILL_REACHABLE');
|
|
9
|
+
if(e.ownerElevation!=='elevated')return finish('OWNER_NOT_ELEVATED');
|
|
10
|
+
if(e.protectedBroker!==true)return finish('BROKER_NOT_PROTECTED');
|
|
11
|
+
if(e.enableUac!==true||e.secureDesktop!==true||
|
|
12
|
+
e.promptMode!=='explicit_secure_desktop')
|
|
13
|
+
return finish('UAC_NOT_INTERACTIVE_AND_SECURE');
|
|
14
|
+
if(e.nativeEpochPinned!==true||e.releaseAndPoliciesPinned!==true)
|
|
15
|
+
return finish('AUTHORITY_EPOCH_OR_POLICY_NOT_PINNED');
|
|
16
|
+
if(e.ownerGesture!=='approved')return finish('OWNER_NOT_APPROVED');
|
|
17
|
+
return Object.freeze({schema:'deadbyte.r188-uac-boundary.v1',
|
|
18
|
+
status:'ELIGIBLE_FOR_PROTECTED_OWNER_REVIEW',reason:null,
|
|
19
|
+
authority_granted:false,grant_issued:false,
|
|
20
|
+
next_gate:'SIGNED_EPOCH_ONE_TIME_GRANT_TRANSACTION'});
|
|
21
|
+
}
|