deadbyte-mcp 0.15.42 → 0.15.44
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 +131 -68
- package/PROVENANCE.json +37 -13
- package/README.txt +18 -18
- package/RELEASE.json +40 -14
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +52 -10
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-native-inspect-r67.exe +0 -0
- package/bin/deadbyte-native-inspect-r67.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-desktop-policy.json +5 -5
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R67-ALPHA-REFERENCE-DIFFERENTIAL.md +42 -0
- package/docs/R67-ALPHA-STATIC-COMPARISON.json +205 -0
- package/docs/R67-CANDIDATE-PARENT.json +10 -0
- package/docs/R67-EVIDENCE-RECONCILE-CLOSEOUT-20261011.md +64 -0
- package/docs/R67-FULL-REGRESSION-UNKNOWN.json +27 -0
- package/docs/R67-NATIVE-INSPECT-BINARY.json +11 -0
- package/docs/R67-NPM-EXDEV-PRESERVATION-20261011.md +39 -0
- package/docs/R67-OBSERVATION-TRUTH.md +82 -0
- package/docs/R67-REFERENCE-GAP-MATRIX.json +57 -0
- package/docs/R67-REGRESSION-CLOSURE-20261011.md +24 -0
- package/native/deadbyte-native-inspect-r67.cpp +252 -0
- package/package.json +7 -3
- package/proof/CONTAINMENT-BUILD.txt +6 -6
- package/scripts/build-native-inspector-r67.ps1 +28 -0
- package/scripts/gate-windows-r66-prepublish.ps1 +65 -0
- package/scripts/gate-windows-r67.ps1 +56 -0
- package/scripts/npm-preserve-artifact-r67.mjs +99 -0
- package/scripts/npm-release-r48.mjs +10 -2
- package/scripts/npm-release-readiness-r67.mjs +26 -0
- package/scripts/r67-acquire-alpha-reference.mjs +95 -0
- package/scripts/r67-audit-nonpass-source.mjs +39 -0
- package/scripts/r67-compare-desktop-alpha.mjs +48 -0
- package/scripts/r67-diagnose-failed-shard.mjs +23 -0
- package/scripts/r67-durable-batch.mjs +42 -0
- package/scripts/r67-focused-gate.mjs +58 -0
- package/scripts/r67-full-regression.mjs +28 -0
- package/scripts/r67-independent-observation-verify.mjs +36 -0
- package/scripts/r67-independent-runtime-observation-verify.mjs +63 -0
- package/scripts/r67-nonpass-adjudicate.mjs +20 -0
- package/scripts/r67-publish-boundary-audit.mjs +32 -0
- package/scripts/r67-regression-reconcile.mjs +10 -0
- package/scripts/r67-regression-shard-run.mjs +70 -0
- package/scripts/r67-run-shard-batch.mjs +18 -0
- package/scripts/r67-stage-candidate.mjs +30 -0
- package/scripts/r67-verify-parent-ping.mjs +22 -0
- package/scripts/release-manifest.mjs +1 -0
- package/src/activation-continuity-engine-r65.mjs +2 -1
- package/src/autonomous-runtime.mjs +1 -1
- package/src/deadbyte-cli.mjs +33 -1
- package/src/doctor-runtime-r58.mjs +3 -1
- package/src/machine-fs-smoke-client.mjs +13 -5
- package/src/machine-identity.mjs +59 -2
- package/src/machine-mcp-tools.mjs +8 -0
- package/src/mcp-server.mjs +11 -2
- package/src/native-inspector-r67.mjs +66 -0
- package/src/r67-diagnostic-export.mjs +118 -0
- package/src/r67-diagnostics-verify.mjs +82 -0
- package/src/r67-evidence-reconcile.mjs +128 -0
- package/src/r67-native-observation-authority.mjs +45 -0
- package/src/r67-observation-receipt.mjs +43 -0
- package/src/r67-regression-shards.mjs +176 -0
- package/src/remote-live-log.mjs +14 -10
- package/src/tool-audit.mjs +1 -1
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-loop.test.mjs +11 -6
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/output-schema.test.mjs +1 -1
- package/test/r51-live-log-polling.test.mjs +12 -0
- package/test/r53-release-contract.test.mjs +1 -1
- package/test/r54-release-contract.test.mjs +1 -1
- package/test/r55-release-contract.test.mjs +1 -1
- package/test/r56-release-contract.test.mjs +1 -1
- package/test/r57-release-contract.test.mjs +1 -1
- package/test/r58-bundled-bridge.test.mjs +1 -1
- package/test/r58-doctor-runtime.test.mjs +18 -0
- package/test/r58-release-contract.test.mjs +2 -2
- package/test/r58-windows-gate.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r60-release-contract.test.mjs +3 -3
- package/test/r61-release-contract.test.mjs +1 -1
- package/test/r62-release-contract.test.mjs +1 -1
- package/test/r63-release-contract.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +1 -1
- package/test/r65-epoch-native-activation-integration.test.mjs +4 -4
- package/test/r65-release-contract.test.mjs +2 -1
- package/test/r66-release-contract.test.mjs +57 -0
- package/test/r67-cli-integration.test.mjs +41 -0
- package/test/r67-diagnostics-cli.test.mjs +60 -0
- package/test/r67-diagnostics-verify.test.mjs +49 -0
- package/test/r67-diagnostics-zip-oracle.test.mjs +38 -0
- package/test/r67-diagnostics.test.mjs +60 -0
- package/test/r67-evidence-reconcile.test.mjs +186 -0
- package/test/r67-isolated-slot-rollback-restore.test.mjs +62 -0
- package/test/r67-live-log-secret-canary.test.mjs +44 -0
- package/test/r67-machine-fs-smoke-epoch.test.mjs +22 -0
- package/test/r67-native-inspector.test.mjs +131 -0
- package/test/r67-native-observation-authority.test.mjs +49 -0
- package/test/r67-npm-cross-volume.test.mjs +74 -0
- package/test/r67-npm-publish-gate.test.mjs +38 -0
- package/test/r67-regression-shards.test.mjs +184 -0
- package/test/r67-release-contract.test.mjs +44 -0
- package/test/r67-runtime-observation-binding.test.mjs +86 -0
- package/test/r67-server-observation-wiring.test.mjs +12 -0
- package/test/r67-signed-receipt.test.mjs +66 -0
- package/test/r67-skip-rationale.test.mjs +31 -0
- package/test/r67-tool-annotations.test.mjs +21 -0
- package/test/r67-windows-gate-argv.test.mjs +13 -0
- package/test/release-version.test.mjs +1 -1
- package/test/remote-live-log-r36.test.mjs +10 -0
- package/test/remote-live-log-r37.test.mjs +29 -0
- package/test/semantic-multilang.test.mjs +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {mkdtemp,writeFile,rm} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {makeOfflineDiagnosticBundle} from '../src/r67-diagnostic-export.mjs';
|
|
8
|
+
test('R67 ZIP interoperates with independent Windows .NET ZipArchive reader',async()=>{
|
|
9
|
+
const folder=await mkdtemp(path.join(os.tmpdir(),'r67-dotnet-zip-'));
|
|
10
|
+
try{
|
|
11
|
+
const file=path.join(folder,'diag.zip');
|
|
12
|
+
const x=makeOfflineDiagnosticBundle({releaseId:'v0.15.43-'+'a'.repeat(16),manifestSha256:'a'.repeat(64),logs:[
|
|
13
|
+
{source:'controller-start.stderr.log',content:'2026-10-11T01:02:03.000Z ERROR timeout win32=109 TOKEN_CANARY_7841'}
|
|
14
|
+
]});
|
|
15
|
+
await writeFile(file,x.zip);
|
|
16
|
+
const escaped=file.replaceAll("'","''");
|
|
17
|
+
const ps=[
|
|
18
|
+
'$ErrorActionPreference="Stop"',
|
|
19
|
+
'Add-Type -AssemblyName System.IO.Compression.FileSystem',
|
|
20
|
+
"$zip=[System.IO.Compression.ZipFile]::OpenRead('"+escaped+"')",
|
|
21
|
+
"try{",
|
|
22
|
+
" $entries=@($zip.Entries|ForEach-Object {$_.FullName})",
|
|
23
|
+
" if($entries.Count -ne 2 -or $entries[0] -ne 'diagnostics.json' -or $entries[1] -ne 'redacted-events.json'){throw 'ZIP_ENTRY_MISMATCH'}",
|
|
24
|
+
" foreach($entry in $zip.Entries){",
|
|
25
|
+
" $reader=New-Object System.IO.StreamReader($entry.Open())",
|
|
26
|
+
" try{$body=$reader.ReadToEnd();$parsed=ConvertFrom-Json -InputObject $body;if(!$parsed){throw 'INVALID_JSON'}}finally{$reader.Dispose()}",
|
|
27
|
+
" if($body.Contains('TOKEN_CANARY_7841')){throw 'SECRET_LEAK'}",
|
|
28
|
+
" }",
|
|
29
|
+
" Write-Output 'ZIP_ORACLE_PASS'",
|
|
30
|
+
"}finally{$zip.Dispose()}"
|
|
31
|
+
].join(';');
|
|
32
|
+
const r=spawnSync('powershell.exe',['-NoLogo','-NoProfile','-NonInteractive','-Command',ps],
|
|
33
|
+
{windowsHide:true,shell:false,encoding:'utf8',timeout:20000,maxBuffer:1024*1024});
|
|
34
|
+
assert.equal(r.error,undefined,r.error?.message);
|
|
35
|
+
assert.equal(r.status,0,r.stderr);
|
|
36
|
+
assert.match(r.stdout,/ZIP_ORACLE_PASS/);
|
|
37
|
+
}finally{await rm(folder,{recursive:true,force:true});}
|
|
38
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,writeFile,readFile,rm,lstat} from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {redactDiagnosticLine,makeOfflineDiagnosticBundle,exportOfflineDiagnostics} from '../src/r67-diagnostic-export.mjs';
|
|
7
|
+
const secret='SENTINEL_SECRET_CANARY_984031';
|
|
8
|
+
const token='sk-proj-FAKE_DIAGNOSTIC_SECRET';
|
|
9
|
+
const sha='a'.repeat(64);
|
|
10
|
+
test('R67 local diagnostics use irreversible token-free allowlist, not regex-redacted raw log',()=>{
|
|
11
|
+
const line='2026-10-11T05:44:03.000Z ERROR reconnect win32=109 Authorization: Bearer '+secret+' '+token+' C:\\Users\\DEADBYTE\\passwords.txt';
|
|
12
|
+
const output=redactDiagnosticLine(line);
|
|
13
|
+
assert.deepEqual(output,{timestamp:'2026-10-11T05:44:03.000Z',level:'ERROR',event:'reconnect',win32_error:109});
|
|
14
|
+
assert.ok(!JSON.stringify(output).includes('FAKE'));
|
|
15
|
+
const unknown=redactDiagnosticLine('password='+secret+' '+token+' cwd=/private/location');
|
|
16
|
+
assert.equal(unknown,null,'unknown free-form lines must be dropped');
|
|
17
|
+
});
|
|
18
|
+
test('R67 offline ZIP contains only fixed names, derived summaries, no raw secrets or network URL',()=>{
|
|
19
|
+
const bundle=makeOfflineDiagnosticBundle({
|
|
20
|
+
releaseId:'v0.15.43-'+'a'.repeat(16),manifestSha256:sha,
|
|
21
|
+
logs:[{source:'controller-start.stderr.log',content:'ERROR reconnect win32=109 Bearer '+secret+'\nINFO ready '+token+'\npassword='+secret+'\n'}]
|
|
22
|
+
});
|
|
23
|
+
assert.ok(Buffer.isBuffer(bundle.zip));
|
|
24
|
+
assert.equal(bundle.zip.readUInt32LE(0),0x04034b50,'ZIP local file header');
|
|
25
|
+
assert.ok(bundle.zip.includes(Buffer.from('diagnostics.json')));
|
|
26
|
+
assert.ok(bundle.zip.includes(Buffer.from('redacted-events.json')));
|
|
27
|
+
for(const value of [secret,token,'password=','Bearer','C:\\Users\\','https://'])
|
|
28
|
+
assert.equal(bundle.zip.includes(Buffer.from(value)),false,'raw or sensitive text leaked');
|
|
29
|
+
assert.equal(bundle.summary.schema,'deadbyte.r67-offline-diagnostics.v1');
|
|
30
|
+
assert.equal(bundle.summary.network_upload,'NEVER');
|
|
31
|
+
assert.equal(bundle.summary.source_count,1);
|
|
32
|
+
assert.equal(bundle.summary.redacted_event_count,2);
|
|
33
|
+
assert.match(bundle.archive_sha256,/^[a-f0-9]{64}$/);
|
|
34
|
+
});
|
|
35
|
+
test('R67 exports only to owner-selected local path, absent target and no arbitrary logs',async()=>{
|
|
36
|
+
const folder=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r67-diagnostic-test-'));
|
|
37
|
+
try{
|
|
38
|
+
const stateRoot=path.join(folder,'state');
|
|
39
|
+
await mkdir(path.join(stateRoot,'runtime','logs'),{recursive:true});
|
|
40
|
+
await writeFile(path.join(stateRoot,'active-release.json'),JSON.stringify({release_id:'v0.15.43-'+'a'.repeat(16),manifest_sha256:sha}));
|
|
41
|
+
await writeFile(path.join(stateRoot,'runtime','logs','controller-start.stderr.log'),'ERROR timeout Authorization: Bearer '+secret+'\nINFO connected '+token+'\n');
|
|
42
|
+
await writeFile(path.join(stateRoot,'runtime','logs','arbitrary.log'),'ERROR THIS MUST NOT BE INCLUDED '+secret+'\n');
|
|
43
|
+
const output=path.join(folder,'owner-approved.zip');
|
|
44
|
+
const result=await exportOfflineDiagnostics({stateRoot,outputPath:output});
|
|
45
|
+
assert.equal(result.status,'written_local_only');
|
|
46
|
+
assert.equal(result.uploaded,false);
|
|
47
|
+
const actual=await readFile(output);
|
|
48
|
+
assert.equal(actual.readUInt32LE(0),0x04034b50);
|
|
49
|
+
assert.equal(actual.includes(Buffer.from(secret)),false);
|
|
50
|
+
assert.equal(actual.includes(Buffer.from(token)),false);
|
|
51
|
+
assert.equal(actual.includes(Buffer.from('arbitrary.log')),false);
|
|
52
|
+
const info=await lstat(output);
|
|
53
|
+
assert.ok(info.isFile()&&!info.isSymbolicLink());
|
|
54
|
+
await assert.rejects(()=>exportOfflineDiagnostics({stateRoot,outputPath:output}),/EEXIST|already exists/);
|
|
55
|
+
}finally{await rm(folder,{recursive:true,force:true});}
|
|
56
|
+
});
|
|
57
|
+
test('R67 without output path fails before read and upload option is always forbidden',async()=>{
|
|
58
|
+
await assert.rejects(()=>exportOfflineDiagnostics({stateRoot:'C:\\a'}),/outputPath/);
|
|
59
|
+
await assert.rejects(()=>exportOfflineDiagnostics({stateRoot:'C:\\a',outputPath:'X',upload:true}),/upload.*disabled|no network/i);
|
|
60
|
+
});
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,readFile,writeFile,rm} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {createR67ShardPlan,r67RegressionTreeHash,r67ShardTimeoutMs} from '../src/r67-regression-shards.mjs';
|
|
8
|
+
import {reconcileR67Evidence} from '../src/r67-evidence-reconcile.mjs';
|
|
9
|
+
|
|
10
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
11
|
+
const op='r67-e2e-fixture-contract';
|
|
12
|
+
async function fixture(){
|
|
13
|
+
const parent=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r67-e2e-'));
|
|
14
|
+
const root=path.join(parent,'candidate'),evidenceRoot=path.join(parent,'evidence');
|
|
15
|
+
await mkdir(path.join(root,'test'),{recursive:true});
|
|
16
|
+
await mkdir(path.join(root,'src'));
|
|
17
|
+
await mkdir(evidenceRoot);
|
|
18
|
+
await writeFile(path.join(root,'package.json'),'{}\n');
|
|
19
|
+
await writeFile(path.join(root,'src','core.mjs'),'export const r67 = true;\n');
|
|
20
|
+
const names=Array.from({length:32},(_,i)=>'test/fixture-'+String(i).padStart(2,'0')+'.test.mjs');
|
|
21
|
+
for(const name of names)await writeFile(path.join(root,name),'import test from "node:test"; test("fixture",()=>{});\n');
|
|
22
|
+
const plan=createR67ShardPlan(names,32);
|
|
23
|
+
const hashes=await Promise.all(names.map(async name=>[name,sha(await readFile(path.join(root,name)))]));
|
|
24
|
+
const testTree=sha(Buffer.from(JSON.stringify(hashes)));
|
|
25
|
+
const opts={roots:['src','test'],topLevel:['package.json'],extra:[]};
|
|
26
|
+
const tree=await r67RegressionTreeHash(root,opts);
|
|
27
|
+
async function writeShard(index,{kind='valid',signed=false}={}){
|
|
28
|
+
const shard=plan.shards[index],folder=path.join(evidenceRoot,op+'-shard-'+String(index).padStart(2,'0'));
|
|
29
|
+
await mkdir(folder,{recursive:true});
|
|
30
|
+
const intent={schema:'deadbyte.r67-shard-intent.v1',operation:op,shard_index:index,plan_sha256:plan.plan_sha256,
|
|
31
|
+
files_sha256:shard.files_sha256,test_tree_sha256:testTree,source_tree_sha256:tree.sha256,
|
|
32
|
+
source_file_count:tree.file_count,started_at_utc:'2026-10-11T00:00:00.000Z',files:shard.files};
|
|
33
|
+
await writeFile(path.join(folder,'intent.json'),JSON.stringify(intent));
|
|
34
|
+
const status=kind==='unknown'?'OUTCOME_UNKNOWN':'PASS';
|
|
35
|
+
const result={schema:kind==='v1'?'deadbyte.r67-shard-result.v1':'deadbyte.r67-shard-result.v2',
|
|
36
|
+
operation_id:op,shard_index:index,plan_sha256:plan.plan_sha256,
|
|
37
|
+
files_sha256:shard.files_sha256,test_tree_sha256:testTree,source_tree_sha256:tree.sha256,
|
|
38
|
+
source_file_count:tree.file_count,status,exit_code:status==='PASS'?0:null,
|
|
39
|
+
test_count:status==='PASS'?1:null,passed:status==='PASS'?1:null,failed:status==='PASS'?0:null,
|
|
40
|
+
skipped:status==='PASS'?0:null,cancelled:status==='PASS'?0:null,todo:status==='PASS'?0:null,
|
|
41
|
+
configured_timeout_ms:r67ShardTimeoutMs(shard),timed_out:false,failure_reason:null,elapsed_ms:1,
|
|
42
|
+
stdout_sha256:sha(Buffer.from('fixture output')),stderr_sha256:sha(Buffer.alloc(0))};
|
|
43
|
+
if(kind==='tampered')result.files_sha256='f'.repeat(64);
|
|
44
|
+
const receipt_sha256=sha(Buffer.from(JSON.stringify(result)));
|
|
45
|
+
await writeFile(path.join(folder,'result.json'),JSON.stringify({...result,receipt_sha256}));
|
|
46
|
+
return folder;
|
|
47
|
+
}
|
|
48
|
+
return {parent,root,evidenceRoot,plan,opts,writeShard,cleanup:()=>rm(parent,{recursive:true,force:true})};
|
|
49
|
+
}
|
|
50
|
+
test('R67 real reconciliation consumes 32 durable v2 receipts and reports PASS from fixture source bytes',async()=>{
|
|
51
|
+
const x=await fixture();
|
|
52
|
+
try{
|
|
53
|
+
for(let i=0;i<32;i++)await x.writeShard(i);
|
|
54
|
+
const r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
55
|
+
assert.equal(r.status,'PASS',JSON.stringify(r));
|
|
56
|
+
assert.equal(r.verified_receipts,32);
|
|
57
|
+
assert.equal(r.verdict.test_count,32);
|
|
58
|
+
assert.deepEqual(r.integrity_faults,[]);
|
|
59
|
+
}finally{await x.cleanup();}
|
|
60
|
+
});
|
|
61
|
+
test('R67 verified legacy v1 receipts cannot count toward a new full suite PASS',async()=>{
|
|
62
|
+
const x=await fixture();
|
|
63
|
+
try{
|
|
64
|
+
for(let i=0;i<32;i++)await x.writeShard(i,{kind:i===5?'v1':'valid'});
|
|
65
|
+
const r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
66
|
+
assert.equal(r.status,'STALE_SOURCE');
|
|
67
|
+
assert.deepEqual(r.stale_indices,[5]);
|
|
68
|
+
assert.equal(r.verified_receipts,31);
|
|
69
|
+
}finally{await x.cleanup();}
|
|
70
|
+
});
|
|
71
|
+
test('R67 rejects changed receipts before considering source staleness',async()=>{
|
|
72
|
+
const x=await fixture();
|
|
73
|
+
try{
|
|
74
|
+
for(let i=0;i<32;i++)await x.writeShard(i,{kind:i===7?'tampered':'valid'});
|
|
75
|
+
const file=path.join(x.evidenceRoot,op+'-shard-07','result.json');
|
|
76
|
+
const broken=JSON.parse(await readFile(file,'utf8'));broken.result_sha256='0'.repeat(64);
|
|
77
|
+
await writeFile(file,JSON.stringify(broken));
|
|
78
|
+
await writeFile(path.join(x.root,'src','core.mjs'),'export const r67 = false;\n');
|
|
79
|
+
const r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
80
|
+
assert.equal(r.status,'FAILED_INTEGRITY');
|
|
81
|
+
assert.ok(r.integrity_faults.some(e=>e.index===7));
|
|
82
|
+
}finally{await x.cleanup();}
|
|
83
|
+
});
|
|
84
|
+
test('R67 source mutation, missing shard and unknown outcome never produce PASS',async()=>{
|
|
85
|
+
const x=await fixture();
|
|
86
|
+
try{
|
|
87
|
+
for(let i=0;i<31;i++)await x.writeShard(i);
|
|
88
|
+
let r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
89
|
+
assert.equal(r.status,'BLOCKED');
|
|
90
|
+
await x.writeShard(31,{kind:'unknown'});
|
|
91
|
+
r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
92
|
+
assert.equal(r.status,'OUTCOME_UNKNOWN');
|
|
93
|
+
await writeFile(path.join(x.root,'src','core.mjs'),'export const r67 = false;\n');
|
|
94
|
+
r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
95
|
+
assert.equal(r.status,'STALE_SOURCE');
|
|
96
|
+
}finally{await x.cleanup();}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('R67 changed test plan makes authentic historical v1 receipts STALE, not integrity failures',async()=>{
|
|
100
|
+
const x=await fixture();
|
|
101
|
+
try{
|
|
102
|
+
for(let i=0;i<32;i++)await x.writeShard(i,{kind:'v1'});
|
|
103
|
+
await writeFile(path.join(x.root,'test','fixture-added.test.mjs'),'import test from "node:test"; test("new",()=>{});\\n');
|
|
104
|
+
const r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
105
|
+
assert.equal(r.status,'STALE_SOURCE',JSON.stringify(r.integrity_faults));
|
|
106
|
+
assert.deepEqual(r.integrity_faults,[]);
|
|
107
|
+
assert.equal(r.stale_indices.length,32);
|
|
108
|
+
}finally{await x.cleanup();}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('R67 v2 PASS cannot coexist with timed_out=true even if receipt SHA is recomputed',async()=>{
|
|
112
|
+
const x=await fixture();
|
|
113
|
+
try{
|
|
114
|
+
for(let i=0;i<32;i++)await x.writeShard(i);
|
|
115
|
+
const file=path.join(x.evidenceRoot,op+'-shard-08','result.json');
|
|
116
|
+
const result=JSON.parse(await readFile(file,'utf8'));result.timed_out=true;
|
|
117
|
+
const {receipt_sha256,...body}=result;result.receipt_sha256=sha(Buffer.from(JSON.stringify(body)));
|
|
118
|
+
await writeFile(file,JSON.stringify(result));
|
|
119
|
+
const r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
120
|
+
assert.equal(r.status,'FAILED_INTEGRITY');
|
|
121
|
+
assert.ok(r.integrity_faults.some(v=>v.index===8));
|
|
122
|
+
}finally{await x.cleanup();}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('R67 v2 skips cannot silently produce a full regression PASS without classification',async()=>{
|
|
126
|
+
const x=await fixture();
|
|
127
|
+
try{
|
|
128
|
+
for(let i=0;i<32;i++)await x.writeShard(i);
|
|
129
|
+
const file=path.join(x.evidenceRoot,op+'-shard-09','result.json');
|
|
130
|
+
const result=JSON.parse(await readFile(file,'utf8'));result.passed=0;result.skipped=1;
|
|
131
|
+
const {receipt_sha256,...body}=result;result.receipt_sha256=sha(Buffer.from(JSON.stringify(body)));
|
|
132
|
+
await writeFile(file,JSON.stringify(result));
|
|
133
|
+
const r=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
134
|
+
assert.equal(r.status,'BLOCKED_NONPASS_CLASSIFICATION');
|
|
135
|
+
assert.deepEqual(r.unclassified_nonpass,[{index:9,skipped:1,todo:0,cancelled:0}]);
|
|
136
|
+
}finally{await x.cleanup();}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('R67 case-level SKIP evidence stays visible and forged case counts fail integrity',async()=>{
|
|
140
|
+
const x=await fixture();
|
|
141
|
+
try{
|
|
142
|
+
for(let i=0;i<32;i++)await x.writeShard(i);
|
|
143
|
+
const file=path.join(x.evidenceRoot,op+'-shard-09','result.json');
|
|
144
|
+
const report=JSON.parse(await readFile(file,'utf8'));
|
|
145
|
+
report.passed=0;report.skipped=1;
|
|
146
|
+
report.nonpass_cases=[{kind:'SKIP',name:'offline fixture',reason:'unsupported platform'}];
|
|
147
|
+
report.nonpass_fully_classified=true;
|
|
148
|
+
const seal=async()=>{
|
|
149
|
+
const {receipt_sha256,...body}=report;report.receipt_sha256=sha(Buffer.from(JSON.stringify(body)));
|
|
150
|
+
await writeFile(file,JSON.stringify(report));
|
|
151
|
+
};
|
|
152
|
+
await seal();
|
|
153
|
+
const observed=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
154
|
+
assert.equal(observed.status,'PASS','Fully reasoned skips must be honestly recorded but should not fabricate test failures');
|
|
155
|
+
assert.deepEqual(observed.classified_nonpass[0].cases,report.nonpass_cases);
|
|
156
|
+
assert.equal(observed.verdict.passed,31);
|
|
157
|
+
assert.equal(observed.verdict.test_count,32);
|
|
158
|
+
report.nonpass_cases.push({kind:'SKIP',name:'forged',reason:'unsupported platform'});
|
|
159
|
+
await seal();
|
|
160
|
+
const broken=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
161
|
+
assert.equal(broken.status,'FAILED_INTEGRITY');
|
|
162
|
+
}finally{await x.cleanup();}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
test('R67 v2 FAILED result must bind a complete named failure case ledger, not an empty passing-shaped ledger',async()=>{
|
|
167
|
+
const x=await fixture();
|
|
168
|
+
try{
|
|
169
|
+
for(let i=0;i<32;i++)await x.writeShard(i);
|
|
170
|
+
const file=path.join(x.evidenceRoot,op+'-shard-07','result.json');
|
|
171
|
+
const result=JSON.parse(await readFile(file,'utf8'));
|
|
172
|
+
const name='remote ownership race';
|
|
173
|
+
result.status='FAILED';result.exit_code=1;result.failed=1;result.passed=0;
|
|
174
|
+
result.failure_cases=[{name,name_sha256:sha(Buffer.from(name,'utf8'))}];
|
|
175
|
+
result.failure_cases_complete=true;
|
|
176
|
+
const seal=async()=>{const {receipt_sha256,...body}=result;result.receipt_sha256=sha(Buffer.from(JSON.stringify(body)));await writeFile(file,JSON.stringify(result));};
|
|
177
|
+
await seal();
|
|
178
|
+
let observed=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
179
|
+
assert.equal(observed.status,'FAILED');
|
|
180
|
+
assert.deepEqual(observed.integrity_faults,[]);
|
|
181
|
+
result.failure_cases=[];result.failure_cases_complete=true;await seal();
|
|
182
|
+
observed=await reconcileR67Evidence({root:x.root,evidenceRoot:x.evidenceRoot,operationId:op,sourceHashOptions:x.opts});
|
|
183
|
+
assert.equal(observed.status,'FAILED_INTEGRITY');
|
|
184
|
+
assert.ok(observed.integrity_faults.some(v=>v.index===7));
|
|
185
|
+
}finally{await x.cleanup();}
|
|
186
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,readFile,rm,writeFile,lstat} from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
import {atomicWriteActivePointer,materializeReleaseSlot,parseActivePointer,parseManifest,renderStableLauncherPs1,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
9
|
+
|
|
10
|
+
const candidate=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
11
|
+
const parent=path.resolve(candidate,'..','deadbyte-mcp-0.15.43-r66');
|
|
12
|
+
const parentManifestSha='4c4a64a4c8e9f5a6e9eecce85fd0007ebcac99121b29c07f1a1fc81d1643cd33';
|
|
13
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
14
|
+
|
|
15
|
+
async function generation(state,label){
|
|
16
|
+
const root=path.join(state,'generations',label);
|
|
17
|
+
await mkdir(root,{recursive:true});
|
|
18
|
+
const config=Buffer.from(`@{ Label = '${label}' }\r\n`,'utf8');
|
|
19
|
+
await writeFile(path.join(root,'controller-config.psd1'),config,{flag:'wx'});
|
|
20
|
+
const manifest=Buffer.from(`${sha(config)} controller-config.psd1\n`,'utf8');
|
|
21
|
+
await writeFile(path.join(root,'GENERATION.SHA256'),manifest,{flag:'wx'});
|
|
22
|
+
return {root,manifest_sha256:sha(manifest),config_path:path.join(root,'controller-config.psd1')};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('R67 isolated immutable slot activates, rolls back exact R66, and restores the same R67 bytes with no authority',async()=>{
|
|
26
|
+
const base=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r67-slot-parity-'));
|
|
27
|
+
const state=path.join(base,'state');
|
|
28
|
+
try{
|
|
29
|
+
const parentManifest=await readFile(path.join(parent,'MANIFEST.SHA256'));
|
|
30
|
+
const candidateManifest=await readFile(path.join(candidate,'MANIFEST.SHA256'));
|
|
31
|
+
assert.equal(sha(parentManifest),parentManifestSha);
|
|
32
|
+
const candidateManifestSha=sha(candidateManifest);
|
|
33
|
+
const parentSlot=await materializeReleaseSlot({source:parent,stateRoot:state,version:'0.15.43',manifestSha256:parentManifestSha});
|
|
34
|
+
const candidateSlot=await materializeReleaseSlot({source:candidate,stateRoot:state,version:'0.15.44',manifestSha256:candidateManifestSha});
|
|
35
|
+
const parentGeneration=await generation(state,'r66-parent');
|
|
36
|
+
const candidateGeneration=await generation(state,'r67-candidate');
|
|
37
|
+
const launcherSha=sha(Buffer.from(renderStableLauncherPs1(),'utf8'));
|
|
38
|
+
const pointerPath=path.join(state,'active-release.json');
|
|
39
|
+
const pointer=(slot,version,g)=>({schema:'deadbyte.active-release.v1',release_id:slot.release_id,version,
|
|
40
|
+
manifest_sha256:slot.manifest_sha256,release_root:slot.release_root,generation_root:g.root,
|
|
41
|
+
generation_manifest_sha256:g.manifest_sha256,launcher_sha256:launcherSha,config_path:g.config_path});
|
|
42
|
+
|
|
43
|
+
const activated=await atomicWriteActivePointer(pointerPath,pointer(candidateSlot,'0.15.44',candidateGeneration));
|
|
44
|
+
assert.equal(activated.release_id,candidateSlot.release_id);
|
|
45
|
+
await verifyManifestTreeExact(candidateSlot.release_root,parseManifest(candidateManifest.toString('utf8')));
|
|
46
|
+
|
|
47
|
+
const rolledBack=await atomicWriteActivePointer(pointerPath,pointer(parentSlot,'0.15.43',parentGeneration));
|
|
48
|
+
assert.equal(rolledBack.release_id,parentSlot.release_id);
|
|
49
|
+
assert.equal(rolledBack.manifest_sha256,parentManifestSha);
|
|
50
|
+
await verifyManifestTreeExact(parentSlot.release_root,parseManifest(parentManifest.toString('utf8')));
|
|
51
|
+
|
|
52
|
+
const restored=await atomicWriteActivePointer(pointerPath,pointer(candidateSlot,'0.15.44',candidateGeneration));
|
|
53
|
+
assert.deepEqual(restored,activated);
|
|
54
|
+
assert.deepEqual(parseActivePointer(await readFile(pointerPath,'utf8')),activated);
|
|
55
|
+
await verifyManifestTreeExact(candidateSlot.release_root,parseManifest(candidateManifest.toString('utf8')));
|
|
56
|
+
for(const name of ['process-grant.json','coding-grant.json','autonomous-grant.json','desktop-grant.json','host-grant.json']){
|
|
57
|
+
await assert.rejects(()=>lstat(path.join(state,'runtime',name)),{code:'ENOENT'});
|
|
58
|
+
}
|
|
59
|
+
}finally{
|
|
60
|
+
await rm(base,{recursive:true,force:true});
|
|
61
|
+
}
|
|
62
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {formatRemoteToolCall,createLiveTimelineTracker} from '../src/remote-live-log.mjs';
|
|
4
|
+
const token='sk-proj-SENTINEL_CANARY_NEVER_PRINT_8675309';
|
|
5
|
+
test('R67 live log does not echo credential-shaped token in path argument, including verbose call',()=>{
|
|
6
|
+
const evt={event_type:'tool.request',request_id:'canary-00000001',tool:'coding_file_read',args:{root_id:'projects',path:'sensitive/'+token+'/file.txt'}};
|
|
7
|
+
const verbose=formatRemoteToolCall(evt);
|
|
8
|
+
assert.equal(typeof verbose,'string');
|
|
9
|
+
assert.doesNotMatch(verbose,/SENTINEL_CANARY_NEVER_PRINT/);
|
|
10
|
+
const track=createLiveTimelineTracker({clock:()=>1000,verbose:true});
|
|
11
|
+
const start=track.observe(evt,{sequence:10});
|
|
12
|
+
const end=track.observe({event_type:'tool.result',request_id:evt.request_id,tool:evt.tool,outcome:{is_error:false,duration_ms:1}},{sequence:11});
|
|
13
|
+
assert.doesNotMatch(String(start)+String(end),/SENTINEL_CANARY_NEVER_PRINT/);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('R67 compact live activity refuses credential in path and pattern even when caller passes unsanitized event',async()=>{
|
|
17
|
+
const {formatCompactAuditEvent,createLiveActivityTracker}=await import('../src/remote-live-log.mjs');
|
|
18
|
+
const sentinel='ghp_PRIVATE_CANARY_ABCDEF0123456789Q';
|
|
19
|
+
const event={event_type:'tool.request',request_id:'span-safe-121',tool:'coding_search',
|
|
20
|
+
args:{root_id:'projects',path:'source/'+sentinel+'/target',pattern:'token='+sentinel}};
|
|
21
|
+
const line=formatCompactAuditEvent(event);
|
|
22
|
+
assert.equal(typeof line,'string');
|
|
23
|
+
assert.doesNotMatch(line,/PRIVATE_CANARY/);
|
|
24
|
+
const activity=createLiveActivityTracker();
|
|
25
|
+
assert.doesNotMatch(activity.observe(event),/PRIVATE_CANARY/);
|
|
26
|
+
});
|
|
27
|
+
test('R67 trace-enabled live log does not disclose credential-shaped user session id or script_path',async()=>{
|
|
28
|
+
const event={event_type:'tool.request',request_id:'good-span-5678',tool:'coding_command_run',
|
|
29
|
+
context:{trace_id:'sk-proj-SENTINEL_TRACE_CANARY_3456789'},
|
|
30
|
+
args:{root_id:'projects',command_id:'node_script',script_path:'tmp/github_pat_PRIVATE_SCRIPT_CANARY_1234567890.ps1'}};
|
|
31
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000,trace:true});
|
|
32
|
+
const line=tracker.observe(event,{sequence:77});
|
|
33
|
+
assert.equal(typeof line,'string');
|
|
34
|
+
assert.doesNotMatch(line,/SENTINEL_TRACE_CANARY|PRIVATE_SCRIPT_CANARY/);
|
|
35
|
+
});
|
|
36
|
+
test('R67 result error reason does not reveal github token in user supplied text',()=>{
|
|
37
|
+
const token='github_pat_SECRET_RESULT_CANARY_ABCDEFGHIJKLMNOP';
|
|
38
|
+
const tracker=createLiveTimelineTracker({clock:()=>1000,trace:true});
|
|
39
|
+
tracker.observe({event_type:'tool.request',request_id:'foo-1',tool:'coding_command_run',args:{root_id:'projects',command_id:'node_script'}},{sequence:4});
|
|
40
|
+
const output=tracker.observe({event_type:'tool.result',request_id:'foo-1',tool:'coding_command_run',
|
|
41
|
+
outcome:{is_error:true,reason:'failure after credential '+token,duration_ms:33}},{sequence:5});
|
|
42
|
+
assert.equal(typeof output,'string');
|
|
43
|
+
assert.doesNotMatch(output,/SECRET_RESULT_CANARY/);
|
|
44
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
|
|
7
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
test('R67 isolated machine-fs smoke binds its Coding grant to a durable authority epoch',()=>{
|
|
10
|
+
const env={...process.env};
|
|
11
|
+
delete env.DEADBYTE_STATE_ROOT;
|
|
12
|
+
const result=spawnSync(process.execPath,['src/machine-fs-smoke-client.mjs'],{
|
|
13
|
+
cwd:root,env,encoding:'utf8',windowsHide:true,shell:false,timeout:30000,maxBuffer:4*1024*1024
|
|
14
|
+
});
|
|
15
|
+
assert.equal(result.error,undefined,String(result.error??''));
|
|
16
|
+
assert.equal(result.status,0,`${result.stdout}\n${result.stderr}`);
|
|
17
|
+
const receipt=JSON.parse(String(result.stdout));
|
|
18
|
+
assert.equal(receipt.status,'passed');
|
|
19
|
+
assert.equal(receipt.mutation_receipts,5);
|
|
20
|
+
assert.equal(receipt.signature_valid,true);
|
|
21
|
+
assert.doesNotMatch(`${result.stdout}\n${result.stderr}`,/legacy-no-epoch/);
|
|
22
|
+
});
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {readFile,writeFile,mkdtemp,rm} from 'node:fs/promises';
|
|
5
|
+
import {tmpdir} from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {fileURLToPath} from 'node:url';
|
|
8
|
+
import {spawnSync} from 'node:child_process';
|
|
9
|
+
const root=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
10
|
+
const exe=path.join(root,'bin','deadbyte-native-inspect-r67.exe');
|
|
11
|
+
const hash=b=>createHash('sha256').update(b).digest('hex');
|
|
12
|
+
function run(...args){
|
|
13
|
+
const r=spawnSync(exe,args,{windowsHide:true,shell:false,encoding:'utf8',timeout:30000,maxBuffer:4*1024*1024});
|
|
14
|
+
assert.equal(r.error,undefined,r.error?.message);
|
|
15
|
+
const out=JSON.parse((r.stdout??'').trim());
|
|
16
|
+
assert.equal(out.schema,'deadbyte.native-inspect.r67.v1');
|
|
17
|
+
if(out.status==='ok')assert.equal(out.data_sha256,hash(Buffer.from(JSON.stringify(out.data),'utf8')),'independent serialized data digest');
|
|
18
|
+
return {...out,exit_code:r.status};
|
|
19
|
+
}
|
|
20
|
+
test('R67 native process snapshot is bounded, identity-bearing, and honestly partial',()=>{
|
|
21
|
+
const r=run('process','48');
|
|
22
|
+
assert.equal(r.status,'ok');
|
|
23
|
+
assert.equal(r.exit_code,0);
|
|
24
|
+
assert.equal(r.operation,'process');
|
|
25
|
+
assert.equal(r.data.api,'CreateToolhelp32Snapshot+OpenProcess+GetProcessTimes+QueryFullProcessImageNameW');
|
|
26
|
+
assert.ok(r.data.processes.length>=1&&r.data.processes.length<=48);
|
|
27
|
+
for(const p of r.data.processes){
|
|
28
|
+
assert.ok(Number.isSafeInteger(p.pid)&&p.pid>=0);
|
|
29
|
+
assert.ok(Number.isSafeInteger(p.parent_pid)&&p.parent_pid>=0);
|
|
30
|
+
assert.ok(['observed','unavailable','stale_or_reused'].includes(p.state));
|
|
31
|
+
if(p.state==='unavailable')assert.ok(Number.isInteger(p.win32_error)&&p.win32_error>0);
|
|
32
|
+
else if(p.state==='stale_or_reused'){assert.equal(p.snapshot_binding,'NAME_MISMATCH');assert.equal(p.win32_error,0);}
|
|
33
|
+
else {assert.equal(p.snapshot_binding,'UNVERIFIED');assert.match(p.creation_time_utc,/^\d{4}-\d\d-\d\dT/);assert.match(p.creation_filetime,/^\d+$/);assert.equal(p.win32_error,0);}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
test('R67 VirtualQueryEx memory map records process identity, monotone bounded regions, and zero private memory data',()=>{
|
|
37
|
+
const r=run('map','self','64');
|
|
38
|
+
assert.equal(r.status,'ok');
|
|
39
|
+
assert.equal(r.exit_code,0);
|
|
40
|
+
assert.equal(r.operation,'map');
|
|
41
|
+
assert.equal(r.data.api,'VirtualQueryEx');
|
|
42
|
+
assert.match(r.data.creation_filetime,/^\d+$/);
|
|
43
|
+
assert.ok(r.data.regions.length>=1&&r.data.regions.length<=64);
|
|
44
|
+
let prev=-1n;
|
|
45
|
+
for(const row of r.data.regions){
|
|
46
|
+
const base=BigInt(row.base),size=BigInt(row.size);
|
|
47
|
+
assert.ok(base>prev&&size>0n);
|
|
48
|
+
prev=base;
|
|
49
|
+
assert.ok(Number.isInteger(row.state)&&Number.isInteger(row.protect)&&Number.isInteger(row.type));
|
|
50
|
+
assert.deepEqual(Object.keys(row),['base','allocation_base','size','state','protect','type']);
|
|
51
|
+
}
|
|
52
|
+
assert.equal(r.data.process_exited,false);
|
|
53
|
+
assert.equal(r.data.outcome,'HANDLE_IDENTITY_CONFIRMED');
|
|
54
|
+
});
|
|
55
|
+
test('R67 PE bytes and section table agree with an independent raw-file oracle',async()=>{
|
|
56
|
+
const b=await readFile(exe);
|
|
57
|
+
assert.equal(b.readUInt16LE(0),0x5a4d);
|
|
58
|
+
const p=b.readInt32LE(0x3c);
|
|
59
|
+
assert.ok(p>=64&&p+24<b.length);
|
|
60
|
+
assert.equal(b.readUInt32LE(p),0x4550);
|
|
61
|
+
const machine=b.readUInt16LE(p+4),sectionCount=b.readUInt16LE(p+6);
|
|
62
|
+
const optLength=b.readUInt16LE(p+20),magic=b.readUInt16LE(p+24);
|
|
63
|
+
const sectionOff=p+24+optLength;
|
|
64
|
+
const r=run('pe',exe);
|
|
65
|
+
assert.equal(r.status,'ok');assert.equal(r.exit_code,0);
|
|
66
|
+
assert.equal(r.data.file_sha256,hash(b));
|
|
67
|
+
assert.equal(r.data.file_size,b.length);
|
|
68
|
+
assert.equal(r.data.machine,machine);
|
|
69
|
+
assert.equal(r.data.number_of_sections,sectionCount);
|
|
70
|
+
assert.equal(r.data.format,magic===0x20b?'PE32+':'PE32');
|
|
71
|
+
assert.equal(r.data.sections.length,sectionCount);
|
|
72
|
+
for(let i=0;i<sectionCount;i++){
|
|
73
|
+
const off=sectionOff+i*40,row=r.data.sections[i];
|
|
74
|
+
assert.equal(row.rva,'0x'+b.readUInt32LE(off+12).toString(16));
|
|
75
|
+
assert.equal(row.raw_size,b.readUInt32LE(off+16));
|
|
76
|
+
assert.equal(row.raw_offset,b.readUInt32LE(off+20));
|
|
77
|
+
assert.equal(row.characteristics,b.readUInt32LE(off+36));
|
|
78
|
+
assert.ok(!row.raw_size||row.raw_offset+row.raw_size<=b.length);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
test('R67 returns failed/error for inaccessible PID and malformed PE, never fabricated empty observations',async()=>{
|
|
82
|
+
const bad=run('map','4294967295','1','4');
|
|
83
|
+
assert.equal(bad.status,'failed');assert.equal(bad.exit_code,2);
|
|
84
|
+
assert.match(bad.error,/OpenProcess.*win32=|invalid PID/);
|
|
85
|
+
assert.ok(['ACCESS_DENIED','INVALID_PARAMETER','WIN32_FAILURE'].includes(bad.error_code));
|
|
86
|
+
assert.ok(Number.isInteger(bad.win32_error)&&bad.win32_error>0);
|
|
87
|
+
const folder=await mkdtemp(path.join(tmpdir(),'deadbyte-r67-pe-'));
|
|
88
|
+
try{
|
|
89
|
+
const file=path.join(folder,'invalid.exe');await writeFile(file,Buffer.alloc(128,0));
|
|
90
|
+
const invalid=run('pe',file);
|
|
91
|
+
assert.equal(invalid.status,'failed');assert.equal(invalid.exit_code,2);assert.match(invalid.error,/MZ|PE/);
|
|
92
|
+
assert.equal(invalid.error_code,'INVALID_INPUT');assert.equal(invalid.win32_error,0);
|
|
93
|
+
}finally{await rm(folder,{recursive:true,force:true});}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('R67 external PID memory map fails closed on missing or mismatched creation FILETIME',()=>{
|
|
97
|
+
const missing=run('map',String(process.pid));
|
|
98
|
+
assert.equal(missing.status,'failed');assert.equal(missing.exit_code,2);
|
|
99
|
+
assert.match(missing.error,/creation_filetime/);
|
|
100
|
+
const swapped=run('map',String(process.pid),'1','4');
|
|
101
|
+
assert.equal(swapped.status,'failed');assert.equal(swapped.exit_code,2);
|
|
102
|
+
assert.equal(swapped.error_code,'PROCESS_IDENTITY_MISMATCH');
|
|
103
|
+
assert.equal(swapped.win32_error,0);
|
|
104
|
+
const observed=run('identity',String(process.pid));
|
|
105
|
+
assert.equal(observed.status,'ok',observed.error);
|
|
106
|
+
assert.equal(observed.data.pid,process.pid);
|
|
107
|
+
const valid=run('map',String(process.pid),observed.data.creation_filetime,'4');
|
|
108
|
+
assert.equal(valid.status,'ok',valid.error);
|
|
109
|
+
assert.equal(valid.data.creation_filetime,observed.data.creation_filetime);
|
|
110
|
+
assert.equal(valid.data.pid,process.pid);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('R67 exited process cannot silently turn into a successful PID-reused memory map',async()=>{
|
|
114
|
+
const {spawn}=await import('node:child_process');
|
|
115
|
+
const {once}=await import('node:events');
|
|
116
|
+
const child=spawn(process.execPath,['-e','setInterval(()=>{},500)'],{shell:false,windowsHide:true,stdio:'ignore'});
|
|
117
|
+
try{
|
|
118
|
+
await once(child,'spawn');
|
|
119
|
+
const identity=run('identity',String(child.pid));
|
|
120
|
+
assert.equal(identity.status,'ok',identity.error);
|
|
121
|
+
assert.equal(identity.data.pid,child.pid);
|
|
122
|
+
const stopped=once(child,'exit');
|
|
123
|
+
assert.equal(child.kill(),true);
|
|
124
|
+
await stopped;
|
|
125
|
+
const after=run('map',String(child.pid),identity.data.creation_filetime,'4');
|
|
126
|
+
assert.equal(after.status,'failed');
|
|
127
|
+
assert.equal(after.exit_code,2);
|
|
128
|
+
assert.equal(Object.hasOwn(after,'data'),false);
|
|
129
|
+
assert.ok(['WIN32_FAILURE','INVALID_PARAMETER','PROCESS_EXITED','PROCESS_IDENTITY_MISMATCH'].includes(after.error_code),after.error_code);
|
|
130
|
+
}finally{if(child.exitCode===null)child.kill();}
|
|
131
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createR67NativeObservationAuthority} from '../src/r67-native-observation-authority.mjs';
|
|
4
|
+
|
|
5
|
+
const hex=c=>c.repeat(64);
|
|
6
|
+
const policy={policy_sha256:hex('a'),roots:{projects:{read:true}}};
|
|
7
|
+
let alive=true,generation=17;
|
|
8
|
+
const getSession=async()=>alive?{
|
|
9
|
+
armed:true,standing:true,reason:'standing-grant',instance_nonce:hex('b'),root_ids:['projects'],
|
|
10
|
+
authority_epoch:{trusted:true,seq:generation,event_sha256:hex('c')}
|
|
11
|
+
}:{armed:false,reason:'disarmed'};
|
|
12
|
+
|
|
13
|
+
test('R67 observer fails closed without explicit owner opt-in, and denies other PID reads',async()=>{
|
|
14
|
+
const disabled=createR67NativeObservationAuthority({getCodingPolicy:()=>policy,sessionStatus:getSession});
|
|
15
|
+
await assert.rejects(()=>disabled({operation:'process',args:['process','4']}),/DISABLED/);
|
|
16
|
+
const enabled=createR67NativeObservationAuthority({getCodingPolicy:()=>policy,sessionStatus:getSession,enabled:true});
|
|
17
|
+
await assert.rejects(()=>enabled({operation:'map',args:['map','4148','3']}),/OTHER_PID/);
|
|
18
|
+
await assert.rejects(()=>enabled({operation:'identity',args:['identity','4148']}),/OTHER_PID/);
|
|
19
|
+
await assert.rejects(()=>enabled({operation:'exec',args:['exec','whoami']}),/SCOPE_DENIED/);
|
|
20
|
+
});
|
|
21
|
+
test('R67 observer binds machine-supplied authority to real standing epoch, not client grant',async()=>{
|
|
22
|
+
alive=true;
|
|
23
|
+
const enabled=createR67NativeObservationAuthority({getCodingPolicy:()=>policy,sessionStatus:getSession,enabled:true});
|
|
24
|
+
const first=await enabled({operation:'map',args:['map','self','4']});
|
|
25
|
+
assert.equal(first.scope,'native-observe-readonly');
|
|
26
|
+
assert.equal(first.policy_sha256,policy.policy_sha256);
|
|
27
|
+
assert.equal(first.authority_generation,generation);
|
|
28
|
+
assert.match(first.grant_id,/^coding\.r67\.[0-9a-f]{32}$/);
|
|
29
|
+
const next=await enabled({operation:'map',args:['map','self','4']});
|
|
30
|
+
assert.deepEqual(first,next);
|
|
31
|
+
generation+=1;
|
|
32
|
+
const rotated=await enabled({operation:'map',args:['map','self','4']});
|
|
33
|
+
assert.notEqual(first.authority_generation,rotated.authority_generation);
|
|
34
|
+
alive=false;
|
|
35
|
+
await assert.rejects(()=>enabled({operation:'process',args:['process','4']}),/AUTHORITY_REVOKED/);
|
|
36
|
+
});
|
|
37
|
+
test('R67 observer rejects unverified lease epoch or changed policy',async()=>{
|
|
38
|
+
const disabled=createR67NativeObservationAuthority({
|
|
39
|
+
getCodingPolicy:()=>policy,enabled:true,sessionStatus:async()=>({
|
|
40
|
+
armed:true,standing:true,reason:'standing-grant',instance_nonce:hex('b'),root_ids:['projects'],
|
|
41
|
+
authority_epoch:{trusted:false,seq:100,event_sha256:hex('c')}
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
|
+
await assert.rejects(()=>disabled({operation:'process',args:['process','4']}),/AUTHORITY_REVOKED/);
|
|
45
|
+
const malformed=createR67NativeObservationAuthority({
|
|
46
|
+
getCodingPolicy:()=>({policy_sha256:hex('a'),roots:{}}),enabled:true,sessionStatus:getSession
|
|
47
|
+
});
|
|
48
|
+
await assert.rejects(()=>malformed({operation:'process',args:['process','4']}),/POLICY_MISSING/);
|
|
49
|
+
});
|