deadbyte-mcp 0.15.46 → 0.15.48

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.
Files changed (98) hide show
  1. package/MANIFEST.SHA256 +97 -45
  2. package/PROVENANCE.json +22 -11
  3. package/README.txt +18 -18
  4. package/RELEASE.json +378 -369
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bin/deadbyte-epoch-lock-r85.node +0 -0
  8. package/bin/deadbyte-kernel-lock-r74.exe +0 -0
  9. package/bridge/package.json +1 -1
  10. package/controller/deadbyte-process-policy.json +7 -7
  11. package/docs/R73-CAPABILITY-GAP-MATRIX.json +17 -0
  12. package/docs/R73-EXECUTION-CONTRACT.md +11 -0
  13. package/docs/R73-IMPLEMENTATION-CHANGELOG.md +12 -0
  14. package/docs/R73-REMAINING-DEFECTS.md +9 -0
  15. package/docs/R74-KERNEL-LOCK-BINARY.json +11 -0
  16. package/docs/R75-RECOVERY-HANDOFF.md +16 -0
  17. package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
  18. package/native/deadbyte-epoch-lock-r85.cpp +241 -0
  19. package/native/deadbyte-kernel-lock-r74.cpp +65 -0
  20. package/package.json +1 -1
  21. package/scripts/build-epoch-lock-r85.ps1 +34 -0
  22. package/scripts/build-kernel-lock-r74.ps1 +28 -0
  23. package/scripts/deploy-r48.mjs +3 -1
  24. package/scripts/gate-windows-r72.ps1 +2 -2
  25. package/scripts/npm-prepublish-contract-r74.mjs +113 -0
  26. package/scripts/npm-release-r48.mjs +24 -4
  27. package/scripts/npm-release-readiness-r108.mjs +134 -0
  28. package/scripts/npm-release-readiness-r74.mjs +46 -0
  29. package/scripts/r73-atomic-benchmark.mjs +39 -0
  30. package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
  31. package/scripts/release-manifest.mjs +1 -1
  32. package/scripts/verify-r70-full-regression.ps1 +12 -2
  33. package/src/activation-authority-proof-r75.mjs +67 -0
  34. package/src/activation-continuity-engine-r65.mjs +17 -4
  35. package/src/activation-preflight-r65.mjs +13 -0
  36. package/src/atomic-mutation.mjs +99 -9
  37. package/src/authority-continuity-r65.mjs +265 -21
  38. package/src/deadbyte-cli.mjs +55 -18
  39. package/src/epoch-lock-r85.mjs +48 -0
  40. package/src/foreground-marker-cas-win32.mjs +284 -0
  41. package/src/foreground-process-identity.mjs +3 -3
  42. package/src/kernel-lock-r74.mjs +125 -0
  43. package/src/privileged-install-trust-r89.mjs +196 -0
  44. package/src/remote-session-guardian.mjs +9 -7
  45. package/src/runtime-update.mjs +5 -22
  46. package/src/version.mjs +1 -1
  47. package/src/windows-powershell-environment.mjs +24 -0
  48. package/test/atomic-mutation.test.mjs +68 -2
  49. package/test/cli-entrypoint.test.mjs +1 -1
  50. package/test/commander-schema-differential-r48.test.mjs +1 -1
  51. package/test/helpers/r75-authority-proof.mjs +9 -0
  52. package/test/r100-posix-owned-lock-race.test.mjs +72 -0
  53. package/test/r101-acl-probe-environment.test.mjs +51 -0
  54. package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
  55. package/test/r107-updater-powershell-path.test.mjs +77 -0
  56. package/test/r33-closeout-regression.test.mjs +2 -1
  57. package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
  58. package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
  59. package/test/r56-rollback-launcher-restore.test.mjs +2 -2
  60. package/test/r57-core-startup-race.test.mjs +2 -2
  61. package/test/r58-concurrent-recovery.test.mjs +10 -7
  62. package/test/r58-stale-owner.test.mjs +5 -6
  63. package/test/r59-tool-surface-compat.test.mjs +1 -1
  64. package/test/r65-activation-orchestration.test.mjs +2 -1
  65. package/test/r65-activation-preflight.test.mjs +7 -6
  66. package/test/r65-authority-epoch-recovery.test.mjs +3 -1
  67. package/test/r65-authority-restored-safe.test.mjs +2 -1
  68. package/test/r65-deploy-preflight.test.mjs +13 -14
  69. package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
  70. package/test/r65-legacy-authority-migration.test.mjs +2 -1
  71. package/test/r67-npm-cross-volume.test.mjs +9 -2
  72. package/test/r71-full-verifier-artifact.test.mjs +21 -15
  73. package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
  74. package/test/r71-release-identity.test.mjs +3 -1
  75. package/test/r73-package-boundary.test.mjs +22 -0
  76. package/test/r74-kernel-lock.test.mjs +156 -0
  77. package/test/r74-prepublish-contract.test.mjs +89 -0
  78. package/test/r75-activation-authority-proof.test.mjs +46 -0
  79. package/test/r75-authority-evidence.test.mjs +77 -0
  80. package/test/r76-pinned-marker-cas.test.mjs +78 -0
  81. package/test/r77-epoch-grant-cas.test.mjs +51 -0
  82. package/test/r78-parent-junction-cas.test.mjs +62 -0
  83. package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
  84. package/test/r79-windows-fsync-regression.test.mjs +45 -0
  85. package/test/r80-durable-epoch-commit.test.mjs +76 -0
  86. package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
  87. package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
  88. package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
  89. package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
  90. package/test/r85-epoch-repeat-append.test.mjs +30 -0
  91. package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
  92. package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
  93. package/test/r88-native-disposition-failure.test.mjs +35 -0
  94. package/test/r89-privileged-install-boundary.test.mjs +123 -0
  95. package/test/r92-acl-ace-applicability.test.mjs +45 -0
  96. package/test/r94-win32-propagation-enum.test.mjs +51 -0
  97. package/test/r99-privileged-import-closure.test.mjs +45 -0
  98. package/test/remote-session-cli.test.mjs +69 -9
