deadbyte-mcp 0.15.59 → 0.15.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.SHA256 +31 -26
- package/PROVENANCE.json +8 -8
- package/README.txt +18 -18
- package/RELEASE.json +9 -9
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bridge/package.json +1 -1
- package/node_modules/which/CHANGELOG.md +166 -0
- package/package.json +1 -1
- package/scripts/bootstrap-uac-migration-r153.ps1 +21 -5
- package/scripts/deploy-r48.mjs +10 -1
- package/scripts/issue-uac-migration-r153.mjs +12 -5
- package/scripts/r153-prepublish-identity.mjs +3 -3
- package/scripts/request-uac-migration-r153.ps1 +26 -9
- package/scripts/verify-protected-stage-r153.mjs +1 -0
- package/src/activation-lock-r65.mjs +37 -1
- package/src/clean-activation-recovery-r153.mjs +1 -1
- package/src/deadbyte-cli.mjs +16 -6
- package/src/host-command-pins-r154.mjs +34 -0
- package/src/uac-approval-contract-r153.mjs +6 -3
- package/src/version.mjs +1 -1
- package/test/r130-release-security-contract.test.mjs +8 -8
- package/test/r132-release-identity.test.mjs +7 -7
- package/test/r153-clean-activation-recovery.test.mjs +2 -2
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r153-r60-host-repin-e2e-contract.test.mjs +30 -0
- package/test/r153-r65-signed-precondition-recovery.test.mjs +58 -0
- package/test/r153-staging-leaf-acl.test.mjs +19 -8
- package/test/r153-uac-approval-contract.test.mjs +5 -3
- package/test/r153-uac-launch-parameter-contract.test.mjs +3 -2
- package/test/r154-multipin-recovery.test.mjs +45 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash,generateKeyPairSync,sign as signBytes} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
8
|
+
import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
|
|
9
|
+
|
|
10
|
+
const sha=buffer=>createHash('sha256').update(buffer).digest('hex');
|
|
11
|
+
const domain=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
|
|
12
|
+
const now=()=>new Date().toISOString();
|
|
13
|
+
function seal(body,key,pub){
|
|
14
|
+
const payload=Buffer.from(canonicalJson(body));
|
|
15
|
+
const signature=signBytes(null,Buffer.concat([domain,payload]),key).toString('base64');
|
|
16
|
+
const e={body,body_sha256:sha(payload),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',
|
|
17
|
+
key_id:sha(pub.export({type:'spki',format:'der'})),signature_base64:signature}};
|
|
18
|
+
return {...e,receipt_sha256:sha(Buffer.from(canonicalJson(e)))};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test('signed R27 initial precondition reject safely reconciles R65 unknown claim',async()=>{
|
|
22
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r153-precondition-'));
|
|
23
|
+
try{
|
|
24
|
+
const updates=path.join(root,'evidence','updates');
|
|
25
|
+
await mkdir(updates,{recursive:true});await mkdir(path.join(root,'trust'),{recursive:true});
|
|
26
|
+
const {privateKey,publicKey}=generateKeyPairSync('ed25519');
|
|
27
|
+
await writeFile(path.join(root,'trust','ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}));
|
|
28
|
+
const manager=createActivationClaimManager({stateRoot:root}),claim=await manager.claim('a'.repeat(64));
|
|
29
|
+
const pointer={schema:'deadbyte.active-release.v1',release_id:'predecessor-r153',version:'0.15.45',
|
|
30
|
+
manifest_sha256:'b'.repeat(64),generation_manifest_sha256:'c'.repeat(64)};
|
|
31
|
+
await writeFile(path.join(root,'active-release.json'),JSON.stringify(pointer));
|
|
32
|
+
const dir=path.join(updates,'r27-'+'d'.repeat(16)+'-synthetic');
|
|
33
|
+
await mkdir(dir);
|
|
34
|
+
const failure=seal({schema:'deadbyte.r27-slot-update-failure.v1',update_id:path.basename(dir),
|
|
35
|
+
release_version:'0.15.61',candidate_manifest_sha256:'d'.repeat(64),failed_at_utc:now(),
|
|
36
|
+
result:'PRECONDITION_REJECTED',phase:'initial',error:{name:'Error',message:"host command 'powershell_script' executable pin drift detected; explicit approval required"},
|
|
37
|
+
prior_pointer:pointer,attempted_pointer:null,rollback:{attempted:false,errors:[]}},privateKey,publicKey);
|
|
38
|
+
await writeFile(path.join(dir,'failure-receipt.json'),JSON.stringify(failure));
|
|
39
|
+
await writeFile(path.join(updates,'r65-activation-outcome-test.json'),JSON.stringify({
|
|
40
|
+
schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:claim.operation_id,
|
|
41
|
+
preflight_subject_sha256:claim.subject_sha256,candidate_version:'0.15.61',
|
|
42
|
+
candidate_manifest_sha256:'d'.repeat(64),predecessor_release_id:pointer.release_id,
|
|
43
|
+
outcome:{state:'ACTIVATION_OUTCOME_UNKNOWN'},update_id:null,continuity_update_id:null,state_path:null,observed_at_utc:now()
|
|
44
|
+
}));
|
|
45
|
+
// A tampered signed envelope must not authorize reclaim.
|
|
46
|
+
const failurePath=path.join(dir,'failure-receipt.json');
|
|
47
|
+
await writeFile(failurePath,JSON.stringify({...failure,body:{...failure.body,phase:'committing'}}));
|
|
48
|
+
await assert.rejects(()=>manager.claim('e'.repeat(64)),/unresolved|in progress/);
|
|
49
|
+
await writeFile(failurePath,JSON.stringify(failure));
|
|
50
|
+
// Even a valid signature cannot authorize reclaim if the live pointer changed.
|
|
51
|
+
await writeFile(path.join(root,'active-release.json'),JSON.stringify({...pointer,release_id:'foreign-runtime'}));
|
|
52
|
+
await assert.rejects(()=>manager.claim('e'.repeat(64)),/unresolved|in progress/);
|
|
53
|
+
await writeFile(path.join(root,'active-release.json'),JSON.stringify(pointer));
|
|
54
|
+
const next=await manager.claim('e'.repeat(64));
|
|
55
|
+
assert.notEqual(next.operation_id,claim.operation_id);
|
|
56
|
+
await next.release();
|
|
57
|
+
}finally{await rm(root,{recursive:true,force:true})}
|
|
58
|
+
});
|
|
@@ -20,28 +20,39 @@ test('R153 Windows ACL reproduction rejects stripped leaf ACE and restores direc
|
|
|
20
20
|
$root = Join-Path $env:TEMP ('r153-native-acl-test-' + [guid]::NewGuid().ToString('N'))
|
|
21
21
|
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
22
22
|
$leaf = Join-Path $root 'child\file.txt'
|
|
23
|
+
function Read-Digest([string]$Path) {
|
|
24
|
+
try {
|
|
25
|
+
$sha = [Security.Cryptography.SHA256]::Create()
|
|
26
|
+
try { return ([BitConverter]::ToString($sha.ComputeHash([IO.File]::ReadAllBytes($Path)))).Replace('-','') }
|
|
27
|
+
finally { $sha.Dispose() }
|
|
28
|
+
} catch [UnauthorizedAccessException] { return $null }
|
|
29
|
+
}
|
|
23
30
|
try {
|
|
24
31
|
New-Item -ItemType Directory -Path (Split-Path -Parent $leaf) -Force | Out-Null
|
|
25
32
|
[IO.File]::WriteAllText($leaf,'read-only regression fixture')
|
|
26
33
|
& icacls.exe $root '/inheritance:r' '/grant:r' ('*'+$sid+':(OI)(CI)(F)') '/T' '/C' | Out-Null
|
|
27
34
|
if($LASTEXITCODE -ne 0){throw 'REPRO_SETUP_FAILED'}
|
|
28
|
-
$blocked =
|
|
35
|
+
$blocked = Read-Digest $leaf
|
|
29
36
|
if($null -ne $blocked){throw 'REPRO_DID_NOT_STRIP_LEAF_ACE'}
|
|
30
37
|
& icacls.exe $root '/grant' ('*'+$sid+':(F)') '/T' '/C' | Out-Null
|
|
31
38
|
if($LASTEXITCODE -ne 0){throw 'LEAF_REPAIR_FAILED'}
|
|
32
|
-
$restored =
|
|
33
|
-
if($null -eq $restored -or $restored.
|
|
34
|
-
$acl =
|
|
35
|
-
$allow=@($acl.
|
|
39
|
+
$restored = Read-Digest $leaf
|
|
40
|
+
if($null -eq $restored -or $restored.Length -ne 64){throw 'LEAF_NOT_READABLE_AFTER_REPAIR'}
|
|
41
|
+
$acl = [IO.File]::GetAccessControl($leaf)
|
|
42
|
+
$allow=@($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier]) | Where-Object {$_.AccessControlType -eq 'Allow'})
|
|
36
43
|
if($allow.Count -ne 1){throw 'LEAF_HAS_UNEXPECTED_ALLOW_ACE_COUNT'}
|
|
37
|
-
if($allow[0].IdentityReference.
|
|
44
|
+
if($allow[0].IdentityReference.Value -ne $sid){throw 'LEAF_HAS_UNEXPECTED_TRUSTEE'}
|
|
38
45
|
Write-Output 'R153_WIN32_LEAF_ACL_REPAIR_PASS'
|
|
39
46
|
} finally {
|
|
40
|
-
if(Test-Path -LiteralPath $root){
|
|
47
|
+
if(Test-Path -LiteralPath $root){
|
|
48
|
+
& icacls.exe $root '/reset' '/T' '/C' | Out-Null
|
|
49
|
+
& icacls.exe $root '/inheritance:e' '/grant:r' ('*'+$sid+':(OI)(CI)(F)') '/T' '/C' | Out-Null
|
|
50
|
+
Remove-Item -LiteralPath $root -Force -Recurse
|
|
51
|
+
}
|
|
41
52
|
}`;
|
|
42
53
|
const encoded=Buffer.from(ps,'utf16le').toString('base64');
|
|
43
54
|
const result=spawnSync('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',encoded],
|
|
44
55
|
{encoding:'utf8',timeout:30000,maxBuffer:256*1024});
|
|
45
56
|
assert.equal(result.status,0,result.stderr||result.stdout||String(result.error));
|
|
46
57
|
assert.match(result.stdout,/R153_WIN32_LEAF_ACL_REPAIR_PASS/);
|
|
47
|
-
});
|
|
58
|
+
});
|
|
@@ -4,12 +4,13 @@ import {createUacApproval,verifyUacApproval,validateApprovalWindow,requireTruste
|
|
|
4
4
|
import {readFileSync} from 'node:fs';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
const h=(n)=>n.repeat(64);
|
|
7
|
-
const base=()=>({stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp',candidateRoot:'C:\\Program Files\\DEADBYTE-R153-TEST',candidateVersion:'0.15.
|
|
7
|
+
const base=()=>({stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp',candidateRoot:'C:\\Program Files\\DEADBYTE-R153-TEST',candidateVersion:'0.15.61',candidateManifestSha256:h('a'),tarballSha256:h('b'),markerReceiptSha256:h('c'),predecessorPointerSha256:h('d'),predecessorPolicySha256:{process:h('1'),coding:h('2'),autonomous:h('3'),desktop:h('4'),host:h('5')},preflightSubjectSha256:h('e'),nodeSha256:h('f'),deployScriptSha256:h('0'),hostCommandPins:[],nonce:h('6'),issuedAt:'2026-10-24T00:00:00.000Z',expiresAt:'2026-10-24T00:05:00.000Z'});
|
|
8
8
|
test('R153 UAC receipt carries exact no-grants narrow migration subject',()=>{
|
|
9
|
-
const x=base(),receipt=createUacApproval(x);
|
|
9
|
+
const x={...base(),hostCommandPins:[{command_id:'powershell_script',sha256:h('a')},{command_id:'cmd_script',sha256:h('b')}]},receipt=createUacApproval(x);
|
|
10
10
|
assert.equal(receipt.schema,'deadbyte.r153-uac-approval.v1');
|
|
11
11
|
assert.equal(receipt.action,'narrow_legacy_policy_and_activate');
|
|
12
12
|
assert.deepEqual(receipt.allowed_authority_after,{process:false,coding:false,autonomous:false,desktop:false,host:false});
|
|
13
|
+
assert.deepEqual(receipt.host_command_pins,[{command_id:'cmd_script',sha256:h('b')},{command_id:'powershell_script',sha256:h('a')}]);
|
|
13
14
|
assert.match(receipt.subject_sha256,/^[0-9a-f]{64}$/);
|
|
14
15
|
assert.deepEqual(verifyUacApproval(receipt,x,{now:'2026-10-24T00:02:00.000Z'}),receipt);
|
|
15
16
|
});
|
|
@@ -19,6 +20,7 @@ test('R153 UAC receipt rejects drift across every authority/candidate/runtime in
|
|
|
19
20
|
assert.throws(()=>verifyUacApproval(receipt,{...x,[key]:value},{now:'2026-10-24T00:02:00.000Z'}),undefined,key);
|
|
20
21
|
}
|
|
21
22
|
assert.throws(()=>verifyUacApproval(receipt,{...x,predecessorPolicySha256:{...x.predecessorPolicySha256,host:h('a')}},{now:'2026-10-24T00:02:00.000Z'}));
|
|
23
|
+
assert.throws(()=>verifyUacApproval(receipt,{...x,hostCommandPins:[{command_id:'cmd_script',sha256:h('a')}]},{now:'2026-10-24T00:02:00.000Z'}));
|
|
22
24
|
});
|
|
23
25
|
test('R153 UAC receipt fails closed on expiration, malformed, replay-like tamper and grant widening',()=>{
|
|
24
26
|
const x=base(),receipt=createUacApproval(x);
|
|
@@ -31,7 +33,7 @@ test('R153 UAC receipt fails closed on expiration, malformed, replay-like tamper
|
|
|
31
33
|
assert.throws(()=>createUacApproval({...x,issuedAt:'not-a-date'}));
|
|
32
34
|
assert.throws(()=>createUacApproval({...x,nonce:'x'}));
|
|
33
35
|
});
|
|
34
|
-
test('
|
|
36
|
+
test('R154 UAC scope only accepts protected root and exact v0.15.61',()=>{
|
|
35
37
|
assert.throws(()=>createUacApproval({...base(),candidateRoot:'C:\\Users\\DEADBYTE\\npm-cache'}),/protected/i);
|
|
36
38
|
assert.throws(()=>createUacApproval({...base(),candidateVersion:'0.15.55'}),/candidate/i);
|
|
37
39
|
assert.throws(()=>validateApprovalWindow('2026-10-24T00:00:00.000Z','2026-10-24T00:00:00.000Z','2026-10-24T00:00:00.000Z'));
|
|
@@ -45,7 +45,7 @@ test('R153 RED: mutable npx entrypoint stages exact registry package before priv
|
|
|
45
45
|
assert.doesNotMatch(script,/npm-cli\.js|npm install/i);
|
|
46
46
|
assert.match(script,/Get-FileHash -LiteralPath \$file -Algorithm SHA256/);
|
|
47
47
|
assert.match(script,/NODE_OPTIONS/);
|
|
48
|
-
assert.match(script,/https:\/\/registry\.npmjs\.org\/deadbyte-mcp\/0\.15\.
|
|
48
|
+
assert.match(script,/https:\/\/registry\.npmjs\.org\/deadbyte-mcp\/0\.15\.61/);
|
|
49
49
|
assert.match(script,/live npm registry tarball SHA-256 mismatch/);
|
|
50
50
|
assert.match(script,/live npm registry tarball integrity mismatch/);
|
|
51
51
|
assert.match(script,/DEADBYTE-R153-Stage-/);
|
|
@@ -54,6 +54,7 @@ test('R153 RED: mutable npx entrypoint stages exact registry package before priv
|
|
|
54
54
|
assert.match(script,/'\/inheritance:r'/);
|
|
55
55
|
assert.match(script,/verify-protected-stage-r153\.mjs/);
|
|
56
56
|
assert.match(script,/request-uac-migration-r153\.ps1/);
|
|
57
|
-
assert.match(script
|
|
57
|
+
assert.match(script,/\$helperArgs\s*=\s*@\('-Elevated'/);
|
|
58
|
+
assert.match(script,/& \$helper @helperArgs/);
|
|
58
59
|
assert.match(script,/Remove-Item -LiteralPath \$prefix -Recurse -Force/);
|
|
59
60
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import {
|
|
6
|
+
normalizeHostCommandPins,
|
|
7
|
+
encodeHostCommandPins,
|
|
8
|
+
decodeHostCommandPins
|
|
9
|
+
} from '../src/host-command-pins-r154.mjs';
|
|
10
|
+
|
|
11
|
+
const h=value=>value.repeat(64);
|
|
12
|
+
|
|
13
|
+
test('R154 accepts a bounded set of distinct existing-command repins',()=>{
|
|
14
|
+
const pins=normalizeHostCommandPins([
|
|
15
|
+
`powershell_script=${h('a')}`,
|
|
16
|
+
`cmd_script=${h('b')}`,
|
|
17
|
+
`cmd_inline=${h('b')}`
|
|
18
|
+
]);
|
|
19
|
+
assert.deepEqual(pins,[
|
|
20
|
+
{command_id:'cmd_inline',sha256:h('b')},
|
|
21
|
+
{command_id:'cmd_script',sha256:h('b')},
|
|
22
|
+
{command_id:'powershell_script',sha256:h('a')}
|
|
23
|
+
]);
|
|
24
|
+
assert.deepEqual(decodeHostCommandPins(encodeHostCommandPins(pins)),pins);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('R154 rejects duplicate, malformed, oversized, and delimiter-injected repins',()=>{
|
|
28
|
+
assert.throws(()=>normalizeHostCommandPins([`cmd_script=${h('a')}`,`cmd_script=${h('a')}`]),/duplicate/i);
|
|
29
|
+
assert.throws(()=>normalizeHostCommandPins([`unrelated_command=${h('a')}`]),/not repinnable/i);
|
|
30
|
+
assert.throws(()=>normalizeHostCommandPins([`bad/name=${h('a')}`]),/contract/i);
|
|
31
|
+
assert.throws(()=>normalizeHostCommandPins([`cmd_script=${h('A')}`]),/contract/i);
|
|
32
|
+
assert.throws(()=>normalizeHostCommandPins(Array.from({length:17},(_,i)=>`cmd_${i}=${h('a')}`)),/limit/i);
|
|
33
|
+
assert.throws(()=>decodeHostCommandPins('["cmd_script='+h('a')+'","evil"]'),/contract/i);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('R154 CLI no longer rejects the second explicit command repin in parsing',()=>{
|
|
37
|
+
const cli=fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url));
|
|
38
|
+
const run=spawnSync(process.execPath,[cli,'remote',
|
|
39
|
+
'--host-command-pin',`powershell_script=${h('a')}`,
|
|
40
|
+
'--host-command-pin',`cmd_script=${h('b')}`,
|
|
41
|
+
'status'],{encoding:'utf8',timeout:8000});
|
|
42
|
+
assert.equal(run.status,1);
|
|
43
|
+
assert.doesNotMatch(run.stderr,/may be specified once|requires powershell_script/);
|
|
44
|
+
assert.match(run.stderr,/presentation flags are valid only for foreground session/);
|
|
45
|
+
});
|
|
@@ -83,7 +83,7 @@ test('R72 compact baseline permits only R64 read offset and authorized coding-on
|
|
|
83
83
|
assert.deepEqual(diff.added,[]);
|
|
84
84
|
assert.deepEqual(diff.removed,[]);
|
|
85
85
|
const packageVersion=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8')).version;
|
|
86
|
-
if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59'].includes(packageVersion)){
|
|
86
|
+
if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60','0.15.61'].includes(packageVersion)){
|
|
87
87
|
assert.deepEqual([...diff.schema_changed].sort(),['machine_fs','native_diff_preview']);
|
|
88
88
|
assert.equal(diff.unchanged.length,41);
|
|
89
89
|
const prior=baseline.tools.find(tool=>tool.name==='machine_fs');
|