deadbyte-mcp 0.15.74 → 0.15.75
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 +102 -42
- package/PROVENANCE.json +19 -21
- package/README.txt +23 -18
- package/RELEASE.json +51 -18
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +22 -2
- package/bin/deadbyte-desktop-uia-r199-fixed.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199-fixed.obj +0 -0
- package/bin/deadbyte-desktop-uia-r199.exe +0 -0
- package/bin/deadbyte-desktop-uia-r199.obj +0 -0
- package/bin/deadbyte-fs-read-r197-fixed.exe +0 -0
- package/bin/deadbyte-fs-read-r198.exe +0 -0
- package/bin/deadbyte-fs-read-r198.obj +0 -0
- package/bin/deadbyte-root-relative-probe-r197.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +2 -8
- package/native/deadbyte-desktop-uia-r199.cpp +345 -0
- package/native/deadbyte-fs-read-r197.cpp +174 -0
- package/native/deadbyte-fs-read-r198.cpp +181 -0
- package/native/deadbyte-root-relative-probe-r197.cpp +97 -0
- package/package.json +4 -2
- package/scripts/build-native-desktop-uia-r199-fixed.ps1 +28 -0
- package/scripts/build-native-desktop-uia-r199.ps1 +28 -0
- package/scripts/build-native-fs-read-r197-fixed.ps1 +28 -0
- package/scripts/build-native-fs-read-r197.ps1 +28 -0
- package/scripts/build-native-fs-read-r198.ps1 +28 -0
- package/scripts/build-native-root-probe-r197.ps1 +28 -0
- package/scripts/gate-windows-r195.ps1 +114 -0
- package/scripts/gate-windows-r206.ps1 +116 -0
- package/scripts/init-desktop-policy-r199.ps1 +85 -0
- package/scripts/npm-release-r48.mjs +17 -0
- package/scripts/npm-release-readiness-r195.mjs +72 -0
- package/scripts/npm-release-readiness-r206.mjs +76 -0
- package/scripts/r193-owner-request-live.mjs +5 -6
- package/scripts/r193-owner-uac-broker.mjs +3 -3
- package/scripts/r205-candidate-selection.mjs +22 -0
- package/src/candidate-selection-evidence-r206.mjs +71 -0
- package/src/candidate-selection-r205.mjs +96 -0
- package/src/desktop-postcondition-r196.mjs +30 -0
- package/src/desktop-runtime-r40.mjs +44 -7
- package/src/desktop-uia-identity-r199.mjs +51 -0
- package/src/desktop-uia-r40.mjs +9 -1
- package/src/machine-fs-handle-read-r196.mjs +43 -0
- package/src/machine-fs-native-read-r197.mjs +73 -0
- package/src/machine-fs-native-read-r198.mjs +76 -0
- package/src/machine-fs-read-worker.mjs +5 -25
- package/src/machine-fs.mjs +31 -16
- package/src/r195-canonical-owner-observation.mjs +15 -0
- package/src/remote-destination-journal-r202.mjs +120 -0
- package/src/remote-device-r44.mjs +89 -14
- package/src/remote-payload-boundary-r201.mjs +71 -0
- package/src/remote-terminal-evidence-r200.mjs +60 -0
- package/src/version.mjs +1 -1
- package/test/desktop-runtime-r40.test.mjs +111 -0
- package/test/desktop-uia-r40.test.mjs +15 -0
- package/test/fixtures/r197-junction-swap-worker.mjs +21 -0
- package/test/fixtures/r203-journal-writer.mjs +6 -0
- package/test/process-policy-refresh.test.mjs +4 -4
- 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 +12 -12
- 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 +9 -9
- package/test/r193-release-identity.test.mjs +24 -22
- package/test/r193-release-readiness.test.mjs +2 -2
- package/test/r193-windows-gate.test.mjs +8 -8
- package/test/r194-release-identity.test.mjs +19 -15
- package/test/r194-release-readiness.test.mjs +2 -2
- package/test/r194-windows-gate.test.mjs +7 -6
- package/test/r195-canonical-owner-observation.test.mjs +32 -0
- package/test/r195-release-readiness.test.mjs +22 -0
- package/test/r196-desktop-postcondition.test.mjs +53 -0
- package/test/r196-handle-bound-read.test.mjs +99 -0
- package/test/r196-remote-outcome.test.mjs +94 -0
- package/test/r197-namespace-race.test.mjs +41 -0
- package/test/r197-native-dispatch-race.test.mjs +49 -0
- package/test/r197-native-file-read.test.mjs +60 -0
- package/test/r197-native-root-open.test.mjs +61 -0
- package/test/r198-root-identity.test.mjs +88 -0
- package/test/r199-native-uia-worker.test.mjs +28 -0
- package/test/r199-uia-identity.test.mjs +43 -0
- package/test/r200-remote-reconcile.test.mjs +106 -0
- package/test/r201-payload-boundary.test.mjs +78 -0
- package/test/r201-payload-fuzz.test.mjs +37 -0
- package/test/r201-tool-boundary-fuzz.test.mjs +120 -0
- package/test/r202-destination-journal.test.mjs +92 -0
- package/test/r203-atomic-journal.test.mjs +86 -0
- package/test/r205-candidate-selection-cli.test.mjs +31 -0
- package/test/r205-candidate-selection.test.mjs +83 -0
- package/test/r206-candidate-evidence.test.mjs +64 -0
- package/test/r206-release-contract.test.mjs +42 -0
- package/test/r206-release-readiness.test.mjs +23 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-machine-fs-large-read.test.mjs +26 -12
|
@@ -7,40 +7,41 @@ import path from 'node:path';
|
|
|
7
7
|
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
8
8
|
const json=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
|
|
9
9
|
const parent={
|
|
10
|
-
version:'0.15.
|
|
11
|
-
release_id:'v0.15.
|
|
12
|
-
manifest_sha256:'
|
|
13
|
-
generation_manifest_sha256:'
|
|
10
|
+
version:'0.15.74',
|
|
11
|
+
release_id:'v0.15.74-e84bf4eed16e9443',
|
|
12
|
+
manifest_sha256:'e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47',
|
|
13
|
+
generation_manifest_sha256:'598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7'
|
|
14
14
|
};
|
|
15
|
-
const parentTarball='
|
|
15
|
+
const parentTarball='5da436a3f8068107e74caf6da58ff1e211d4a62f94266853136b601762f36f0c';
|
|
16
16
|
|
|
17
|
-
test('
|
|
17
|
+
test('R206 package identity preserves the R193 owner boundary and separates R205 source from active R194 runtime',async()=>{
|
|
18
18
|
const [pkg,bridge,release,provenance,matrix,sbom,versionSource]=await Promise.all([
|
|
19
19
|
json('package.json'),json('bridge/package.json'),json('RELEASE.json'),json('PROVENANCE.json'),
|
|
20
20
|
json('TEST-MATRIX.json'),json('SBOM.cdx.json'),readFile(path.join(root,'src','version.mjs'),'utf8')]);
|
|
21
|
-
assert.equal(pkg.version,'0.15.
|
|
22
|
-
assert.equal(bridge.version,'0.15.
|
|
23
|
-
assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.
|
|
24
|
-
assert.equal(release.version,'0.15.
|
|
25
|
-
assert.equal(release.train,'
|
|
21
|
+
assert.equal(pkg.version,'0.15.75');
|
|
22
|
+
assert.equal(bridge.version,'0.15.75');
|
|
23
|
+
assert.match(versionSource,/DEADBYTE_VERSION = '0\.15\.75'/);
|
|
24
|
+
assert.equal(release.version,'0.15.75');
|
|
25
|
+
assert.equal(release.train,'R206');
|
|
26
26
|
for(const key of Object.keys(parent)){
|
|
27
27
|
assert.equal(release.parent[key],parent[key]);
|
|
28
28
|
assert.equal(release.runtime_predecessor[key],parent[key]);
|
|
29
29
|
}
|
|
30
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
31
|
-
assert.equal(release.source_predecessor.train,'
|
|
32
|
-
assert.equal(release.source_predecessor.manifest_sha256,'
|
|
33
|
-
assert.equal(release.source_predecessor.tarball_sha256,
|
|
34
|
-
assert.equal(provenance.version,'0.15.
|
|
30
|
+
assert.equal(release.source_predecessor.version,'0.15.75');
|
|
31
|
+
assert.equal(release.source_predecessor.train,'R205');
|
|
32
|
+
assert.equal(release.source_predecessor.manifest_sha256,'166eebde18d53a9ec46f738383c289733f9cd37918b3572b7515d1ab9b122c92');
|
|
33
|
+
assert.equal(release.source_predecessor.tarball_sha256,null);
|
|
34
|
+
assert.equal(provenance.version,'0.15.75');
|
|
35
35
|
assert.equal(provenance.parent_release_id,parent.release_id);
|
|
36
36
|
assert.equal(provenance.parent_manifest_sha256,parent.manifest_sha256);
|
|
37
37
|
assert.equal(provenance.parent_generation_manifest_sha256,parent.generation_manifest_sha256);
|
|
38
|
-
assert.equal(provenance.source_predecessor_version,'0.15.
|
|
39
|
-
assert.equal(provenance.
|
|
40
|
-
assert.equal(
|
|
41
|
-
assert.equal(matrix.
|
|
42
|
-
assert.equal(
|
|
43
|
-
assert.
|
|
38
|
+
assert.equal(provenance.source_predecessor_version,'0.15.75');
|
|
39
|
+
assert.equal(provenance.source_predecessor_train,'R205');
|
|
40
|
+
assert.equal(provenance.source_predecessor_tarball_sha256,null);
|
|
41
|
+
assert.equal(matrix.version,'0.15.75');
|
|
42
|
+
assert.equal(matrix.train,'R206');
|
|
43
|
+
assert.equal(sbom.metadata.component.version,'0.15.75');
|
|
44
|
+
assert.match(sbom.serialNumber,/r206:0\.15\.75$/);
|
|
44
45
|
});
|
|
45
46
|
|
|
46
47
|
test('R193 manifest binds every protected owner-boundary implementation file',async()=>{
|
|
@@ -50,6 +51,7 @@ test('R193 manifest binds every protected owner-boundary implementation file',as
|
|
|
50
51
|
'scripts/r193-migrate-runtime-limited.ps1','scripts/r193-owner-keygen.mjs',
|
|
51
52
|
'scripts/r193-owner-request-live.mjs','scripts/r193-owner-uac-approve.ps1',
|
|
52
53
|
'scripts/r193-owner-uac-broker.mjs','scripts/r193-owner-uac-enroll-and-approve.ps1',
|
|
54
|
+
'src/r195-canonical-owner-observation.mjs',
|
|
53
55
|
'src/r193-live-owner-subject.mjs','src/r193-owner-secret-acl.mjs',
|
|
54
56
|
'src/r193-owner-uac-broker.mjs','test/r193-release-identity.test.mjs'
|
|
55
57
|
]) assert.match(manifest,new RegExp(' '+file.replaceAll('.','\\.')+'(?:\\r?\\n|$)'));
|
|
@@ -77,11 +77,11 @@ test('R193 publisher refuses a repacked artifact that differs from closure bytes
|
|
|
77
77
|
observedSha256:'c'.repeat(64),observedIntegrity:'sha512-other',observedShasum:'d'.repeat(40)}),/PUBLISH_BLOCKED R193.*repacked tarball/);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
test('canonical npm release entrypoint enforces
|
|
80
|
+
test('canonical npm release entrypoint enforces current R195 closure before registry or pack work',async()=>{
|
|
81
81
|
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
82
82
|
const env={...process.env};delete env.DEADBYTE_R193_CLOSURE_ROOT;
|
|
83
83
|
const result=spawnSync(process.execPath,[path.join(root,'scripts','npm-release-r48.mjs'),'--publish-only'],{cwd:root,env,encoding:'utf8',windowsHide:true});
|
|
84
84
|
assert.notEqual(result.status,0);
|
|
85
|
-
assert.match(`${result.stdout}\n${result.stderr}`,/PUBLISH_BLOCKED
|
|
85
|
+
assert.match(`${result.stdout}\n${result.stderr}`,/PUBLISH_BLOCKED R206.*closure root absent/);
|
|
86
86
|
assert.doesNotMatch(`${result.stdout}\n${result.stderr}`,/npm publish|Packing artifact/);
|
|
87
87
|
});
|
|
@@ -6,17 +6,17 @@ import path from 'node:path';
|
|
|
6
6
|
|
|
7
7
|
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
8
8
|
|
|
9
|
-
test('
|
|
9
|
+
test('R206 Windows gate is the canonical exact-identity prepublication gate',async()=>{
|
|
10
10
|
const [pkg,gate]=await Promise.all([
|
|
11
11
|
readFile(path.join(root,'package.json'),'utf8').then(JSON.parse),
|
|
12
|
-
readFile(path.join(root,'scripts','gate-windows-
|
|
12
|
+
readFile(path.join(root,'scripts','gate-windows-r206.ps1'),'utf8')
|
|
13
13
|
]);
|
|
14
|
-
assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-
|
|
15
|
-
assert.match(gate,/0\.15\.
|
|
16
|
-
assert.match(gate,/
|
|
17
|
-
assert.match(gate,/v0\.15\.
|
|
18
|
-
assert.match(gate,/
|
|
19
|
-
assert.match(gate,/
|
|
14
|
+
assert.equal(pkg.scripts['gate:windows'],'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r206.ps1 -Stage all');
|
|
15
|
+
assert.match(gate,/0\.15\.75/);
|
|
16
|
+
assert.match(gate,/R206/);
|
|
17
|
+
assert.match(gate,/v0\.15\.74-e84bf4eed16e9443/);
|
|
18
|
+
assert.match(gate,/e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47/);
|
|
19
|
+
assert.match(gate,/598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7/);
|
|
20
20
|
assert.match(gate,/test\/r193-release-identity\.test\.mjs/);
|
|
21
21
|
assert.match(gate,/test\/r193-release-readiness\.test\.mjs/);
|
|
22
22
|
assert.match(gate,/test\/r193-owner-uac-broker\.test\.mjs/);
|
|
@@ -5,28 +5,28 @@ import{fileURLToPath}from'node:url';
|
|
|
5
5
|
import path from'node:path';
|
|
6
6
|
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
7
7
|
const json=async n=>JSON.parse(await readFile(path.join(root,n),'utf8'));
|
|
8
|
-
test('
|
|
8
|
+
test('R206 exact release identity binds R205 source separately from active R194 runtime',async()=>{
|
|
9
9
|
const [pkg,bridge,release,provenance,matrix,sbom,lock,ver]=await Promise.all([
|
|
10
10
|
json('package.json'),json('bridge/package.json'),json('RELEASE.json'),json('PROVENANCE.json'),
|
|
11
11
|
json('TEST-MATRIX.json'),json('SBOM.cdx.json'),json('docs/R184-package-lock.json'),
|
|
12
12
|
readFile(path.join(root,'src','version.mjs'),'utf8')]);
|
|
13
13
|
for(const v of [pkg.version,bridge.version,release.version,provenance.version,matrix.version,
|
|
14
|
-
sbom.metadata.component.version,lock.version,lock.packages[''].version])assert.equal(v,'0.15.
|
|
15
|
-
assert.match(ver,/DEADBYTE_VERSION = '0\.15\.
|
|
16
|
-
assert.equal(release.train,'
|
|
17
|
-
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:
|
|
18
|
-
assert.equal(release.parent.release_id,'v0.15.
|
|
14
|
+
sbom.metadata.component.version,lock.version,lock.packages[''].version])assert.equal(v,'0.15.75');
|
|
15
|
+
assert.match(ver,/DEADBYTE_VERSION = '0\.15\.75'/);
|
|
16
|
+
assert.equal(release.train,'R206');assert.equal(matrix.train,'R206');
|
|
17
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r206:0.15.75');
|
|
18
|
+
assert.equal(release.parent.release_id,'v0.15.74-e84bf4eed16e9443');
|
|
19
19
|
assert.equal(release.runtime_predecessor.release_id,release.parent.release_id);
|
|
20
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
21
|
-
assert.equal(release.source_predecessor.train,'
|
|
22
|
-
assert.equal(release.source_predecessor.release_status,'
|
|
23
|
-
assert.equal(release.source_predecessor.manifest_sha256,'
|
|
24
|
-
assert.equal(release.source_predecessor.tarball_sha256,
|
|
25
|
-
assert.equal(provenance.source_predecessor_version,'0.15.
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.75');
|
|
21
|
+
assert.equal(release.source_predecessor.train,'R205');
|
|
22
|
+
assert.equal(release.source_predecessor.release_status,'DEVELOPMENT_VERIFIED_RELEASE_BLOCKED');
|
|
23
|
+
assert.equal(release.source_predecessor.manifest_sha256,'166eebde18d53a9ec46f738383c289733f9cd37918b3572b7515d1ab9b122c92');
|
|
24
|
+
assert.equal(release.source_predecessor.tarball_sha256,null);
|
|
25
|
+
assert.equal(provenance.source_predecessor_version,'0.15.75');
|
|
26
26
|
assert.equal(provenance.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
|
|
27
|
-
assert.equal(release.
|
|
28
|
-
assert.equal(release.
|
|
29
|
-
assert.equal(release.
|
|
27
|
+
assert.equal(release.r195.scope,'CANONICAL_PROTECTED_OWNER_OBSERVATION');
|
|
28
|
+
assert.equal(release.r195.npm_publish,'NOT_RUN');
|
|
29
|
+
assert.equal(release.r195.production_activation,'NOT_RUN');
|
|
30
30
|
});
|
|
31
31
|
test('R194 canonical publisher binds R194 readiness before registry/pack effects',async()=>{
|
|
32
32
|
const [entry,readiness]=await Promise.all([
|
|
@@ -34,6 +34,10 @@ test('R194 canonical publisher binds R194 readiness before registry/pack effects
|
|
|
34
34
|
readFile(path.join(root,'scripts/npm-release-readiness-r194.mjs'),'utf8')]);
|
|
35
35
|
assert.match(entry,/assertR194NpmPublishReady/);
|
|
36
36
|
assert.match(entry,/if\(r194Readiness\)assertR194PackedIdentity/);
|
|
37
|
+
assert.match(entry,/assertR195NpmPublishReady/);
|
|
38
|
+
assert.match(entry,/if\(r195Readiness\)assertR195PackedIdentity/);
|
|
39
|
+
assert.match(entry,/assertR206NpmPublishReady/);
|
|
40
|
+
assert.match(entry,/if\(r206Readiness\)assertR206PackedIdentity/);
|
|
37
41
|
assert.match(entry,/authorizePublish:ownerAuth,authorizeAuthRetry:ownerAuth/);
|
|
38
42
|
assert.match(entry,/version==='0\.15\.74'/);
|
|
39
43
|
assert.match(entry,/if \(!publishOnly\)/);
|
|
@@ -77,11 +77,11 @@ test('R194 publisher refuses a repacked artifact that differs from closure bytes
|
|
|
77
77
|
observedSha256:'c'.repeat(64),observedIntegrity:'sha512-other',observedShasum:'d'.repeat(40)}),/PUBLISH_BLOCKED R194.*repacked tarball/);
|
|
78
78
|
});
|
|
79
79
|
|
|
80
|
-
test('canonical npm release entrypoint enforces
|
|
80
|
+
test('canonical npm release entrypoint enforces current R195 closure before registry or pack work',async()=>{
|
|
81
81
|
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
82
82
|
const env={...process.env};delete env.DEADBYTE_R194_CLOSURE_ROOT;
|
|
83
83
|
const result=spawnSync(process.execPath,[path.join(root,'scripts','npm-release-r48.mjs'),'--publish-only'],{cwd:root,env,encoding:'utf8',windowsHide:true});
|
|
84
84
|
assert.notEqual(result.status,0);
|
|
85
|
-
assert.match(`${result.stdout}\n${result.stderr}`,/PUBLISH_BLOCKED
|
|
85
|
+
assert.match(`${result.stdout}\n${result.stderr}`,/PUBLISH_BLOCKED R206.*closure root absent/);
|
|
86
86
|
assert.doesNotMatch(`${result.stdout}\n${result.stderr}`,/npm publish|Packing artifact/);
|
|
87
87
|
});
|
|
@@ -4,17 +4,18 @@ import {readFile} from 'node:fs/promises';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
7
|
-
test('
|
|
7
|
+
test('R206 prepublication Windows gate keeps active R194 runtime parent pinned',async()=>{
|
|
8
8
|
const [pkg,gate]=await Promise.all([
|
|
9
9
|
readFile(path.join(root,'package.json'),'utf8').then(JSON.parse),
|
|
10
|
-
readFile(path.join(root,'scripts/gate-windows-
|
|
10
|
+
readFile(path.join(root,'scripts/gate-windows-r206.ps1'),'utf8')]);
|
|
11
11
|
assert.equal(pkg.scripts['gate:windows'],
|
|
12
|
-
'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-
|
|
13
|
-
assert.match(gate,/0\.15\.
|
|
14
|
-
assert.match(gate,/v0\.15\.
|
|
15
|
-
assert.match(gate,/
|
|
12
|
+
'powershell -NoProfile -ExecutionPolicy Bypass -File scripts/gate-windows-r206.ps1 -Stage all');
|
|
13
|
+
assert.match(gate,/0\.15\.75/);assert.match(gate,/R206/);
|
|
14
|
+
assert.match(gate,/v0\.15\.74-e84bf4eed16e9443/);
|
|
15
|
+
assert.match(gate,/598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7/);
|
|
16
16
|
assert.match(gate,/test\/r194-release-identity\.test\.mjs/);
|
|
17
17
|
assert.match(gate,/test\/r194-npm-auth-recovery\.test\.mjs/);
|
|
18
|
+
assert.match(gate,/test\/r195-canonical-owner-observation\.test\.mjs/);
|
|
18
19
|
assert.match(gate,/test\/r193-owner-uac-broker\.test\.mjs/);
|
|
19
20
|
assert.doesNotMatch(gate,/arm(process|coding|autonomous|desktop|host)/i);
|
|
20
21
|
assert.doesNotMatch(gate,/deploy|activate|npm publish/i);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {observeCanonicalController} from '../src/r195-canonical-owner-observation.mjs';
|
|
4
|
+
|
|
5
|
+
test('R195 protected owner flow observes the active controller through the canonical launcher',async()=>{
|
|
6
|
+
const calls=[];
|
|
7
|
+
const result=await observeCanonicalController({
|
|
8
|
+
stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp',
|
|
9
|
+
powershell:'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
|
|
10
|
+
env:{PATH:'fixture'},
|
|
11
|
+
execFileAsync:async(file,args,options)=>{
|
|
12
|
+
calls.push({file,args,options});
|
|
13
|
+
return {stdout:'{"state":"ONLINE","supervisor":{"running":true}}\n'};
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
assert.deepEqual(result,{state:'ONLINE',supervisor:{running:true}});
|
|
17
|
+
assert.equal(calls.length,1);
|
|
18
|
+
assert.equal(calls[0].file,'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe');
|
|
19
|
+
assert.deepEqual(calls[0].args,[
|
|
20
|
+
'-NoLogo','-NoProfile','-NonInteractive','-ExecutionPolicy','Bypass','-File',
|
|
21
|
+
'C:\\Users\\DEADBYTE\\.deadbyte-mcp\\bin\\DEADBYTE-MCP.ps1','status','-Json'
|
|
22
|
+
]);
|
|
23
|
+
assert.equal(calls[0].options.shell,false);
|
|
24
|
+
assert.equal(calls[0].options.windowsHide,true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('R195 canonical owner observation rejects caller-selected relative state roots',async()=>{
|
|
28
|
+
await assert.rejects(()=>observeCanonicalController({
|
|
29
|
+
stateRoot:'.\\state',powershell:'powershell.exe',env:{},
|
|
30
|
+
execFileAsync:async()=>({stdout:'{}'})
|
|
31
|
+
}),/absolute state root/);
|
|
32
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {assertR195NpmPublishReady,assertR195PackedIdentity} from '../scripts/npm-release-readiness-r195.mjs';
|
|
4
|
+
|
|
5
|
+
const release={
|
|
6
|
+
version:'0.15.75',train:'R195',
|
|
7
|
+
parent:{version:'0.15.74',release_id:'v0.15.74-e84bf4eed16e9443',
|
|
8
|
+
manifest_sha256:'e84bf4eed16e9443fbd0bf9f0f562e26b01db778647aefcc8b12fd7f48636b47',
|
|
9
|
+
generation_manifest_sha256:'598f896be9ce65e6b0c618e8c8f6eaa2a9d91617a76a146bf3deb385a56d19e7'},
|
|
10
|
+
r195:{scope:'CANONICAL_PROTECTED_OWNER_OBSERVATION',public_tool_delta:0,authority_scope_delta:0}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
test('R195 publication refuses an absent exact-byte closure before pack or registry effects',async()=>{
|
|
14
|
+
await assert.rejects(assertR195NpmPublishReady(release,{root:'D:\\candidate'}),/PUBLISH_BLOCKED R195.*closure root absent/);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('R195 packed identity refuses any artifact differing from the protected closure',()=>{
|
|
18
|
+
assert.throws(()=>assertR195PackedIdentity({
|
|
19
|
+
tarball_sha256:'a'.repeat(64),tarball_integrity:'sha512-expected',tarball_shasum:'b'.repeat(40),
|
|
20
|
+
observedSha256:'c'.repeat(64),observedIntegrity:'sha512-other',observedShasum:'d'.repeat(40)
|
|
21
|
+
}),/PUBLISH_BLOCKED R195.*repacked tarball/);
|
|
22
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {evaluateDesktopPostcondition,validateDesktopPostconditionExpectation} from '../src/desktop-postcondition-r196.mjs';
|
|
5
|
+
|
|
6
|
+
const H=x=>createHash('sha256').update(String(x)).digest('hex');
|
|
7
|
+
const frame=(id,img)=>Object.freeze({frame_id:H('frame-'+id),image_sha256:H(img)});
|
|
8
|
+
const before=frame(1,'initial');
|
|
9
|
+
const after=frame(2,'changed');
|
|
10
|
+
|
|
11
|
+
test('R196 input delivery or changed pixels cannot silently confirm application effect',()=>{
|
|
12
|
+
const value=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after});
|
|
13
|
+
assert.equal(value.status,'unknown');
|
|
14
|
+
assert.equal(value.reason,'no_explicit_expectation');
|
|
15
|
+
assert.equal(value.kind,null);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('R196 explicit fresh expected image can satisfy an observable postcondition',()=>{
|
|
19
|
+
const expectation={kind:'frame_image_sha256',expected_sha256:after.image_sha256};
|
|
20
|
+
const result=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after,expectation});
|
|
21
|
+
assert.equal(result.status,'satisfied');
|
|
22
|
+
assert.equal(result.reason,'exact_post_image_observed');
|
|
23
|
+
assert.equal(result.expected_sha256,after.image_sha256);
|
|
24
|
+
assert.equal(result.after_frame_id,after.frame_id);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('R196 desired postcondition absent after input is unsatisfied',()=>{
|
|
28
|
+
const expected=frame(3,'desired').image_sha256;
|
|
29
|
+
const result=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after,
|
|
30
|
+
expectation:{kind:'frame_image_sha256',expected_sha256:expected}});
|
|
31
|
+
assert.equal(result.status,'unsatisfied');
|
|
32
|
+
assert.equal(result.reason,'post_image_differs_from_expected');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('R196 pre-existing expected state cannot prove input caused an effect',()=>{
|
|
36
|
+
const result=evaluateDesktopPostcondition({beforeFrame:before,afterFrame:after,
|
|
37
|
+
expectation:{kind:'frame_image_sha256',expected_sha256:before.image_sha256}});
|
|
38
|
+
assert.equal(result.status,'unknown');
|
|
39
|
+
assert.equal(result.reason,'expected_state_already_present_before_action');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('R196 forged or unsupported postcondition must be rejected before injection',()=>{
|
|
43
|
+
for(const value of [
|
|
44
|
+
{},{kind:'file_sha256',expected_sha256:H('anything')},
|
|
45
|
+
{kind:'frame_image_sha256',expected_sha256:'bogus'},
|
|
46
|
+
{kind:'frame_image_sha256',expected_sha256:H('valid'),_internal:true}
|
|
47
|
+
])assert.throws(()=>validateDesktopPostconditionExpectation(value),/unsupported/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('R196 invalid frames never produce a satisfied or unknown false evidence result',()=>{
|
|
51
|
+
assert.throws(()=>evaluateDesktopPostcondition({beforeFrame:{},afterFrame:after,
|
|
52
|
+
expectation:{kind:'frame_image_sha256',expected_sha256:after.image_sha256}}),/frames invalid/);
|
|
53
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import {mkdtemp,mkdir,writeFile,rm,open,rename,lstat} from 'node:fs/promises';
|
|
6
|
+
import {tmpdir} from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {syncBuiltinESMExports} from 'node:module';
|
|
9
|
+
import {spawnSync} from 'node:child_process';
|
|
10
|
+
import {fileURLToPath} from 'node:url';
|
|
11
|
+
import {loadCodingPolicy} from '../src/coding-plane.mjs';
|
|
12
|
+
import {createMachineFsRuntime} from '../src/machine-fs.mjs';
|
|
13
|
+
import {readRangeFromHandle} from '../src/machine-fs-handle-read-r196.mjs';
|
|
14
|
+
import {generateTrustKeypair} from '../src/trust.mjs';
|
|
15
|
+
|
|
16
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
17
|
+
async function fixture(t){
|
|
18
|
+
const root=await mkdtemp(path.join(tmpdir(),'deadbyte-r196-fd-'));
|
|
19
|
+
t.after(()=>rm(root,{recursive:true,force:true}));
|
|
20
|
+
const project=path.join(root,'project');await mkdir(project);
|
|
21
|
+
const policyPath=path.join(root,'policy.json');
|
|
22
|
+
await writeFile(policyPath,JSON.stringify({
|
|
23
|
+
schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
24
|
+
lease_file:path.join(root,'grant.json'),evidence_dir:path.join(root,'evidence'),
|
|
25
|
+
roots:{project:{path:project,read:true,write:false,exec:false,deny_paths:[]}},
|
|
26
|
+
commands:{},max_read_bytes:4194304,max_write_bytes:65536
|
|
27
|
+
}));
|
|
28
|
+
const policy=await loadCodingPolicy(policyPath);
|
|
29
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(root,'private.pem'),publicKeyPath:path.join(root,'public.pem')});
|
|
30
|
+
return {root,project,runtime:createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath})};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('R196 read hash must not reopen path after reading different bytes',async t=>{
|
|
34
|
+
const f=await fixture(t),a=Buffer.from('original-fd-a'),b=Buffer.from('alternate-path-b');
|
|
35
|
+
await writeFile(path.join(f.project,'source.bin'),a);
|
|
36
|
+
await writeFile(path.join(f.project,'alternate.bin'),b);
|
|
37
|
+
const original=fs.createReadStream;
|
|
38
|
+
let calls=0;
|
|
39
|
+
fs.createReadStream=(_p,opts)=>{calls++;return original(path.join(f.project,'alternate.bin'),opts)};
|
|
40
|
+
syncBuiltinESMExports();
|
|
41
|
+
try{
|
|
42
|
+
const r=await f.runtime.read({rootId:'project',relativePath:'source.bin',offset:0,maxBytes:64,hashFile:true});
|
|
43
|
+
assert.deepEqual(Buffer.from(r.data_base64,'base64'),a,'range reads source bytes');
|
|
44
|
+
assert.equal(r.file_sha256,sha(a),'digest must belong to the same file object as returned data');
|
|
45
|
+
assert.equal(calls,0,'hash must not reopen a pathname through createReadStream');
|
|
46
|
+
}finally{
|
|
47
|
+
fs.createReadStream=original;syncBuiltinESMExports();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('R196 standalone worker returns digest and bytes from source file',async t=>{
|
|
52
|
+
const f=await fixture(t),a=Buffer.from('worker-fd-identity\0\x01');
|
|
53
|
+
const p=path.join(f.project,'worker.bin');await writeFile(p,a);
|
|
54
|
+
const script=fileURLToPath(new URL('../src/machine-fs-read-worker.mjs',import.meta.url));
|
|
55
|
+
const run=spawnSync(process.execPath,[script,p,'0','32','1'],{encoding:'utf8',windowsHide:true,timeout:10000,shell:false});
|
|
56
|
+
assert.equal(run.status,0,run.stderr);
|
|
57
|
+
const v=JSON.parse(run.stdout);
|
|
58
|
+
assert.equal(v.schema,'deadbyte.machine-fs-read-worker.v1');
|
|
59
|
+
assert.deepEqual(Buffer.from(v.data_base64,'base64'),a);
|
|
60
|
+
assert.equal(v.file_sha256,sha(a));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('R196 retained FileHandle survives same-size pathname replacement',async t=>{
|
|
64
|
+
const f=await fixture(t);
|
|
65
|
+
const name=path.join(f.project,'mutable.bin'),oldName=path.join(f.project,'old.bin'),replacement=path.join(f.project,'replacement.bin');
|
|
66
|
+
const a=Buffer.from('AAAAAAAA-ORIGINAL'),b=Buffer.from('BBBBBBBB-REPLACED');
|
|
67
|
+
assert.equal(a.length,b.length);
|
|
68
|
+
await writeFile(name,a);await writeFile(replacement,b);
|
|
69
|
+
const handle=await open(name,'r');
|
|
70
|
+
try{
|
|
71
|
+
const opened=await handle.stat({bigint:true});
|
|
72
|
+
await rename(name,oldName);
|
|
73
|
+
await rename(replacement,name);
|
|
74
|
+
const current=await lstat(name,{bigint:true});
|
|
75
|
+
assert.notEqual(opened.ino,current.ino,'test fixture did not replace the file object');
|
|
76
|
+
const v=await readRangeFromHandle(handle,{offset:0,length:32,hashFile:true});
|
|
77
|
+
assert.deepEqual(Buffer.from(v.data_base64,'base64'),a);
|
|
78
|
+
assert.equal(v.file_sha256,sha(a));
|
|
79
|
+
assert.equal(v.file_ino,String(opened.ino));
|
|
80
|
+
}finally{await handle.close()}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('R196 in-place mutation between range and hash refuses mixed file epoch',async t=>{
|
|
84
|
+
const f=await fixture(t);
|
|
85
|
+
const file=path.join(f.project,'inplace.bin');
|
|
86
|
+
await writeFile(file,Buffer.from('old'));
|
|
87
|
+
const handle=await open(file,'r');
|
|
88
|
+
const proto=Object.getPrototypeOf(handle),original=proto.createReadStream;
|
|
89
|
+
let attempted=false;
|
|
90
|
+
proto.createReadStream=function(options){
|
|
91
|
+
attempted=true;
|
|
92
|
+
fs.writeFileSync(file,Buffer.from('new-bytes-with-different-size'));
|
|
93
|
+
return original.call(this,options);
|
|
94
|
+
};
|
|
95
|
+
try{
|
|
96
|
+
await assert.rejects(()=>readRangeFromHandle(handle,{offset:0,length:3,hashFile:true}),/opened file changed during read\/hash/);
|
|
97
|
+
assert.equal(attempted,true);
|
|
98
|
+
}finally{proto.createReadStream=original;await handle.close()}
|
|
99
|
+
});
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {generateKeyPairSync,createHash} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,rm} from 'node:fs/promises';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import {createRemoteDevicePlaneR44} from '../src/remote-device-r44.mjs';
|
|
8
|
+
|
|
9
|
+
const hash=b=>createHash('sha256').update(b).digest('hex');
|
|
10
|
+
function key(){
|
|
11
|
+
const {publicKey}=generateKeyPairSync('ed25519');
|
|
12
|
+
const der=Buffer.from(publicKey.export({type:'spki',format:'der'}));
|
|
13
|
+
return {id:hash(der),spki:der.toString('base64')};
|
|
14
|
+
}
|
|
15
|
+
async function fixture(t,relay,{timeoutMs=2000}={}){
|
|
16
|
+
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r196-remote-'));
|
|
17
|
+
t.after(()=>rm(dir,{recursive:true,force:true}));
|
|
18
|
+
const owner=key();
|
|
19
|
+
const factory=()=>createRemoteDevicePlaneR44({stateDir:dir,relay,authorizeCapability:async()=>true,
|
|
20
|
+
relayTimeoutMs:timeoutMs,probeRetries:0,reachableTtlMs:5000});
|
|
21
|
+
const plane=await factory();
|
|
22
|
+
await plane.pair({publicKeySpkiBase64:owner.spki,displayName:'R196'});
|
|
23
|
+
await plane.reportOnline({deviceId:owner.id,epoch:1,connectionId:'r196-disarmed'});
|
|
24
|
+
await plane.probe({deviceId:owner.id,epoch:1});
|
|
25
|
+
return {owner,plane,factory};
|
|
26
|
+
}
|
|
27
|
+
const req=(owner,id)=>({deviceId:owner.id,epoch:1,capabilityId:'machine.fs.write',payload:{file:'safe.txt',content:'one'},requestId:id.repeat(64)});
|
|
28
|
+
|
|
29
|
+
test('R196 concurrent identical request ID has only one relay side effect',async t=>{
|
|
30
|
+
let release,started;
|
|
31
|
+
const entered=new Promise(resolve=>started=resolve);
|
|
32
|
+
const gate=new Promise(resolve=>release=resolve);
|
|
33
|
+
let effects=0;
|
|
34
|
+
const relay={
|
|
35
|
+
async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
|
|
36
|
+
async request(x){effects++;started();await gate;return {ack:'accepted',request_id:x.request_id,terminal:{status:'completed',result_sha256:'a'.repeat(64)}};}
|
|
37
|
+
};
|
|
38
|
+
const {owner,plane}=await fixture(t,relay);
|
|
39
|
+
const one=plane.dispatch(req(owner,'1'));
|
|
40
|
+
const two=plane.dispatch(req(owner,'1'));
|
|
41
|
+
await entered;
|
|
42
|
+
await new Promise(resolve=>setTimeout(resolve,20));
|
|
43
|
+
release();
|
|
44
|
+
const [first,replay]=await Promise.all([one,two]);
|
|
45
|
+
assert.equal(effects,1,'duplicate side effect for concurrent same-ID dispatch');
|
|
46
|
+
assert.equal(first.state,'completed');
|
|
47
|
+
assert.equal(first.effect_state,'confirmed');
|
|
48
|
+
assert.equal(first.transport_state,'terminal_received');
|
|
49
|
+
assert.equal(replay.replay,true);
|
|
50
|
+
assert.equal(replay.request_sha256,first.request_sha256);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('R196 lost reply after committed remote effect is UNKNOWN not a confirmed operation failure',async t=>{
|
|
54
|
+
let effects=0;
|
|
55
|
+
const relay={
|
|
56
|
+
async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
|
|
57
|
+
async request(){effects++;return await new Promise(()=>{});}
|
|
58
|
+
};
|
|
59
|
+
const {owner,plane,factory}=await fixture(t,relay,{timeoutMs:100});
|
|
60
|
+
const args=req(owner,'2');
|
|
61
|
+
const result=await plane.dispatch(args);
|
|
62
|
+
assert.equal(result.state,'failed','legacy transport failure label remains for compatibility');
|
|
63
|
+
assert.equal(result.error_code,'transport_error');
|
|
64
|
+
assert.equal(result.transport_state,'timed_out');
|
|
65
|
+
assert.equal(result.effect_state,'unknown','transport timeout cannot prove no remote mutation');
|
|
66
|
+
assert.equal(effects,1);
|
|
67
|
+
const restarted=await factory();
|
|
68
|
+
const second=await restarted.dispatch(args);
|
|
69
|
+
assert.equal(second.replay,true);
|
|
70
|
+
assert.equal(second.effect_state,'unknown');
|
|
71
|
+
assert.equal(effects,1,'restart must never reexecute accepted unknown-outcome mutation');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('R196 explicit missing ACK and terminal ACK preserve independent transport/effect facts',async t=>{
|
|
75
|
+
const relay={
|
|
76
|
+
async probe(x){return {device_id:x.device_id,epoch:x.epoch,nonce:x.nonce,ready:true};},
|
|
77
|
+
async request(){return null;}
|
|
78
|
+
};
|
|
79
|
+
const {owner,plane}=await fixture(t,relay);
|
|
80
|
+
const noAck=await plane.dispatch(req(owner,'3'));
|
|
81
|
+
assert.equal(noAck.error_code,'transport_no_ack');
|
|
82
|
+
assert.equal(noAck.transport_state,'ack_missing');
|
|
83
|
+
assert.equal(noAck.effect_state,'unknown');
|
|
84
|
+
relay.request=async x=>({ack:'accepted',request_id:x.request_id});
|
|
85
|
+
const noTerminal=await plane.dispatch(req(owner,'4'));
|
|
86
|
+
assert.equal(noTerminal.error_code,'transport_no_terminal');
|
|
87
|
+
assert.equal(noTerminal.transport_state,'acknowledged');
|
|
88
|
+
assert.equal(noTerminal.effect_state,'unknown');
|
|
89
|
+
relay.request=async x=>({ack:'accepted',request_id:x.request_id,terminal:{status:'completed',result_sha256:'b'.repeat(64)}});
|
|
90
|
+
const complete=await plane.dispatch(req(owner,'5'));
|
|
91
|
+
assert.equal(complete.transport_state,'terminal_received');
|
|
92
|
+
assert.equal(complete.effect_state,'confirmed');
|
|
93
|
+
assert.equal(complete.state,'completed');
|
|
94
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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 {fileURLToPath} from 'node:url';
|
|
7
|
+
import {loadCodingPolicy} from '../src/coding-plane.mjs';
|
|
8
|
+
import {createMachineFsRuntime} from '../src/machine-fs.mjs';
|
|
9
|
+
import {generateTrustKeypair} from '../src/trust.mjs';
|
|
10
|
+
|
|
11
|
+
test('R197 legacy injectable pathname worker demonstrates escaped open but parent rejects data',async t=>{
|
|
12
|
+
if(process.platform!=='win32')return t.skip('native Windows reparse fixture');
|
|
13
|
+
const scratch=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r197-namespace-'));
|
|
14
|
+
t.after(()=>rm(scratch,{recursive:true,force:true}));
|
|
15
|
+
const project=path.join(scratch,'project'),external=path.join(scratch,'external');
|
|
16
|
+
await mkdir(project);await mkdir(external);await mkdir(path.join(project,'mutable'));
|
|
17
|
+
await writeFile(path.join(project,'mutable','sample.bin'),'INSIDE-1234');
|
|
18
|
+
await writeFile(path.join(external,'sample.bin'),'OUTSIDE-123');
|
|
19
|
+
const policyFile=path.join(scratch,'policy.json');
|
|
20
|
+
await writeFile(policyFile,JSON.stringify({
|
|
21
|
+
schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
22
|
+
lease_file:path.join(scratch,'grant.json'),evidence_dir:path.join(scratch,'evidence'),
|
|
23
|
+
roots:{project:{path:project,read:true,write:false,exec:false,deny_paths:[]}},
|
|
24
|
+
commands:{},max_read_bytes:65536,max_write_bytes:65536
|
|
25
|
+
}));
|
|
26
|
+
const policy=await loadCodingPolicy(policyFile);
|
|
27
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(scratch,'private.pem'),publicKeyPath:path.join(scratch,'public.pem')});
|
|
28
|
+
const worker=fileURLToPath(new URL('./fixtures/r197-junction-swap-worker.mjs',import.meta.url));
|
|
29
|
+
const runtime=createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,
|
|
30
|
+
verifyKeyPath:keys.publicKeyPath,readWorkerPath:worker});
|
|
31
|
+
let outcome;
|
|
32
|
+
try{outcome=await runtime.read({rootId:'project',relativePath:'mutable/sample.bin',offset:0,maxBytes:32,hashFile:true,timeoutMs:5000});}
|
|
33
|
+
catch(e){outcome={error:String(e?.message??e)}}
|
|
34
|
+
const marker=path.join(scratch,'r197-worker-entered.marker');
|
|
35
|
+
await lstat(marker); // Proves an actual post-precheck execution point was reached.
|
|
36
|
+
const escaped=path.join(scratch,'r197-escaped-open.marker');
|
|
37
|
+
const observed=await lstat(escaped).then(()=>true,()=>false);
|
|
38
|
+
// This deliberately characterizes the old injected-worker path. It is NOT a secure reader proof.
|
|
39
|
+
assert.equal(observed,true,'R197 diagnostic fixture must demonstrate the legacy escaped open');
|
|
40
|
+
assert.equal(outcome.error!==undefined,true,'raced request must fail closed');
|
|
41
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import cp from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import {mkdtemp,mkdir,writeFile,rm,realpath} from 'node:fs/promises';
|
|
6
|
+
import {syncBuiltinESMExports} from 'node:module';
|
|
7
|
+
import {tmpdir} from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import {loadCodingPolicy} from '../src/coding-plane.mjs';
|
|
10
|
+
import {createMachineFsRuntime} from '../src/machine-fs.mjs';
|
|
11
|
+
import {generateTrustKeypair} from '../src/trust.mjs';
|
|
12
|
+
|
|
13
|
+
test('R197 default native read refuses a junction swapped after validation but before kernel open',async t=>{
|
|
14
|
+
if(process.platform!=='win32')return t.skip('Windows native test only');
|
|
15
|
+
const scratch=await mkdtemp(path.join(tmpdir(),'deadbyte-r197-native-race-'));
|
|
16
|
+
t.after(()=>rm(scratch,{recursive:true,force:true}));
|
|
17
|
+
const root=path.join(scratch,'project'),external=path.join(scratch,'external');
|
|
18
|
+
await mkdir(root);await mkdir(external);await mkdir(path.join(root,'mutable'));
|
|
19
|
+
await writeFile(path.join(root,'mutable','sample.bin'),'INSIDE-1234');
|
|
20
|
+
await writeFile(path.join(external,'sample.bin'),'OUTSIDE-123');
|
|
21
|
+
const file=path.join(scratch,'policy.json');
|
|
22
|
+
await writeFile(file,JSON.stringify({
|
|
23
|
+
schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
24
|
+
lease_file:path.join(scratch,'grant.json'),evidence_dir:path.join(scratch,'evidence'),
|
|
25
|
+
roots:{project:{path:root,read:true,write:false,exec:false,deny_paths:[]}},
|
|
26
|
+
commands:{},max_read_bytes:65536,max_write_bytes:65536
|
|
27
|
+
}));
|
|
28
|
+
const policy=await loadCodingPolicy(file);
|
|
29
|
+
const keys=await generateTrustKeypair({privateKeyPath:path.join(scratch,'private.pem'),publicKeyPath:path.join(scratch,'public.pem')});
|
|
30
|
+
const runtime=createMachineFsRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
31
|
+
const original=cp.spawn;
|
|
32
|
+
let intercepted=0;
|
|
33
|
+
cp.spawn=(command,args,options)=>{
|
|
34
|
+
if(path.basename(String(command)).toLowerCase()==='deadbyte-fs-read-r198.exe'){
|
|
35
|
+
intercepted++;
|
|
36
|
+
fs.renameSync(path.join(root,'mutable'),path.join(root,'mutable-original'));
|
|
37
|
+
fs.symlinkSync(external,path.join(root,'mutable'),'junction');
|
|
38
|
+
}
|
|
39
|
+
return original(command,args,options);
|
|
40
|
+
};
|
|
41
|
+
syncBuiltinESMExports();
|
|
42
|
+
try{
|
|
43
|
+
await assert.rejects(runtime.read({rootId:'project',relativePath:'mutable/sample.bin',
|
|
44
|
+
offset:0,maxBytes:32,hashFile:true,timeoutMs:5000}),/native root-relative read refused/);
|
|
45
|
+
}finally{cp.spawn=original;syncBuiltinESMExports();}
|
|
46
|
+
assert.equal(intercepted,1,'native operation was not reached after path precheck');
|
|
47
|
+
assert.equal((await realpath(path.join(root,'mutable','sample.bin'))).toLowerCase(),
|
|
48
|
+
path.join(external,'sample.bin').toLowerCase(),'fixture did not redirect the pathname outside root');
|
|
49
|
+
});
|