@@ -0,0 +1,196 @@
1
+ // R89 development: fail closed before requiring a privileged Windows native addon.
2
+ // Trust is an OS-owned installation property, not a manifest hash or npm version.
3
+ // This checks an honest process against lower-trust filesystem principals; it is
4
+ // NOT an attestation against a malicious actor already executing with equivalent
5
+ // write/administrative rights, or against a compromised Node/PowerShell runtime.
6
+ import {execFile} from 'node:child_process';
7
+ import {promisify} from 'node:util';
8
+ import path from 'node:path';
9
+
10
+ const execFileAsync=promisify(execFile);
11
+ function windowsPowerShellEnvironment(base=process.env){
12
+ const env={...base};
13
+ // The ACL probe must not autoload commands from caller-controlled module
14
+ // directories. Its executable is pinned to C:\Windows below, so use only
15
+ // that installation's built-in modules and matching OS-root variables.
16
+ for(const key of Object.keys(env)){
17
+ if(['psmodulepath','systemroot','windir'].includes(key.toLowerCase()))delete env[key];
18
+ }
19
+ env.SystemRoot='C:\\Windows';
20
+ env.WINDIR='C:\\Windows';
21
+ env.PSModulePath='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules';
22
+ return env;
23
+ }
24
+ const DRIVE=/^[A-Za-z]:\\/;
25
+ const SYSTEM='S-1-5-18';
26
+ const ADMINISTRATORS='S-1-5-32-544';
27
+ const TRUSTED_INSTALLER='S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464';
28
+ const TRUSTED=new Set([SYSTEM,ADMINISTRATORS,TRUSTED_INSTALLER]);
29
+ // Block untrusted changes to an existing directory or file, including security
30
+ // descriptor, content, attributes, or deletion. Create-child alone is not enough
31
+ // to replace an existing child, but full Modify contains DELETE and is denied.
32
+ const COMMON_MUTATION=0x000d0150; // WRITE_EA, DELETE_CHILD, WRITE_ATTRIBUTES, DELETE, WRITE_DAC, WRITE_OWNER
33
+ const FILE_MUTATION=COMMON_MUTATION|0x00000002|0x00000004; // WRITE_DATA / APPEND_DATA
34
+ const DIR_MUTATION=COMMON_MUTATION;
35
+ // Win32 ACEs may retain generic/indeterminate masks rather than expanded
36
+ // FILE_GENERIC_WRITE/FILE_ALL_ACCESS. Do not treat those as harmless.
37
+ const GENERIC_WRITE_OR_ALL=0x40000000|0x10000000;
38
+ const INDETERMINATE_ACCESS=0x02000000; // MAXIMUM_ALLOWED is not a finite read-only grant
39
+ const ANY_MUTATION_OR_UNDECIDABLE=(GENERIC_WRITE_OR_ALL|INDETERMINATE_ACCESS);
40
+ // Windows ACE scope is not the same as ACE inheritance origin. INHERIT_ONLY
41
+ // means the ACE does NOT apply to the currently inspected file/directory;
42
+ // it may apply to children, which are checked separately in the path chain.
43
+ // Missing / malformed scope must never silently become an allow.
44
+ const INHERIT_ONLY=2; // .NET PropagationFlags.InheritOnly=2; NoPropagateInherit=1
45
+ const KNOWN_INHERITANCE_FLAGS=3; // ContainerInherit | ObjectInherit
46
+ const KNOWN_PROPAGATION_FLAGS=3; // InheritOnly | NoPropagateInherit
47
+
48
+ function normalizeWindows(file){
49
+ if(typeof file!=='string'||!DRIVE.test(file)||file.includes('\0')||file.startsWith('\\\\'))
50
+ throw new Error('R89 privileged path must be a Windows drive-absolute path');
51
+ const normalized=path.win32.normalize(file);
52
+ if(!DRIVE.test(normalized))throw new Error('R89 privileged path normalization invalid');
53
+ return normalized.toLowerCase();
54
+ }
55
+ export function privilegedEpochInstallFiles(root,nodeExecutable){
56
+ const join=(...segments)=>path.win32.join(root,...segments);
57
+ return [nodeExecutable,
58
+ join('bin','deadbyte-epoch-lock-r85.node'),
59
+ join('docs','R85-EPOCH-LOCK-BINARY.json'),
60
+ join('native','deadbyte-epoch-lock-r85.cpp'),
61
+ join('src','epoch-lock-r85.mjs'),
62
+ join('src','privileged-install-trust-r89.mjs'),
63
+ join('src','authority-continuity-r65.mjs'),
64
+ join('src','canonical-json.mjs'),
65
+ join('src','text-policy.mjs'),
66
+ join('src','foreground-marker-cas-win32.mjs'),
67
+ join('src','foreground-process-identity.mjs'),
68
+ join('src','windows-powershell-environment.mjs')];
69
+ }
70
+ export function requiredAclPathChain(files){
71
+ if(!Array.isArray(files)||files.length<1||files.length>32)
72
+ throw new Error('R89 privileged path inventory invalid');
73
+ const seen=new Set();
74
+ for(const file of files){
75
+ let current=normalizeWindows(file);
76
+ for(let depth=0;depth<128;depth++){
77
+ seen.add(current);
78
+ const parent=path.win32.dirname(current);
79
+ if(parent===current)break;
80
+ if(depth===127)throw new Error('R89 privileged ancestor depth exceeded');
81
+ current=parent;
82
+ }
83
+ }
84
+ return [...seen].sort();
85
+ }
86
+
87
+ // Pure conservative evaluator. Records must come from Windows Get-Acl, never
88
+ // from a writable package file, environment variable or release metadata.
89
+ export function evaluatePrivilegedAclSnapshot(records,files){
90
+ const required=requiredAclPathChain(files);
91
+ if(!Array.isArray(records)||records.length!==required.length)
92
+ return Object.freeze({trusted:false,reason:'INCOMPLETE_ACL_SNAPSHOT'});
93
+ const byPath=new Map();
94
+ for(const rec of records){
95
+ let key;
96
+ try{key=normalizeWindows(rec?.path);}catch{return Object.freeze({trusted:false,reason:'INVALID_ACL_PATH'});}
97
+ if(byPath.has(key))return Object.freeze({trusted:false,reason:'DUPLICATE_ACL_PATH',path:key});
98
+ byPath.set(key,rec);
99
+ }
100
+ const leaves=new Set(files.map(normalizeWindows));
101
+ for(const file of required){
102
+ const rec=byPath.get(file);
103
+ if(!rec)return Object.freeze({trusted:false,reason:'MISSING_ACL_NODE',path:file});
104
+ if(rec.reparse!==false || typeof rec.directory!=='boolean' ||
105
+ !TRUSTED.has(rec.owner) || !Array.isArray(rec.aces))
106
+ return Object.freeze({trusted:false,reason:'UNTRUSTED_OWNER_OR_REPARSE',path:file});
107
+ if(leaves.has(file)&&rec.directory===true)
108
+ return Object.freeze({trusted:false,reason:'EXPECTED_FILE_NOT_FILE',path:file});
109
+ if(!leaves.has(file)&&rec.directory!==true)
110
+ return Object.freeze({trusted:false,reason:'EXPECTED_PARENT_NOT_DIRECTORY',path:file});
111
+ for(const ace of rec.aces){
112
+ if(!ace||typeof ace.sid!=='string'||!['Allow','Deny'].includes(ace.type)||
113
+ !Number.isSafeInteger(ace.mask)||ace.mask<0||ace.mask>0xffffffff)
114
+ return Object.freeze({trusted:false,reason:'UNKNOWN_ACL_RULE',path:file});
115
+ // These values are obtained directly from OS AccessRule fields below.
116
+ // An inheritance-only CREATOR OWNER placeholder is not an effective
117
+ // mutation grant on THIS object. An effective CREATOR OWNER ACE (or an
118
+ // untrusted effective GENERIC_ALL) is still rejected, not whitelisted.
119
+ if(!Number.isInteger(ace.inheritance)||ace.inheritance<0||
120
+ (ace.inheritance&~KNOWN_INHERITANCE_FLAGS)!==0||
121
+ !Number.isInteger(ace.propagation)||ace.propagation<0||
122
+ (ace.propagation&~KNOWN_PROPAGATION_FLAGS)!==0||
123
+ (ace.propagation!==0&&ace.inheritance===0)||
124
+ ((ace.propagation&INHERIT_ONLY)!==0&&(!rec.directory||ace.inheritance===0)))
125
+ return Object.freeze({trusted:false,reason:'UNKNOWN_ACL_SCOPE',path:file});
126
+ if((ace.propagation&INHERIT_ONLY)!==0)continue;
127
+ if(ace.type==='Allow'&&!TRUSTED.has(ace.sid)&&
128
+ (ace.mask&((rec.directory?DIR_MUTATION:FILE_MUTATION)|ANY_MUTATION_OR_UNDECIDABLE))!==0)
129
+ return Object.freeze({trusted:false,reason:'UNTRUSTED_WRITE_ACE',path:file,sid:ace.sid});
130
+ }
131
+ }
132
+ return Object.freeze({trusted:true,reason:'PROTECTED_INSTALLATION_ACL_SNAPSHOT'});
133
+ }
134
+
135
+ // This command is compiled into the module; it does NOT run a repo-local .ps1.
136
+ // Errors, unknown ACEs/SIDs and inaccessible paths are fatal.
137
+ const aclProbe=String.raw`
138
+ $ErrorActionPreference='Stop'; Set-StrictMode -Version Latest
139
+ try {
140
+ $utf8=[Text.Encoding]::UTF8
141
+ $files=ConvertFrom-Json -InputObject ($utf8.GetString([Convert]::FromBase64String('__PATHS_BASE64__')))
142
+ $seen=[System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
143
+ $items=New-Object System.Collections.ArrayList
144
+ foreach($p in @($files)) {
145
+ $node=[IO.Path]::GetFullPath([string]$p)
146
+ while($true) {
147
+ if($seen.Add($node)) {
148
+ $item=Get-Item -LiteralPath $node -Force -ErrorAction Stop
149
+ $acl=Get-Acl -LiteralPath $node -ErrorAction Stop
150
+ $owner=$acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value
151
+ $aces=New-Object System.Collections.ArrayList
152
+ foreach($entry in $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])) {
153
+ [void]$aces.Add(@{sid=$entry.IdentityReference.Value;type=$entry.AccessControlType.ToString();mask=([int64]$entry.FileSystemRights -band [int64]4294967295);inheritance=[int]$entry.InheritanceFlags;propagation=[int]$entry.PropagationFlags})
154
+ }
155
+ [void]$items.Add(@{path=$node;owner=$owner;reparse=[bool]($item.Attributes -band [IO.FileAttributes]::ReparsePoint);directory=[bool]$item.PSIsContainer;aces=@($aces.ToArray())})
156
+ }
157
+ $parent=[IO.Directory]::GetParent($node)
158
+ if($null -eq $parent){break}
159
+ $node=$parent.FullName
160
+ }
161
+ }
162
+ ConvertTo-Json -InputObject @($items.ToArray()) -Depth 9 -Compress
163
+ } catch { [Console]::Error.WriteLine('R89_ACL_PROBE_FAILED: '+$_.Exception.GetType().Name); exit 12 }
164
+ `;
165
+
166
+ export async function inspectTrustedInstallation(files){
167
+ if(process.platform!=='win32'||process.arch!=='x64')
168
+ throw new Error('R89 privileged native loading requires Windows x64');
169
+ const required=requiredAclPathChain(files);
170
+ // Absolute OS executable: do not resolve a user-controlled PATH, run a
171
+ // repo-owned helper, or honor a caller-provided tool override.
172
+ const ps='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
173
+ const json=Buffer.from(JSON.stringify(files),'utf8').toString('base64');
174
+ const script=aclProbe.replace('__PATHS_BASE64__',json);
175
+ const encoded=Buffer.from(script,'utf16le').toString('base64');
176
+ let stdout;
177
+ try{
178
+ ({stdout}=await execFileAsync(ps,['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',encoded],
179
+ {windowsHide:true,timeout:12000,maxBuffer:512*1024,encoding:'utf8',env:windowsPowerShellEnvironment()}));
180
+ }catch(error){
181
+ throw Object.assign(new Error('R89 privileged installation ACL inspection failed closed',{cause:error}),
182
+ {code:'EPOCH_NATIVE_SLOT_UNTRUSTED'});
183
+ }
184
+ let records;
185
+ try{records=JSON.parse(stdout);}catch{
186
+ throw Object.assign(new Error('R89 privileged installation ACL response invalid'),
187
+ {code:'EPOCH_NATIVE_SLOT_UNTRUSTED'});
188
+ }
189
+ const decision=evaluatePrivilegedAclSnapshot(records,files);
190
+ if(!decision.trusted){
191
+ throw Object.assign(new Error(`R89 privileged installation not protected: ${decision.reason}`),
192
+ {code:'EPOCH_NATIVE_SLOT_UNTRUSTED',reason:decision.reason});
193
+ }
194
+ if(required.length!==records.length)throw new Error('R89 ACL result cardinality changed');
195
+ return decision;
196
+ }
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { lstat, readFile, rm } from 'node:fs/promises';
2
+ import { lstat } from 'node:fs/promises';
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
@@ -7,6 +7,7 @@ import { setTimeout as sleep } from 'node:timers/promises';
7
7
  import { windowsPowerShellEnvironment } from './runtime-update.mjs';
