chainlesschain 0.166.93 → 0.166.94

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 (167) hide show
  1. package/README.md +16 -7
  2. package/package.json +1 -1
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/assets/web-panel/assets/{AIOps-oo9rE0gh.js → AIOps-CzxfrB7_.js} +1 -1
  6. package/src/assets/web-panel/assets/{ActionButton-CVJVVv4i.js → ActionButton-6a4WFIb0.js} +1 -1
  7. package/src/assets/web-panel/assets/{Analytics-CUXuwmia.js → Analytics-O3Hv5QVR.js} +3 -3
  8. package/src/assets/web-panel/assets/{AppLayout-iM4PvCuY.js → AppLayout-D96Jtyrz.js} +5 -5
  9. package/src/assets/web-panel/assets/{Artifacts-DKUIJbf9.js → Artifacts-BxbCrGsv.js} +1 -1
  10. package/src/assets/web-panel/assets/{Audit-C4_SzFN2.js → Audit-Dvhj3ev4.js} +1 -1
  11. package/src/assets/web-panel/assets/{BackgroundAgents-C3427uy-.js → BackgroundAgents-zgrU13eK.js} +1 -1
  12. package/src/assets/web-panel/assets/{Backup-BpEQQiog.js → Backup-B3ePsQ8z.js} +1 -1
  13. package/src/assets/web-panel/assets/{BaseInput-C_exQomZ.js → BaseInput-CemgxVZi.js} +1 -1
  14. package/src/assets/web-panel/assets/{Chat-DzpkLtWa.js → Chat-BNxuigZo.js} +4 -4
  15. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DX4XmTWm.js → ChatBubbleRenderer-DE73IMLf.js} +1 -1
  16. package/src/assets/web-panel/assets/{Checkbox-CxAC1CU8.js → Checkbox-BvHaZkOM.js} +1 -1
  17. package/src/assets/web-panel/assets/{Codegen-wDFAGCLp.js → Codegen--DNXNLnq.js} +1 -1
  18. package/src/assets/web-panel/assets/{Col-BvwirhMf.js → Col-C8xvHu-3.js} +1 -1
  19. package/src/assets/web-panel/assets/{Community-BJuA7paY.js → Community-v19Cxqtl.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compact-DzV35_u2.js → Compact-BdQ659sW.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compliance-CIDPPvr9.js → Compliance-CE0-97Ph.js} +1 -1
  22. package/src/assets/web-panel/assets/{Cowork-DyTMxP3N.js → Cowork-YCuhzIZE.js} +3 -3
  23. package/src/assets/web-panel/assets/{Cron-PcNLjMFx.js → Cron-C5xa6iEB.js} +2 -2
  24. package/src/assets/web-panel/assets/{Crosschain-B_O7ck6Z.js → Crosschain-CmsyQp8f.js} +1 -1
  25. package/src/assets/web-panel/assets/{DID-CII35Me1.js → DID-FY-oN652.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dashboard-DU56fkNz.js → Dashboard-CFJD_yB7.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dropdown-DHC8HVeJ.js → Dropdown-QM5qyalT.js} +1 -1
  28. package/src/assets/web-panel/assets/{EmailListRenderer-oUOHFNsD.js → EmailListRenderer-BbA-SPX0.js} +1 -1
  29. package/src/assets/web-panel/assets/{EvolutionSettings-D7RCAWNl.js → EvolutionSettings-BGH3oR71.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CGONPZxC.js → FamilyGuardDashboard-C4s2LeR4.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-BbDlPWcl.js → Federation-Cfb8190f.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-BBJbFQQT.js → FormItemContext-DFVYcD2A.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-iuxAero4.js → GenericCardRenderer-CU-yqT1M.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-Dw0prdY5.js → Git-tYiQuWYO.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-DOGqi_uV.js → Governance-DRH8tKKy.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-BY5oUGGj.js → Inference-v2GbRYb4.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-CcE2EVYX.js → KnowledgeGraph-B5Ff0wSA.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-DyfN4R5w.js → Logs-mcHQsQZC.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-rk-aXvLV.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-CuuuAXUh.js → Marketplace-LUstH49t.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-948Fithv.js → McpTools-kFFP54H9.js} +2 -2
  42. package/src/assets/web-panel/assets/{Memory-D4PINvyy.js → Memory--KBUL4b_.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-ECKMaLzX.js → MobileBridge-CQL_xVyD.js} +1 -1
  44. package/src/assets/web-panel/assets/MobileProjects-DoIR8HA0.js +1 -0
  45. package/src/assets/web-panel/assets/{Mtc-BRzIM3HJ.js → Mtc-BrUPLX-C.js} +6 -6
  46. package/src/assets/web-panel/assets/{MtcAudit-CGeAXJGn.js → MtcAudit-46jjW0Ne.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-C_qJ-8K-.js → Multisig-3XX8crha.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DAEyCn7T.js → NLProgramming-C53SRk52.js} +1 -1
  49. package/src/assets/web-panel/assets/Notes-RxpNEOb6.js +7 -0
  50. package/src/assets/web-panel/assets/{NotificationSettings-Cv4Q3VC9.js → NotificationSettings-C2R6A0rD.js} +1 -1
  51. package/src/assets/web-panel/assets/OrderTableRenderer-Bhhp4H-K.js +1 -0
  52. package/src/assets/web-panel/assets/{Organization-DqAFdz6p.js → Organization-B48YmG99.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-8Ye9cIcE.js → Overflow-BYPoJ7Qo.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-755SfHoJ.js → P2P-CI9F_5Xz.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-CgZLiucX.js → PdhVaultBrowser-C50vLFTe.js} +4 -4
  56. package/src/assets/web-panel/assets/{Permissions-C-vFF_rL.js → Permissions-CKMhcV4D.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-Diz1oXM3.js → PersonalDataHub-CFwVht-l.js} +5 -5
  58. package/src/assets/web-panel/assets/{Pipeline-D4kIzsZG.js → Pipeline-BQliRCrn.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-QKLkSAUH.js → Privacy-z-9F-MIF.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-I0S9o_RT.js → ProjectInit-DAuaSe48.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-DdgV3-Ho.js → ProjectSettings-BpCCZLAx.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-BJHHegtj.js → Projects-CguDJz4k.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-uVa1zfbS.js → Providers-DOXv8vOy.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-YLVpOChR.js → QrScannerModal-BiMsPToH.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-535AYH61.js → QuickAsk-Dxjfq39w.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-C_P6eNSw.js → Recommend-Y5kQ_a2f.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-EXH1SbNj.js → RemoteSession-BSIDk0es.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-Cxc7d3eU.js → Reputation-BLjwaybi.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-EDqJy-wS.js → Row-7geSc2GW.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-C03Xeg9_.js → RssFeed-BophPGWk.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-DsIga9DK.js → Search-iBXsRvIQ.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-DgArMFc-.js → Security-sM5aX0dn.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-MooDpHBP.js → Services-8n708_pG.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-B10P1Dr6.js → Skeleton-D2rl_ikr.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-DHLHEkLl.js → Skills-DJpknGnz.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-D4RT7d4g.js → Sla-mA8eEXRj.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-D8u6F-LV.js → SpeechSettings-BVrmiTgd.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-4nvNzRq8.js → SyncSettings-Cd-KRX72.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CmCb8zNO.js → Tasks-DYAvmen_.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-CzESIa21.js → Templates-DPdlYX7S.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-Ch_N3p4d.js → Tenant-wfSNgGDT.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BZccUf_X.js → Terminal-BqJDIQEC.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-FrKcNy_3.js → TimelineRenderer-eeuMKufC.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-4xvLpXfD.js → Tokens-DW9xE5CY.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-84UjyxXd.js → Trigger-BBniBnCp.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-M6UtHnji.js → Trust-C7fZAxYI.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-L_xEgtCg.js → UkeySign-X_cT1LN_.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-BLJCX9ru.js → VideoEditing-C95qSEAg.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-DwL6uJCj.js → Wallet-_Y76V1eI.js} +3 -3
  90. package/src/assets/web-panel/assets/{WebAuthn-Bmn9pdzz.js → WebAuthn-CUpi7QWI.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-DSsZ5xG1.js → WorkflowEditor-CdTwu4JZ.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat--FGNUaa8.js → chat-D2hW7s3x.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-DeuwNdqX.js → colors-Cxes7tn4.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-I4XKxRFU.js → compact-item-BuLnefA8.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-RoD2p5Ba.js → createContext-DSJr7jrZ.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-BHLx7Ctv.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-BsvyddUM.js → hasIn-CPtCYTi-.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-jaHeti4i.js → index--J0mLXrw.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-BrZPb0On.js → index--gm5GIKK.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CesTu01s.js → index-5Vtfy09g.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DYOpAS6-.js → index-B5GXCqB0.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CPS3zv1c.js → index-BC-FDx0Y.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Wco_uvRo.js → index-BDaNLJss.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-DXnpVGG3.js → index-BOKMxs4C.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CBazK4-7.js → index-BOXgWdJY.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DWUmhNO-.js → index-BSHkOFqM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-ZgFED_Zd.js → index-B_mChW9O.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-D4IZsKbF.js → index-Bq-CMI2h.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-vRiGKIP0.js → index-BudaM8zA.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-R4St_zjR.js → index-C2qdbxEw.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D4P4vpmv.js → index-C6jDeixf.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D1ONyBTW.js → index-C88mhge1.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-UWdT_lty.js → index-C8xMmdFz.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CiHlSuMl.js → index-CCI2rS5G.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B8-eGLN9.js → index-CFTO4avR.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-VAlor744.js → index-CVgzXcvk.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-D6J8dz7X.js → index-C_RLdbKl.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BnrL4s-3.js → index-CighFQgt.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DKUCl_Lk.js → index-CmV_i-tY.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DiH-3cR2.js → index-Ct9hZUrk.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-4xHc2WjT.js → index-D4YILZTK.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-ScgoDU9V.js → index-DDSmCL8t.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BS_uqSTa.js → index-DFSJeXBu.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DRluW3_r.js → index-DGIumCxN.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-0tRU1Sv-.js → index-DIkV-pwg.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BYEVBlCl.js → index-DTtVX2yD.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CdgKZYgm.js → index-Dz6h62uC.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DlEfwgzr.js → index-PyLgXmQZ.js} +1 -1
  129. package/src/assets/web-panel/assets/index-V4IYxvvE.js +1 -0
  130. package/src/assets/web-panel/assets/{index-DChy2sHE.js → index-_AYMw6sF.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-rJDabtw4.js → index-ePTIQg1n.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CSrZiiAs.js → index-f1fHG0MC.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CsLVac9n.js → index-lOl7QbP4.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BcBM065G.js → index-vsUYvuDU.js} +1 -1
  135. package/src/assets/web-panel/assets/index-y4vqXg0S.js +1 -0
  136. package/src/assets/web-panel/assets/{index-CVwZMt9Z.js → index-ystQEkV4.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-BHT0PacY.js → initDefaultProps-CAxtYUKo.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-CBYsMtsC.js → motion-CgPmr4vy.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-DnHfFV4K.js → move-XGK3_B35.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-DfGkRYrx.js → mtc-parser-C6Vfqz8e.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-SjLcPQA9.js → omit-DquSN-3H.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-CCZHF4ep.js → pickAttrs-BH-ir5eD.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DjCGn2sJ.js → placementArrow-Drf-QXk8.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-BCFCl2on.js → responsiveObserve-BUBMbJsH.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-DsfmuIZu.js → slide-BCqinMKC.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-hPKuJUia.js → statusUtils-CAp3-0Ss.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-DOXKH7Kf.js → styleChecker-DIuQh0dB.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-B_G7yqio.js → useFlexGapSupport-CWXPAfZR.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-BXOTOm2l.js → useFs-905ulWWY.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-CRvie3g7.js → usePersonalDataHub-DajnovG6.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-D9_zMmww.js → vnode-BE_Yg3pz.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-C4tW03qI.js → zoom-BLndEna7.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/data/changelog.json +13 -0
  155. package/src/lib/process-execution-broker/windows-native-evaluator.js +247 -28
  156. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  157. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  158. package/src/lib/process-execution-broker/windows-sandbox.cs +140 -6
  159. package/src/lib/settings-loader.cjs +10 -1
  160. package/src/lib/with-file-lock.js +86 -16
  161. package/src/assets/web-panel/assets/MarkdownRenderer-Bj62Aq0M.js +0 -1
  162. package/src/assets/web-panel/assets/MobileProjects-CB5MAcvD.js +0 -1
  163. package/src/assets/web-panel/assets/Notes-DmD3MEec.js +0 -7
  164. package/src/assets/web-panel/assets/OrderTableRenderer-BNlpaFjH.js +0 -1
  165. package/src/assets/web-panel/assets/devWarning-ctnml4IW.js +0 -1
  166. package/src/assets/web-panel/assets/index-C7PN8mh1.js +0 -1
  167. package/src/assets/web-panel/assets/index-KnbBJAO-.js +0 -1
@@ -8,9 +8,77 @@ const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
8
8
  const MAX_FILE_BYTES = 1024 * 1024;
9
9
  const MAX_TOTAL_BYTES = 8 * MAX_FILE_BYTES;
10
10
  const MAX_FILES = 64;
11
+ const V1_LIMITS = Object.freeze({
12
+ version: 1,
13
+ files: MAX_FILES,
14
+ fileBytes: MAX_FILE_BYTES,
15
+ totalBytes: MAX_TOTAL_BYTES,
16
+ directories: 128,
17
+ pathBytes: 180,
18
+ depth: 8,
19
+ });
20
+ const V2_LIMITS = Object.freeze({
21
+ version: 2,
22
+ files: 20000,
23
+ fileBytes: 32 * MAX_FILE_BYTES,
24
+ totalBytes: 512 * MAX_FILE_BYTES,
25
+ directories: 20000,
26
+ pathBytes: 1024,
27
+ depth: 32,
28
+ });
11
29
  function requireCondition(condition, message) {
12
30
  if (!condition) throw new Error(`Windows native evaluator: ${message}`);
13
31
  }
32
+ function readBoundedBytes(file, maxBytes) {
33
+ const named = fs.lstatSync(file, { bigint: true });
34
+ requireCondition(
35
+ named.isFile() &&
36
+ !named.isSymbolicLink() &&
37
+ named.nlink === 1n &&
38
+ named.size <= BigInt(maxBytes),
39
+ "plain bounded file required",
40
+ );
41
+ const fd = fs.openSync(file, fs.constants.O_RDONLY);
42
+ try {
43
+ const before = fs.fstatSync(fd, { bigint: true });
44
+ requireCondition(
45
+ before.isFile() && before.nlink === 1n && before.size <= BigInt(maxBytes),
46
+ "opened file exceeds byte limit or is linked",
47
+ );
48
+ const bytes = Buffer.alloc(Number(before.size) + 1);
49
+ let count = 0;
50
+ while (count < bytes.length) {
51
+ const read = fs.readSync(fd, bytes, count, bytes.length - count, count);
52
+ if (!read) break;
53
+ count += read;
54
+ }
55
+ const after = fs.fstatSync(fd, { bigint: true });
56
+ const current = fs.lstatSync(file, { bigint: true });
57
+ const reopened = fs.openSync(file, fs.constants.O_RDONLY);
58
+ let second;
59
+ try {
60
+ second = fs.fstatSync(reopened, { bigint: true });
61
+ } finally {
62
+ fs.closeSync(reopened);
63
+ }
64
+ const same = (left, right) =>
65
+ ["dev", "ino", "size", "mtimeNs", "ctimeNs", "nlink"].every(
66
+ (key) => left[key] === right[key],
67
+ );
68
+ // Compare pathname identities and descriptor identities in their own
69
+ // domains; on Windows their inode representations can differ.
70
+ requireCondition(
71
+ count === Number(before.size) &&
72
+ same(named, current) &&
73
+ same(before, after) &&
74
+ same(after, second),
75
+ "file changed during bounded capture",
76
+ );
77
+ return bytes.subarray(0, count);
78
+ } finally {
79
+ fs.closeSync(fd);
80
+ }
81
+ }
14
82
  function identity(file, directory = false, maxBytes = MAX_FILE_BYTES) {
15
83
  const stat = fs.lstatSync(file, { bigint: true });
16
84
  requireCondition(
@@ -32,20 +100,28 @@ function identity(file, directory = false, maxBytes = MAX_FILE_BYTES) {
32
100
  ino: String(stat.ino),
33
101
  ...(directory
34
102
  ? {}
35
- : { bytes: Number(stat.size), sha256: hash(fs.readFileSync(file)) }),
103
+ : {
104
+ bytes: Number(stat.size),
105
+ sha256: hash(readBoundedBytes(file, maxBytes)),
106
+ }),
36
107
  };
37
108
  }
38
- function relativeFile(value) {
109
+ function relativeFile(value, limits = V1_LIMITS) {
39
110
  requireCondition(
40
- typeof value === "string" && value.length <= 180,
111
+ typeof value === "string" && value.length <= limits.pathBytes,
41
112
  "invalid relative file",
42
113
  );
43
114
  const parts = value.split("/");
44
115
  requireCondition(
45
- parts.length <= 8 &&
116
+ parts.length <= limits.depth &&
46
117
  parts.every(
47
118
  (part) =>
48
- /^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/u.test(part) &&
119
+ (limits.version === 1
120
+ ? /^[A-Za-z0-9_-][A-Za-z0-9_.-]*$/u
121
+ : /^(?!\.{1,2}$)[^\\:*?<>|"]+$/u
122
+ ).test(part) &&
123
+ [...part].every((character) => character.charCodeAt(0) >= 32) &&
124
+ !part.endsWith(" ") &&
49
125
  !part.endsWith(".") &&
50
126
  !/^(?:con|prn|aux|nul|com[0-9]|lpt[0-9])(?:\.|$)/iu.test(part),
51
127
  ),
@@ -53,17 +129,20 @@ function relativeFile(value) {
53
129
  );
54
130
  return parts;
55
131
  }
56
- function snapshotFile(root, relative) {
57
- const parts = relativeFile(relative);
132
+ function snapshotFile(root, relative, limits = V1_LIMITS) {
133
+ const parts = relativeFile(relative, limits);
58
134
  let current = root;
59
135
  for (const [index, part] of parts.entries()) {
60
136
  current = path.join(current, part);
61
- identity(current, index < parts.length - 1);
137
+ identity(current, index < parts.length - 1, limits.fileBytes);
62
138
  }
63
- const before = identity(current);
64
- requireCondition(before.bytes <= MAX_FILE_BYTES, "source file exceeds 1 MiB");
65
- const bytes = fs.readFileSync(current);
66
- const after = identity(current);
139
+ const before = identity(current, false, limits.fileBytes);
140
+ requireCondition(
141
+ before.bytes <= limits.fileBytes,
142
+ "source file exceeds byte limit",
143
+ );
144
+ const bytes = readBoundedBytes(current, limits.fileBytes);
145
+ const after = identity(current, false, limits.fileBytes);
67
146
  requireCondition(
68
147
  JSON.stringify(before) === JSON.stringify(after) &&
69
148
  hash(bytes) === before.sha256,
@@ -77,6 +156,84 @@ function snapshotFile(root, relative) {
77
156
  * Source bytes and the trusted evaluator are copied before any policy exists.
78
157
  */
79
158
  export function createWindowsNativeEvaluator(options) {
159
+ return createEvaluator(options, V1_LIMITS);
160
+ }
161
+
162
+ // Experimental Null transport only; never an admission capability.
163
+ export function createWindowsNativeNullEvaluator(options) {
164
+ requireCondition(
165
+ process.arch === "x64",
166
+ "experimental Null requires Windows x64",
167
+ );
168
+ return createEvaluator(
169
+ options,
170
+ V1_LIMITS,
171
+ null,
172
+ "chainlesschain/windows-node-runtime-adapter@3",
173
+ );
174
+ }
175
+
176
+ /** Explicit v2 capsule transport. Snapshots bind every copied byte; these
177
+ * bindings never grant review admission, registry trust, or model access.
178
+ * v1's size/identity/one-shot contract remains independent.
179
+ */
180
+ export function createWindowsNativeCapsuleEvaluator(options) {
181
+ requireCondition(
182
+ options && Array.isArray(options.snapshots),
183
+ "capsule snapshots required",
184
+ );
185
+ const { snapshots, binding, ...base } = options;
186
+ requireCondition(
187
+ base.files === undefined,
188
+ "capsule paths must come from snapshots",
189
+ );
190
+ requireCondition(
191
+ binding &&
192
+ Object.keys(binding).sort().join(",") ===
193
+ "inventoryDigest,lockDigest,planDigest,projectCommit,runtime",
194
+ "capsule binding incomplete",
195
+ );
196
+ for (const key of ["inventoryDigest", "lockDigest", "planDigest"])
197
+ requireCondition(
198
+ /^sha256:[a-f0-9]{64}$/u.test(binding[key] || ""),
199
+ "capsule digest invalid",
200
+ );
201
+ requireCondition(
202
+ /^[a-f0-9]{40}$/u.test(binding.projectCommit || ""),
203
+ "capsule project identity invalid",
204
+ );
205
+ const runtime = binding.runtime;
206
+ requireCondition(
207
+ runtime &&
208
+ runtime.platform === process.platform &&
209
+ runtime.architecture === process.arch &&
210
+ runtime.nodeVersion === process.version &&
211
+ runtime.modulesAbi === process.versions.modules &&
212
+ /^sha256:[a-f0-9]{64}$/u.test(runtime.executableDigest || ""),
213
+ "capsule runtime binding differs",
214
+ );
215
+ for (const snapshot of snapshots)
216
+ requireCondition(
217
+ snapshot &&
218
+ Number.isSafeInteger(snapshot.bytes) &&
219
+ snapshot.bytes >= 0 &&
220
+ snapshot.bytes <= V2_LIMITS.fileBytes &&
221
+ /^sha256:[a-f0-9]{64}$/u.test(snapshot.digest || ""),
222
+ "capsule file binding invalid",
223
+ );
224
+ return createEvaluator(
225
+ { ...base, files: snapshots.map((item) => item.path) },
226
+ V2_LIMITS,
227
+ { snapshots, binding },
228
+ );
229
+ }
230
+
231
+ function createEvaluator(
232
+ options,
233
+ limits,
234
+ capsule = null,
235
+ experimentalNullDeviceProfile = null,
236
+ ) {
80
237
  requireCondition(process.platform === "win32", "Windows host required");
81
238
  requireCondition(
82
239
  options &&
@@ -93,8 +250,8 @@ export function createWindowsNativeEvaluator(options) {
93
250
  "invalid wall-time limit",
94
251
  );
95
252
  requireCondition(
96
- Array.isArray(files) && files.length > 0 && files.length <= MAX_FILES,
97
- "expected 1..64 source files",
253
+ Array.isArray(files) && files.length > 0 && files.length <= limits.files,
254
+ `expected 1..${limits.files} source files`,
98
255
  );
99
256
  requireCondition(
100
257
  new Set(files.map((file) => String(file).toLowerCase())).size ===
@@ -109,31 +266,68 @@ export function createWindowsNativeEvaluator(options) {
109
266
  );
110
267
  const source = path.resolve(sourceRoot);
111
268
  identity(source, true);
112
- const captures = files.map((relative) => ({
113
- relative,
114
- bytes: snapshotFile(source, relative),
115
- }));
269
+ const captures = [];
270
+ let capturedBytes = Buffer.byteLength(checkSource);
271
+ for (const relative of files) {
272
+ const parts = relativeFile(relative, limits);
273
+ let file = source;
274
+ for (const [index, part] of parts.entries()) {
275
+ file = path.join(file, part);
276
+ identity(file, index < parts.length - 1, limits.fileBytes);
277
+ }
278
+ const descriptor = identity(file, false, limits.fileBytes);
279
+ requireCondition(
280
+ capturedBytes + descriptor.bytes <= limits.totalBytes,
281
+ "stage exceeds byte limit",
282
+ );
283
+ const bytes = snapshotFile(source, relative, limits);
284
+ requireCondition(
285
+ bytes.length === descriptor.bytes && hash(bytes) === descriptor.sha256,
286
+ "source changed before capture",
287
+ );
288
+ capturedBytes += bytes.length;
289
+ captures.push({ relative, bytes });
290
+ }
291
+ if (capsule)
292
+ for (const [index, capture] of captures.entries())
293
+ requireCondition(
294
+ capture.bytes.length === capsule.snapshots[index].bytes &&
295
+ `sha256:${hash(capture.bytes)}` === capsule.snapshots[index].digest,
296
+ "capsule source changed after inventory",
297
+ );
116
298
  const directoryNames = new Set(["", "workspace", "control", "scratch"]);
117
299
  for (const file of files) {
118
- const parts = relativeFile(file);
300
+ const parts = relativeFile(file, limits);
119
301
  for (let depth = 1; depth < parts.length; depth++)
120
302
  directoryNames.add(`workspace/${parts.slice(0, depth).join("/")}`);
121
303
  }
122
- requireCondition(directoryNames.size <= 128, "too many stage directories");
304
+ requireCondition(
305
+ directoryNames.size <= limits.directories,
306
+ "too many stage directories",
307
+ );
123
308
  requireCondition(
124
309
  captures.reduce(
125
310
  (total, file) => total + file.bytes.length,
126
311
  Buffer.byteLength(checkSource),
127
- ) <= MAX_TOTAL_BYTES,
128
- "stage exceeds 8 MiB",
312
+ ) <= limits.totalBytes,
313
+ "stage exceeds byte limit",
129
314
  );
130
315
  const sourceRuntime = identity(
131
316
  fs.realpathSync.native(process.execPath),
132
317
  false,
133
318
  128 * MAX_FILE_BYTES,
134
319
  );
320
+ if (capsule)
321
+ requireCondition(
322
+ `sha256:${sourceRuntime.sha256}` ===
323
+ capsule.binding.runtime.executableDigest,
324
+ "runtime bytes changed after inventory",
325
+ );
135
326
  const root = fs.mkdtempSync(
136
- path.join(fs.realpathSync.native(os.tmpdir()), "cc-native-evaluator-"),
327
+ path.join(
328
+ fs.realpathSync.native(os.tmpdir()),
329
+ limits.version === 1 ? "cc-native-evaluator-" : "cc-native-capsule-",
330
+ ),
137
331
  );
138
332
  const directories = new Set([root]);
139
333
  const ensure = (directory) => {
@@ -158,17 +352,32 @@ export function createWindowsNativeEvaluator(options) {
158
352
  );
159
353
  const stagedFiles = [];
160
354
  for (const capture of captures) {
161
- const target = path.join(workspace, ...relativeFile(capture.relative));
355
+ const target = path.join(
356
+ workspace,
357
+ ...relativeFile(capture.relative, limits),
358
+ );
162
359
  ensure(path.dirname(target));
163
360
  fs.writeFileSync(target, capture.bytes, { flag: "wx", mode: 0o600 });
164
- stagedFiles.push(identity(target));
361
+ stagedFiles.push(identity(target, false, limits.fileBytes));
165
362
  }
166
363
  const check = path.join(control, "check.cjs");
167
364
  fs.writeFileSync(check, checkSource, { flag: "wx", mode: 0o600 });
168
365
  stagedFiles.push(identity(check));
169
- requireCondition(directories.size <= 128, "too many stage directories");
366
+ requireCondition(
367
+ directories.size <= limits.directories,
368
+ "too many stage directories",
369
+ );
170
370
  const manifest = Object.freeze({
171
- version: 1,
371
+ version: limits.version,
372
+ ...(capsule
373
+ ? {
374
+ capsuleBinding: Object.freeze({
375
+ ...capsule.binding,
376
+ runtime: Object.freeze({ ...capsule.binding.runtime }),
377
+ }),
378
+ }
379
+ : {}),
380
+ ...(experimentalNullDeviceProfile ? { experimentalNullDeviceProfile } : {}),
172
381
  root,
173
382
  workspace,
174
383
  control,
@@ -183,9 +392,19 @@ export function createWindowsNativeEvaluator(options) {
183
392
  ),
184
393
  files: Object.freeze(stagedFiles.map(Object.freeze)),
185
394
  });
395
+ // The existing byte-loaded helper transports an integrity-bound invocation
396
+ // file (8 MiB), never an expanded command line. Reserve space for Base64 and
397
+ // launch metadata, and reject oversize before any policy can be issued.
398
+ requireCondition(
399
+ Buffer.byteLength(JSON.stringify(manifest)) <= 4 * MAX_FILE_BYTES,
400
+ "native manifest exceeds transport bound",
401
+ );
186
402
  const manifestDigest = hash(JSON.stringify(manifest));
187
403
  const token = Object.freeze({
188
- kind: "windows-native-evaluator-v1",
404
+ kind:
405
+ limits.version === 1
406
+ ? "windows-native-evaluator-v1"
407
+ : "windows-native-capsule-v2",
189
408
  manifestDigest,
190
409
  });
191
410
  issued.set(token, { manifest, manifestDigest });
@@ -484,6 +484,64 @@ namespace ChainlessChain.WindowsSandbox
484
484
  [DllImport("kernel32.dll", SetLastError = true)]
485
485
  private static extern UInt32 GetFileType(IntPtr handle);
486
486
 
487
+ [DllImport("ntdll.dll")]
488
+ private static extern Int32 NtQueryObject(IntPtr handle, Int32 informationClass,
489
+ IntPtr information, UInt32 size, out UInt32 returned);
490
+
491
+ [DllImport("ntdll.dll", EntryPoint = "NtQueryInformationFile")]
492
+ private static extern Int32 NtQueryFileMode(IntPtr handle, out IO_STATUS_BLOCK status,
493
+ out UInt32 mode, UInt32 size, Int32 informationClass);
494
+
495
+ // This path is reachable only for the separately gated experimental v3.
496
+ // Kernel object identity, exact rights, and synchronous mode are proved
497
+ // before either extra handle is placed in the root inheritance list.
498
+ private static void VerifyNullObjectString(IntPtr handle, int kind, string expected)
499
+ {
500
+ IntPtr buffer = Marshal.AllocHGlobal(4096);
501
+ try
502
+ {
503
+ UInt32 returned;
504
+ if (NtQueryObject(handle, kind, buffer, 4096, out returned) != 0 || returned > 4096)
505
+ throw new InvalidDataException("Null object query failed");
506
+ int length = (UInt16)Marshal.ReadInt16(buffer);
507
+ int maximum = (UInt16)Marshal.ReadInt16(buffer, 2);
508
+ IntPtr value = Marshal.ReadIntPtr(buffer, IntPtr.Size == 8 ? 8 : 4);
509
+ long offset = value.ToInt64() - buffer.ToInt64();
510
+ if (length != expected.Length * 2 || maximum < length || offset < IntPtr.Size * 2 ||
511
+ offset > 4096 - maximum || Marshal.PtrToStringUni(value, length / 2) != expected)
512
+ throw new InvalidDataException("Null object identity differs");
513
+ }
514
+ finally { Marshal.FreeHGlobal(buffer); }
515
+ }
516
+
517
+ private static IntPtr PrepareExperimentalNull(UInt32 access, List<IntPtr> owned)
518
+ {
519
+ SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES();
520
+ attributes.nLength = checked((UInt32)Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES)));
521
+ attributes.bInheritHandle = true;
522
+ IntPtr handle = CreateFile("NUL", access, FILE_SHARE_READ | FILE_SHARE_WRITE,
523
+ ref attributes, OPEN_EXISTING, 0, IntPtr.Zero);
524
+ if (IsInvalidHandle(handle)) ThrowLastError("CreateFile(experimental Null)");
525
+ owned.Add(handle);
526
+ if (GetFileType(handle) != 2) throw new InvalidDataException("Null is not a character device");
527
+ VerifyNullObjectString(handle, 1, "\\Device\\Null");
528
+ VerifyNullObjectString(handle, 2, "File");
529
+ IntPtr basic = Marshal.AllocHGlobal(256);
530
+ try
531
+ {
532
+ UInt32 returned;
533
+ if (NtQueryObject(handle, 0, basic, 56, out returned) != 0 || returned < 8 || returned > 256 ||
534
+ unchecked((UInt32)Marshal.ReadInt32(basic, 4)) != access)
535
+ throw new InvalidDataException("Null granted access differs");
536
+ }
537
+ finally { Marshal.FreeHGlobal(basic); }
538
+ IO_STATUS_BLOCK status;
539
+ UInt32 mode;
540
+ if (NtQueryFileMode(handle, out status, out mode, 4, 16) != 0 || mode != 0x20)
541
+ throw new InvalidDataException("Null synchronous mode differs");
542
+ return handle;
543
+ }
544
+
487
545
  [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
488
546
  private static extern IntPtr CreateFile(
489
547
  string fileName,
@@ -3029,9 +3087,19 @@ namespace ChainlessChain.WindowsSandbox
3029
3087
  IntPtr standardError,
3030
3088
  int nodeIpcFd,
3031
3089
  IntPtr entrySnapshotHandle,
3090
+ IntPtr experimentalNullRead,
3091
+ IntPtr experimentalNullWrite,
3032
3092
  out IntPtr handleBytes)
3033
3093
  {
3034
3094
  List<IntPtr> handles = new List<IntPtr>();
3095
+ if (!IsInvalidHandle(experimentalNullRead) || !IsInvalidHandle(experimentalNullWrite))
3096
+ {
3097
+ if (IsInvalidHandle(experimentalNullRead) || IsInvalidHandle(experimentalNullWrite) ||
3098
+ experimentalNullRead == experimentalNullWrite)
3099
+ throw new InvalidDataException("Experimental Null pair is incomplete");
3100
+ handles.Add(experimentalNullRead);
3101
+ handles.Add(experimentalNullWrite);
3102
+ }
3035
3103
  handles.Add(standardInput);
3036
3104
  if (!handles.Contains(standardOutput))
3037
3105
  handles.Add(standardOutput);
@@ -3267,6 +3335,26 @@ namespace ChainlessChain.WindowsSandbox
3267
3335
  [DataMember] public string sha256 { get; set; }
3268
3336
  }
3269
3337
 
3338
+ [DataContract]
3339
+ public sealed class NativeCapsuleRuntimeSpec
3340
+ {
3341
+ [DataMember] public string platform { get; set; }
3342
+ [DataMember] public string architecture { get; set; }
3343
+ [DataMember] public string nodeVersion { get; set; }
3344
+ [DataMember] public string modulesAbi { get; set; }
3345
+ [DataMember] public string executableDigest { get; set; }
3346
+ }
3347
+
3348
+ [DataContract]
3349
+ public sealed class NativeCapsuleBindingSpec
3350
+ {
3351
+ [DataMember] public string inventoryDigest { get; set; }
3352
+ [DataMember] public string lockDigest { get; set; }
3353
+ [DataMember] public string planDigest { get; set; }
3354
+ [DataMember] public string projectCommit { get; set; }
3355
+ [DataMember] public NativeCapsuleRuntimeSpec runtime { get; set; }
3356
+ }
3357
+
3270
3358
  [DataContract]
3271
3359
  public sealed class NativeEvaluatorSpec
3272
3360
  {
@@ -3281,6 +3369,8 @@ namespace ChainlessChain.WindowsSandbox
3281
3369
  [DataMember] public EvaluatorPathSpec runtime { get; set; }
3282
3370
  [DataMember] public EvaluatorPathSpec[] directories { get; set; }
3283
3371
  [DataMember] public EvaluatorPathSpec[] files { get; set; }
3372
+ [DataMember] public NativeCapsuleBindingSpec capsuleBinding { get; set; }
3373
+ [DataMember] public string experimentalNullDeviceProfile { get; set; }
3284
3374
  }
3285
3375
 
3286
3376
  private static bool SameEvaluatorPath(string left, string right)
@@ -3328,15 +3418,39 @@ namespace ChainlessChain.WindowsSandbox
3328
3418
  string[] arguments, string workingDirectory, string appSid, int wallTimeMs,
3329
3419
  List<IntPtr> guards, ref bool rootAttested)
3330
3420
  {
3331
- if (spec.version != 1 || !IsLowercaseSha256(spec.manifestDigest) || spec.runtime == null ||
3332
- spec.directories == null || spec.directories.Length < 4 || spec.directories.Length > 128 ||
3333
- spec.files == null || spec.files.Length < 2 || spec.files.Length > 65 ||
3421
+ bool capsule = spec.version == 2;
3422
+ if (spec.experimentalNullDeviceProfile != null &&
3423
+ (capsule || spec.experimentalNullDeviceProfile != "chainlesschain/windows-node-runtime-adapter@3"))
3424
+ throw new InvalidDataException("Unsupported experimental Null profile");
3425
+ int maxDirectories = capsule ? 20000 : 128;
3426
+ int maxFiles = capsule ? 20001 : 65;
3427
+ long maxFileBytes = capsule ? 32L * 1024 * 1024 : 1024 * 1024;
3428
+ long maxTotalBytes = capsule ? 512L * 1024 * 1024 : 8 * 1024 * 1024;
3429
+ if ((spec.version != 1 && !capsule) || !IsLowercaseSha256(spec.manifestDigest) || spec.runtime == null ||
3430
+ spec.directories == null || spec.directories.Length < 4 || spec.directories.Length > maxDirectories ||
3431
+ spec.files == null || spec.files.Length < 2 || spec.files.Length > maxFiles ||
3334
3432
  wallTimeMs < 1 || spec.wallTimeMs != wallTimeMs || String.IsNullOrWhiteSpace(appSid))
3335
3433
  throw new InvalidDataException("Native evaluator manifest is incomplete");
3434
+ if (capsule)
3435
+ {
3436
+ NativeCapsuleBindingSpec binding = spec.capsuleBinding;
3437
+ if (binding == null || binding.runtime == null ||
3438
+ !IsPrefixedSha256(binding.inventoryDigest) || !IsPrefixedSha256(binding.lockDigest) ||
3439
+ !IsPrefixedSha256(binding.planDigest) ||
3440
+ !System.Text.RegularExpressions.Regex.IsMatch(binding.projectCommit ?? "", "^[a-f0-9]{40}$") ||
3441
+ binding.runtime.platform != "win32" ||
3442
+ (binding.runtime.architecture != "x64" && binding.runtime.architecture != "arm64" && binding.runtime.architecture != "ia32") ||
3443
+ !System.Text.RegularExpressions.Regex.IsMatch(binding.runtime.nodeVersion ?? "", "^v[0-9]+\\.[0-9]+\\.[0-9]+$") ||
3444
+ !System.Text.RegularExpressions.Regex.IsMatch(binding.runtime.modulesAbi ?? "", "^[0-9]+$") ||
3445
+ binding.runtime.executableDigest != "sha256:" + spec.runtime.sha256)
3446
+ throw new InvalidDataException("Native capsule inventory/runtime binding is incomplete");
3447
+ }
3448
+ else if (spec.capsuleBinding != null)
3449
+ throw new InvalidDataException("v1 evaluator cannot accept a capsule binding");
3336
3450
  string root = NormalizeLocalDosPath(spec.root, null, "Native evaluator stage");
3337
3451
  string expectedParent = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp");
3338
3452
  if (!SameEvaluatorPath(Path.GetDirectoryName(root), expectedParent) ||
3339
- !System.Text.RegularExpressions.Regex.IsMatch(Path.GetFileName(root), "^cc-native-evaluator-[A-Za-z0-9_-]{6}$") ||
3453
+ !System.Text.RegularExpressions.Regex.IsMatch(Path.GetFileName(root), capsule ? "^cc-native-capsule-[A-Za-z0-9_-]{6}$" : "^cc-native-evaluator-[A-Za-z0-9_-]{6}$") ||
3340
3454
  !SameEvaluatorPath(spec.workspace, Path.Combine(root, "workspace")) ||
3341
3455
  !SameEvaluatorPath(spec.control, Path.Combine(root, "control")) ||
3342
3456
  !SameEvaluatorPath(spec.scratch, Path.Combine(root, "scratch")) ||
@@ -3368,10 +3482,11 @@ namespace ChainlessChain.WindowsSandbox
3368
3482
  {
3369
3483
  string full = Path.GetFullPath(file.path);
3370
3484
  if ((!full.StartsWith(spec.workspace + "\\", StringComparison.OrdinalIgnoreCase) && !SameEvaluatorPath(full, spec.check)) ||
3371
- !expected.Add(full) || file.bytes < 0 || file.bytes > 1024 * 1024)
3485
+ !expected.Add(full) || file.bytes < 0 || file.bytes > maxFileBytes ||
3486
+ (SameEvaluatorPath(full, spec.check) && file.bytes > 1024 * 1024))
3372
3487
  throw new InvalidDataException("Native evaluator file is outside its read-only roots or exceeds bounds");
3373
3488
  total = checked(total + file.bytes);
3374
- if (total > 8 * 1024 * 1024) throw new InvalidDataException("Native evaluator tree exceeds byte bound");
3489
+ if (total > maxTotalBytes) throw new InvalidDataException("Native evaluator tree exceeds byte bound");
3375
3490
  VerifyEvaluatorPath(file, HoldEvaluatorPath(full, false, guards), false);
3376
3491
  }
3377
3492
  if (!expected.Contains(spec.check) || !expected.Contains(spec.workspace) || !expected.Contains(spec.control) || !expected.Contains(spec.scratch))
@@ -3419,6 +3534,12 @@ namespace ChainlessChain.WindowsSandbox
3419
3534
  SetEvaluatorAccessControl(spec.runtime.path, runtimeSecurity);
3420
3535
  }
3421
3536
 
3537
+ private static bool IsPrefixedSha256(string value)
3538
+ {
3539
+ return value != null && value.StartsWith("sha256:", StringComparison.Ordinal) &&
3540
+ IsLowercaseSha256(value.Substring(7));
3541
+ }
3542
+
3422
3543
  private static void SetEvaluatorAccessControl(string path, FileSystemSecurity security)
3423
3544
  {
3424
3545
  // Directory/File.SetAccessControl exist only in .NET Framework.
@@ -3582,6 +3703,7 @@ namespace ChainlessChain.WindowsSandbox
3582
3703
  int entrySnapshotFd = -1;
3583
3704
  byte[] entrySnapshot = null;
3584
3705
  List<IntPtr> ownedStandardHandles = new List<IntPtr>();
3706
+ IntPtr experimentalNullRead = IntPtr.Zero, experimentalNullWrite = IntPtr.Zero;
3585
3707
  List<LaunchPathLock> launchPathLocks = new List<LaunchPathLock>();
3586
3708
  PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
3587
3709
  bool useAppContainer =
@@ -3673,6 +3795,16 @@ namespace ChainlessChain.WindowsSandbox
3673
3795
  targetEnvironment["SystemRoot"] = Path.GetDirectoryName(Environment.SystemDirectory);
3674
3796
  targetEnvironment["WINDIR"] = Path.GetDirectoryName(Environment.SystemDirectory);
3675
3797
  targetEnvironment["PATH"] = Path.GetDirectoryName(application);
3798
+ // Only the guarded evaluator exposes this attested identity;
3799
+ // an inherited caller value is never used as its authority.
3800
+ targetEnvironment["CC_WINDOWS_APPCONTAINER_SID"] = expectedAppContainerSid;
3801
+ if (nativeEvaluator.experimentalNullDeviceProfile != null)
3802
+ {
3803
+ experimentalNullRead = PrepareExperimentalNull(0x120089, ownedStandardHandles);
3804
+ experimentalNullWrite = PrepareExperimentalNull(0x120196, ownedStandardHandles);
3805
+ targetEnvironment["CC_WINDOWS_NULL_READ_V3"] = experimentalNullRead.ToInt64().ToString("x");
3806
+ targetEnvironment["CC_WINDOWS_NULL_WRITE_V3"] = experimentalNullWrite.ToInt64().ToString("x");
3807
+ }
3676
3808
  ConfigureAppContainerEnvironment(targetEnvironment);
3677
3809
  }
3678
3810
  environmentBuffer =
@@ -3855,6 +3987,8 @@ namespace ChainlessChain.WindowsSandbox
3855
3987
  startup.StartupInfo.hStdError,
3856
3988
  nodeIpcFd,
3857
3989
  entrySnapshotReadHandle,
3990
+ experimentalNullRead,
3991
+ experimentalNullWrite,
3858
3992
  out inheritedHandleBytes);
3859
3993
  attributeList = BuildProcessAttributeList(
3860
3994
  inheritedHandleBuffer,
@@ -491,7 +491,16 @@ function addRule({
491
491
  committed = true;
492
492
  return { file, added: true };
493
493
  },
494
- { failIfUnavailable: true },
494
+ {
495
+ failIfUnavailable: true,
496
+ // Keep the existing two-second acquisition deadline. A writer doing
497
+ // several synchronous addRule calls must give existing contenders a
498
+ // retry window after each release instead of immediately reacquiring.
499
+ retryMs: 5,
500
+ maxRetryMs: 25,
501
+ retryJitterMs: 5,
502
+ yieldAfterReleaseMs: 32,
503
+ },
495
504
  );
496
505
  if (began) finishSettingsMutation("ready");
497
506
  return result;