8
8
  import { resolveStateRoot } from './state-root.mjs';
9
9
  import { AUTHORITY_PLANES, parseAuthorityObservation, assertAuthorityVector } from './remote-authority.mjs';
10
+ import { readRemoteMarkerSnapshot, deletePinnedRemoteMarker } from './foreground-marker-cas-win32.mjs';
10
11
 
11
12
  const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost'];
12
13
  // This inherited IPC channel is an OS-owned parent lifetime handle, not a PID
@@ -33,10 +34,8 @@ function pidAlive(pid){
33
34
  catch(error){return error?.code!=='ESRCH';}
34
35
  }
35
36
  async function readMarker(file){
36
- let text;
37
- try{text=await readFile(file,'utf8');}
38
- catch(error){if(error?.code==='ENOENT') return null;throw error;}
39
- try{return JSON.parse(text);}catch{return null;}
37
+ const snapshot=await readRemoteMarkerSnapshot(file);
38
+ return snapshot?.marker??null;
40
39
  }
41
40
  function markerMatches(marker,{ownerPid,nonce}){
42
41
  return marker?.schema==='deadbyte.remote-session.v2'&&
@@ -99,8 +98,11 @@ async function main(){
99
98
  if(observed.state==='ONLINE'&&!invokeCleanup(powershell,launcher,'machinetest',root,'revoked'))
100
99
  throw new Error('guardian runtime enforcement cleanup unverified; ownership marker retained');
101
100
  if(!invokeCleanup(powershell,launcher,'park',root)) throw new Error('guardian park failed; ownership marker retained');
102
- const current=await readMarker(markerPath);
103
- if(current&&markerMatches(current,{ownerPid,nonce})) await rm(markerPath,{force:true});
101
+ const current=await readRemoteMarkerSnapshot(markerPath);
102
+ if(!current)return;
103
+ if(!markerMatches(current.marker,{ownerPid,nonce}))return;
104
+ if(deletePinnedRemoteMarker(markerPath,current.sha256)!=='REMOVED')
105
+ throw new Error('guardian marker changed before native deletion; marker retained');
104
106
  return;
105
107
  }
106
108
  }
@@ -21,6 +21,9 @@ import { resolveStateRoot } from './state-root.mjs';
21
21
  import {canonicalLauncherPaths,assertCanonicalLaunchers,renderStateBoundLauncherCmd} from './launcher-isolation.mjs';
22
22
  import {assertStageOwnedPath,assertStagingBootstrapInputs,assertStagingControlConfig} from './staging-input-isolation.mjs';
23
23
  import { verifyProjectOwnerApproval } from './r70-owner-approval.mjs';
24
+ import {windowsPowerShellEnvironment} from './windows-powershell-environment.mjs';
25
+
26
+ export {windowsPowerShellEnvironment};
24
27
 
25
28
  const HEX64=/^[0-9a-f]{64}$/;
26
29
  const SEMVER=/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
@@ -216,31 +219,11 @@ export function parseRollbackArgs(argv){
216
219
  }
217
220
 
218
221
  function powershellPath(){
219
- return path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
220
- }
221
- export function windowsPowerShellEnvironment(base=process.env){
222
- const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
223
- const userProfile=base.USERPROFILE||os.homedir();
224
- const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
225
- const environment={...base};
226
- const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath').flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
227
- for(const key of Object.keys(environment)) if(key.toLowerCase()==='psmodulepath') delete environment[key];
228
- const modules=[
229
- path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
230
- path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
231
- path.join(programFiles,'WindowsPowerShell','Modules'),
232
- ...inherited
233
- ];
234
- const seen=new Set();
235
- return {...environment,PSModulePath:modules.filter(modulePath=>{
236
- const key=modulePath.toLowerCase();
237
- if(seen.has(key)) return false;
238
- seen.add(key);return true;
239
- }).join(';')};
222
+ return 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
240
223
  }
241
224
  function commandEnvironment(exe,base=process.env){
242
225
  return path.resolve(exe).toLowerCase()===path.resolve(powershellPath()).toLowerCase()
243
- ?windowsPowerShellEnvironment(base):base;
226
+ ?windowsPowerShellEnvironment({...base,SystemRoot:'C:\\Windows',WINDIR:'C:\\Windows'}):base;
244
227
  }
245
228
  function runCaptured(exe,args,{cwd,env,timeoutMs=45000}={}){
246
229
  const baseEnv=env??process.env;
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.46';
1
+ export const DEADBYTE_VERSION = '0.15.48';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -0,0 +1,24 @@
1
+ // Dependency-light PowerShell child environment for security-critical process
2
+ // identity and pinned-marker operations. Do not import the updater here.
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ export function windowsPowerShellEnvironment(base=process.env){
7
+ const systemRoot=base.SystemRoot||base.SYSTEMROOT||'C:\\Windows';
8
+ const userProfile=base.USERPROFILE||os.homedir();
9
+ const programFiles=base.ProgramFiles||base.PROGRAMFILES||'C:\\Program Files';
10
+ const hasWindir=Object.keys(base).some(key=>key.toLowerCase()==='windir');
11
+ const environment={...base};
12
+ const inherited=Object.entries(base).filter(([key])=>key.toLowerCase()==='psmodulepath')
13
+ .flatMap(([,value])=>String(value??'').split(';')).filter(Boolean);
14
+ for(const key of Object.keys(environment))if(['psmodulepath','systemroot','windir'].includes(key.toLowerCase()))delete environment[key];
15
+ environment.SystemRoot=systemRoot;
16
+ if(hasWindir)environment.WINDIR=systemRoot;
17
+ const modules=[path.join(systemRoot,'System32','WindowsPowerShell','v1.0','Modules'),
18
+ path.join(userProfile,'Documents','WindowsPowerShell','Modules'),
19
+ path.join(programFiles,'WindowsPowerShell','Modules'),...inherited];
20
+ const seen=new Set();
21
+ return {...environment,PSModulePath:modules.filter(item=>{
22
+ const key=item.toLowerCase();if(seen.has(key))return false;seen.add(key);return true;
23
+ }).join(';')};
24
+ }
@@ -1,10 +1,13 @@
1
1
  import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import {createHash} from 'node:crypto';
4
- import {mkdtemp,readFile,rm,writeFile} from 'node:fs/promises';
4
+ import {mkdtemp,readFile,rm,utimes,writeFile} from 'node:fs/promises';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
- import {atomicMutation} from '../src/atomic-mutation.mjs';
7
+ import {pathToFileURL} from 'node:url';
8
+
9
+ const modulePath=process.env.DEADBYTE_R73_ATOMIC_BASELINE;
10
+ const {atomicMutation}=await import(modulePath?pathToFileURL(modulePath).href:'../src/atomic-mutation.mjs');
8
11
 
9
12
  const digest=bytes=>createHash('sha256').update(bytes).digest('hex');
10
13
 
@@ -59,3 +62,66 @@ test('postcondition verifier loss after exact commit is explicit unknown effect'
59
62
  assert.equal(await readFile(target,'utf8'),'new');
60
63
  }finally{await rm(root,{recursive:true,force:true});}
61
64
  });
65
+
66
+ test('an old lock owned by a live writer is not reclaimed by a concurrent writer',async()=>{
67
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-atomic-live-lock-'));
68
+ const target=path.join(root,'value.txt');await writeFile(target,'old');
69
+ const oldHash=digest(Buffer.from('old'));
70
+ const readState=async file=>{const bytes=await readFile(file);return {exists:true,sha256:digest(bytes)};};
71
+ let enter;
72
+ const entered=new Promise(resolve=>{enter=resolve;});
73
+ let release;
74
+ const held=new Promise(resolve=>{release=resolve;});
75
+ const first=atomicMutation({target,bytes:Buffer.from('first'),expected:oldHash,readState,
76
+ assertExpected(state,expected){assert.equal(state.sha256,expected);},
77
+ authorityCheck:async()=>{enter();await held;}
78
+ });
79
+ try{
80
+ await entered;
81
+ const lock=`${target}.deadbyte-mutation.lock`;
82
+ const stale=new Date(Date.now()-180000);
83
+ await utimes(lock,stale,stale);
84
+ const second=atomicMutation({target,bytes:Buffer.from('second'),expected:oldHash,readState,
85
+ assertExpected(state,expected){assert.equal(state.sha256,expected);}
86
+ }).then(()=>({status:'committed'}),error=>({status:'rejected',error}));
87
+ const early=await Promise.race([second,new Promise(resolve=>setTimeout(()=>resolve({status:'waiting'}),250))]);
88
+ release();
89
+ await first;
90
+ const result=await second;
91
+ assert.equal(early.status,'waiting','second writer entered while the first still owned the lock');
92
+ assert.equal(result.status,'rejected');
93
+ assert.equal(await readFile(target,'utf8'),'first');
94
+ }finally{release();await first.catch(()=>{});await rm(root,{recursive:true,force:true});}
95
+ });
96
+
97
+ test('an aged lock from a dead owner cannot be stolen without atomic ownership proof',async()=>{
98
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-atomic-dead-lock-'));
99
+ const target=path.join(root,'value.txt');await writeFile(target,'old');
100
+ const lock=`${target}.deadbyte-mutation.lock`;
101
+ await writeFile(lock,'999999999:0:abandoned');
102
+ const stale=new Date(Date.now()-180000);await utimes(lock,stale,stale);
103
+ const readState=async file=>{const bytes=await readFile(file);return {exists:true,sha256:digest(bytes)};};
104
+ try{
105
+ await assert.rejects(atomicMutation({target,bytes:Buffer.from('new'),expected:digest(Buffer.from('old')),readState,
106
+ assertExpected(state,expected){assert.equal(state.sha256,expected);}
107
+ }),/abandoned.*reconciliation/i);
108
+ assert.equal(await readFile(target,'utf8'),'old');
109
+ assert.equal(await readFile(lock,'utf8'),'999999999:0:abandoned');
110
+ }finally{await rm(root,{recursive:true,force:true});}
111
+ });
112
+
113
+ test('lock ownership loss after commit is classified as an unknown-effect failure',async()=>{
114
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-atomic-lock-loss-'));
115
+ const target=path.join(root,'value.txt');await writeFile(target,'old');
116
+ const readState=async file=>{const bytes=await readFile(file);return {exists:true,sha256:digest(bytes)};};
117
+ try{
118
+ const error=await atomicMutation({target,bytes:Buffer.from('new'),expected:digest(Buffer.from('old')),readState,
119
+ assertExpected(state,expected){assert.equal(state.sha256,expected);},
120
+ verifyPostcondition:async()=>{await writeFile(`${target}.deadbyte-mutation.lock`,'foreign-owner');}
121
+ }).then(()=>null,e=>e);
122
+ assert.equal(error?.outcome,'FAILED_AFTER_UNKNOWN_EFFECT');
123
+ assert.equal(error?.lock_cleanup_failed,true);
124
+ assert.equal(await readFile(target,'utf8'),'new');
125
+ assert.equal(await readFile(`${target}.deadbyte-mutation.lock`,'utf8'),'foreign-owner');
126
+ }finally{await rm(root,{recursive:true,force:true});}
127
+ });
@@ -44,7 +44,7 @@ test('npx CLI exposes local supervisor lifecycle without any supervisor arm comm
44
44
  assert.match(source,/supervisorstop/);
45
45
  assert.match(source,/supervisorstatus/);
46
46
  assert.match(source,/supervisorrecover/);
47
- const mapping=source.match(/const SUPERVISOR_COMMANDS=.*?;\n/s)?.[0]??'';
47
+ const mapping=source.match(/const SUPERVISOR_COMMANDS=[^;]*;/s)?.[0]??'';
48
48
  assert.doesNotMatch(mapping,/arm(?:coding|autonomous|process|host)/i);
49
49
  assert.match(source,/npx -y \$\{exact\} supervisor status/);
50
50
  });
@@ -5,7 +5,7 @@ import {readFile} from 'node:fs/promises';
5
5
  import {createCommanderWireAdapter} from '../src/commander-wire-adapter-r47.mjs';
6
6
  import {registerCommanderExtendedBridge} from '../src/commander-extended-bridge-r47.mjs';
7
7
 
8
- const referencePath='D:\\Projects\\deadbyte-mcp\\references\\desktop-commander\\dist\\tools\\schemas.js';
8
+ const referencePath='D:\\Projects\\deadbyte-mcp\\references\\desktop-commander v0.2.52\\dist\\tools\\schemas.js';
9
9
  const expectedHash='f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140';
10
10
  async function referenceSchemas(){
11
11
  let source=await readFile(referencePath,'utf8');
@@ -0,0 +1,9 @@
1
+ // Unit fixtures use synthetic observations; integration tests verify real policy/grant files.
2
+ export function withAuthorityProof(observation){
3
+ const authority_proof=Object.fromEntries(Object.entries(observation.authority).map(([plane,entry])=>{
4
+ const armed=typeof entry==='boolean'?entry:entry.armed;
5
+ return [plane,{state:armed?'VERIFIED_ARMED':'VERIFIED_DISARMED',
6
+ policy_sha256:observation.policy_sha256[plane],grant_absent:!armed}];
7
+ }));
8
+ return {...observation,authority_proof};
9
+ }
@@ -0,0 +1,72 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync,randomBytes} from 'node:crypto';
4
+ import {mkdtemp,mkdir,writeFile,readFile,rename,rm,access} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import os from 'node:os';
7
+ import {pathToFileURL} from 'node:url';
8
+
9
+ const source=process.env.R100_BASELINE_SOURCE
10
+ ?pathToFileURL(process.env.R100_BASELINE_SOURCE)
11
+ :new URL('../src/authority-continuity-r65.mjs',import.meta.url);
12
+ const {createAuthorityEpochStore}=await import(source.href);
13
+
14
+ test('R100 POSIX owned release cannot unlink a byte-identical successor lock',
15
+ {skip:process.platform==='win32'},async()=>{
16
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r100-posix-'));
17
+ try{
18
+ const runtimeDir=path.join(root,'runtime'),trust=path.join(root,'trust');
19
+ await mkdir(runtimeDir);await mkdir(trust);
20
+ const keys=generateKeyPairSync('ed25519');
21
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
22
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
23
+ const nonce=randomBytes(32).toString('hex');
24
+ const grantBytes=Buffer.from(JSON.stringify({instance_nonce:nonce}));
25
+ const lock=path.join(runtimeDir,'authority-epochs','process','.lock');
26
+ let replaced=0,successor;
27
+ const store=createAuthorityEpochStore({runtimeDir,onLockDispositionStage:async(stage,file)=>{
28
+ if(stage!=='release_before_cas')return;
29
+ assert.equal(file,lock);
30
+ successor=await readFile(lock);
31
+ const other=lock+'.successor';
32
+ await writeFile(other,successor,{flag:'wx'});
33
+ await rename(other,lock);
34
+ replaced++;
35
+ }});
36
+ await assert.rejects(store.append({plane:'process',action:'grant',grantBytes,
37
+ grantInstanceNonce:nonce}),/authority epoch lock changed|ownership lost|POSIX.*unsupported/);
38
+ if(replaced===0){
39
+ await assert.rejects(access(path.join(runtimeDir,'authority-epochs','process','head.json')),{code:'ENOENT'});
40
+ }else{
41
+ assert.equal(replaced,1,'replacement interleave must execute at most once');
42
+ assert.deepEqual(await readFile(lock),successor,'byte-identical successor lock must survive');
43
+ }
44
+ }finally{await rm(root,{recursive:true,force:true});}
45
+ });
46
+
47
+ test('R100 POSIX unsupported mutation makes no epoch directory and preserves an existing lock',
48
+ {skip:process.platform==='win32'},async()=>{
49
+ const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r100-no-side-effect-'));
50
+ try{
51
+ const runtimeDir=path.join(root,'runtime');
52
+ const trust=path.join(root,'trust');
53
+ await mkdir(runtimeDir);await mkdir(trust);
54
+ const keys=generateKeyPairSync('ed25519');
55
+ await writeFile(path.join(trust,'ed25519-private.pem'),keys.privateKey.export({type:'pkcs8',format:'pem'}),{flag:'wx'});
56
+ await writeFile(path.join(trust,'ed25519-public.pem'),keys.publicKey.export({type:'spki',format:'pem'}),{flag:'wx'});
57
+ const nonce=randomBytes(32).toString('hex');
58
+ const grantBytes=Buffer.from(JSON.stringify({instance_nonce:nonce}));
59
+ const operation=()=>createAuthorityEpochStore({runtimeDir}).append({plane:'process',action:'grant',
60
+ grantBytes,grantInstanceNonce:nonce});
61
+ await assert.rejects(operation,/authority epoch POSIX mutation unsupported/);
62
+ const epochDir=path.join(runtimeDir,'authority-epochs','process');
63
+ await assert.rejects(access(epochDir),{code:'ENOENT'});
64
+ assert.equal(await createAuthorityEpochStore({runtimeDir}).readHead({plane:'process'}),null,
65
+ 'read-only empty-head inspection remains available');
66
+ await mkdir(epochDir,{recursive:true});
67
+ const lock=path.join(epochDir,'.lock'),original=Buffer.from('not-an-owned-lock');
68
+ await writeFile(lock,original,{flag:'wx'});
69
+ await assert.rejects(operation,/authority epoch POSIX mutation unsupported/);
70
+ assert.deepEqual(await readFile(lock),original);
71
+ }finally{await rm(root,{recursive:true,force:true});}
72
+ });
@@ -0,0 +1,51 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {spawnSync} from 'node:child_process';
4
+ import {createRequire, syncBuiltinESMExports} from 'node:module';
5
+ import {pathToFileURL} from 'node:url';
6
+ import path from 'node:path';
7
+
8
+ const require=createRequire(import.meta.url);
9
+
10
+ test('R101 privileged ACL probe excludes inherited module roots at child dispatch',
11
+ {skip:process.platform!=='win32'}, async()=>{
12
+ const childProcess=require('node:child_process');
13
+ const originalExecFile=childProcess.execFile;
14
+ const originalModulePath=process.env.PSModulePath;
15
+ const originalSystemRoot=process.env.SystemRoot;
16
+ let captured;
17
+ try {
18
+ childProcess.execFile=(executable,args,options,callback)=>{
19
+ captured={executable,args,environment:options.env};
20
+ callback(Object.assign(new Error('test stopped child before execution'),{code:'TEST_STOP'}));
21
+ };
22
+ syncBuiltinESMExports();
23
+ process.env.PSModulePath='C:\\r101-poisoned-module-root';
24
+ process.env.SystemRoot='C:\\r101-false-windows-root';
25
+ const target=process.env.R101_BASELINE_SOURCE
26
+ ? pathToFileURL(path.join(process.env.R101_BASELINE_SOURCE,'src','privileged-install-trust-r89.mjs'))
27
+ : new URL('../src/privileged-install-trust-r89.mjs',import.meta.url);
28
+ const {inspectTrustedInstallation}=await import(`${target.href}?r101-acl-probe`);
29
+ await assert.rejects(inspectTrustedInstallation(['C:\\Program Files\\Deadbyte\\bin\\addon.node']),
30
+ {code:'EPOCH_NATIVE_SLOT_UNTRUSTED'});
31
+ assert.ok(captured,'ACL child must have been dispatched');
32
+ assert.equal(captured.executable,'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe');
33
+ assert.equal(captured.environment.PSModulePath,
34
+ 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules');
35
+ assert.equal(captured.environment.SystemRoot,'C:\\Windows');
36
+ assert.equal(captured.environment.WINDIR,'C:\\Windows');
37
+ const observed=spawnSync(captured.executable,['-NoLogo','-NoProfile','-NonInteractive',
38
+ '-Command',"(Get-Command Get-Acl -ErrorAction Stop).Module.Path"],
39
+ {env:captured.environment,encoding:'utf8',timeout:12000,windowsHide:true});
40
+ assert.equal(observed.status,0,observed.stderr);
41
+ assert.match(observed.stdout.trim(),
42
+ /^C:\\Windows\\System32\\WindowsPowerShell\\v1\.0\\Modules\\Microsoft\.PowerShell\.Security\\/i);
43
+ } finally {
44
+ childProcess.execFile=originalExecFile;
45
+ syncBuiltinESMExports();
46
+ if(originalModulePath===undefined)delete process.env.PSModulePath;
47
+ else process.env.PSModulePath=originalModulePath;
48
+ if(originalSystemRoot===undefined)delete process.env.SystemRoot;
49
+ else process.env.SystemRoot=originalSystemRoot;
50
+ }
51
+ });
@@ -0,0 +1,53 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import childProcess from 'node:child_process';
4
+ import {syncBuiltinESMExports} from 'node:module';
5
+ import {windowsPowerShellEnvironment} from '../src/windows-powershell-environment.mjs';
6
+
7
+ const TRUSTED_POWERSHELL='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
8
+
9
+ test('PowerShell child environment collapses case-variant Windows root keys',()=>{
10
+ const environment=windowsPowerShellEnvironment({
11
+ SystemRoot:'C:\\Windows',SYSTEMROOT:'C:\\untrusted-root',
12
+ WINDIR:'C:\\Windows',windir:'C:\\untrusted-root',
13
+ PSModulePath:'C:\\custom-modules'
14
+ });
15
+ assert.deepEqual(Object.keys(environment).filter(key=>key.toLowerCase()==='systemroot'),['SystemRoot']);
16
+ assert.deepEqual(Object.keys(environment).filter(key=>key.toLowerCase()==='windir'),['WINDIR']);
17
+ assert.equal(environment.SystemRoot,'C:\\Windows');
18
+ assert.equal(environment.WINDIR,'C:\\Windows');
19
+ });
20
+
21
+ test('security-sensitive Windows probes cannot select PowerShell via caller environment',
22
+ {skip:process.platform!=='win32',concurrency:false},async()=>{
23
+ const originalSpawn=childProcess.spawnSync;
24
+ const originalRoot=process.env.SystemRoot;
25
+ const originalWindir=process.env.WINDIR;
26
+ const calls=[];
27
+ try{
28
+ process.env.SystemRoot='C:\\untrusted-root';
29
+ process.env.WINDIR='C:\\untrusted-root';
30
+ childProcess.spawnSync=(file,args,options)=>{
31
+ calls.push({file,options});
32
+ return {status:0,stdout:calls.length===1?'MISSING\n':'null\n',stderr:''};
33
+ };
34
+ syncBuiltinESMExports();
35
+ const marker=await import('../src/foreground-marker-cas-win32.mjs');
36
+ const identity=await import('../src/foreground-process-identity.mjs');
37
+ assert.equal(marker.readPinnedEpochLockSnapshot('C:\\scratch\\lock.json'),null);
38
+ assert.equal(identity.queryForegroundIdentity(12345),null);
39
+ assert.equal(calls.length,2);
40
+ for(const call of calls){
41
+ assert.equal(call.file,TRUSTED_POWERSHELL);
42
+ assert.equal(call.options.env.SystemRoot,'C:\\Windows');
43
+ assert.equal(call.options.env.WINDIR,'C:\\Windows');
44
+ }
45
+ }finally{
46
+ childProcess.spawnSync=originalSpawn;
47
+ syncBuiltinESMExports();
48
+ if(originalRoot===undefined)delete process.env.SystemRoot;
49
+ else process.env.SystemRoot=originalRoot;
50
+ if(originalWindir===undefined)delete process.env.WINDIR;
51
+ else process.env.WINDIR=originalWindir;
52
+ }
53
+ });