chainlesschain 0.166.93 → 0.166.96
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/README.md +16 -5
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-oo9rE0gh.js → AIOps-TNYT0Ehu.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CVJVVv4i.js → ActionButton-GAU5WRiJ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CUXuwmia.js → Analytics-CGBIObIC.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-iM4PvCuY.js → AppLayout-oOSmcEm1.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DKUIJbf9.js → Artifacts-CmEZTNjU.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C4_SzFN2.js → Audit-Dbt3E2nA.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C3427uy-.js → BackgroundAgents-D_BZmYk4.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BpEQQiog.js → Backup-DgDOxdZF.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C_exQomZ.js → BaseInput-Bo2pg8D_.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DzpkLtWa.js → Chat-BF6Lb0W_.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BEmIX0R1.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CxAC1CU8.js → Checkbox-ZJM64PMU.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-wDFAGCLp.js → Codegen-CCauMpRn.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BvwirhMf.js → Col-CA_MU4xw.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BJuA7paY.js → Community-B5VQptTJ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DzV35_u2.js → Compact-C7Ehqru2.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CIDPPvr9.js → Compliance-Dyt6kfTg.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DyTMxP3N.js → Cowork-B_yOOOHo.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-PcNLjMFx.js → Cron-cbhAQANJ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B_O7ck6Z.js → Crosschain-BYC4ITZo.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CII35Me1.js → DID-Cc2Czj0c.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DU56fkNz.js → Dashboard-B4VX9dth.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DHC8HVeJ.js → Dropdown-g7e6T92p.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-oUOHFNsD.js → EmailListRenderer-CzUK_NJA.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-D7RCAWNl.js → EvolutionSettings-ameoB_GI.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CGONPZxC.js → FamilyGuardDashboard-DuehDwv2.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbDlPWcl.js → Federation-DuMbIjYG.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BBJbFQQT.js → FormItemContext-CS-om7RD.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-iuxAero4.js → GenericCardRenderer-CuKgo9Q0.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Dw0prdY5.js → Git-Bp6I9kxu.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DOGqi_uV.js → Governance-C_AzSOmj.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BY5oUGGj.js → Inference-C-qkoYD5.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CcE2EVYX.js → KnowledgeGraph-D8Q4D_EN.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DyfN4R5w.js → Logs-BzkfZQQF.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CaGz2EYV.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CuuuAXUh.js → Marketplace-BWFjqLRM.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-948Fithv.js → McpTools-BJngUzks.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-D4PINvyy.js → Memory-DcFAqj9b.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-ECKMaLzX.js → MobileBridge-CQjRXWWS.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Cvg9bcvQ.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BRzIM3HJ.js → Mtc-C_7mSz17.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-CGeAXJGn.js → MtcAudit-DcYdxIWN.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-C_qJ-8K-.js → Multisig-ypAr20wL.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DAEyCn7T.js → NLProgramming-CJWFA8xg.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DmD3MEec.js → Notes-D1GhvZvR.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cv4Q3VC9.js → NotificationSettings-CWOgdSHm.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CE5Oqf07.js +1 -0
- package/src/assets/web-panel/assets/{Organization-DqAFdz6p.js → Organization-wddxd4Ka.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-8Ye9cIcE.js → Overflow-Dczw5Ggs.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-755SfHoJ.js → P2P-Bmlos4fT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CgZLiucX.js → PdhVaultBrowser-DNp_WBr0.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-C-vFF_rL.js → Permissions-tt7tio9B.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-Diz1oXM3.js → PersonalDataHub-C_ItcMUf.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-D4kIzsZG.js → Pipeline-BS0NENVX.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-QKLkSAUH.js → Privacy-o9QNmXcZ.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-I0S9o_RT.js → ProjectInit-g3r30nEt.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DdgV3-Ho.js → ProjectSettings-CQCKz8vR.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BJHHegtj.js → Projects-DWH5cBmp.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-uVa1zfbS.js → Providers-KGyPcaEb.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-YLVpOChR.js → QrScannerModal-CFgEHopX.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-535AYH61.js → QuickAsk-hLl0Nel4.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-C_P6eNSw.js → Recommend-BX6FCAMS.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-EXH1SbNj.js → RemoteSession-C5BLLEcs.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Cxc7d3eU.js → Reputation-DVUhhjBy.js} +1 -1
- package/src/assets/web-panel/assets/{Row-EDqJy-wS.js → Row-Bx3-cvc4.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C03Xeg9_.js → RssFeed-DA091bsm.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DsIga9DK.js → Search-CJqXJFAx.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DgArMFc-.js → Security-BWZ5dHzy.js} +2 -2
- package/src/assets/web-panel/assets/{Services-MooDpHBP.js → Services-QhwMuZb3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-B10P1Dr6.js → Skeleton-DgbULoTC.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DHLHEkLl.js → Skills-cl7ldo6F.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D4RT7d4g.js → Sla-Cek1zXct.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-D8u6F-LV.js → SpeechSettings-D6iXwMpA.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-4nvNzRq8.js → SyncSettings-bC93x91L.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CmCb8zNO.js → Tasks-CcZOqgkI.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CzESIa21.js → Templates-BTq2AoyQ.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-Ch_N3p4d.js → Tenant-D7qU02Di.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BZccUf_X.js → Terminal-YZpeH0oN.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-FrKcNy_3.js → TimelineRenderer-DDg43Omm.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-4xvLpXfD.js → Tokens-D-RO6fo4.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-84UjyxXd.js → Trigger-TEnd7fo4.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-M6UtHnji.js → Trust-DVnK8TWT.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-L_xEgtCg.js → UkeySign-B8FmpNyk.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BLJCX9ru.js → VideoEditing-DxIs_fZY.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DwL6uJCj.js → Wallet-BOyELv3w.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Bmn9pdzz.js → WebAuthn-CIHCpt3j.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DSsZ5xG1.js → WorkflowEditor-Bz7cskus.js} +1 -1
- package/src/assets/web-panel/assets/{chat--FGNUaa8.js → chat-Dv8SddNl.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DeuwNdqX.js → colors-BEV5nkv4.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-I4XKxRFU.js → compact-item-9vwfkqFB.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-RoD2p5Ba.js → createContext-ent1l9xX.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DcHR2Rj6.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BsvyddUM.js → hasIn-Q8eEU5e7.js} +1 -1
- package/src/assets/web-panel/assets/{index-Wco_uvRo.js → index--G-igD6F.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVwZMt9Z.js → index-6eOIwKrl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CdgKZYgm.js → index-B7Gu0QHO.js} +1 -1
- package/src/assets/web-panel/assets/{index-ZgFED_Zd.js → index-BBTcn6Fs.js} +1 -1
- package/src/assets/web-panel/assets/{index-DiH-3cR2.js → index-BCfGsk6N.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXnpVGG3.js → index-BSa4k_WD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DChy2sHE.js → index-BUxM7YPq.js} +1 -1
- package/src/assets/web-panel/assets/index-BZYMgiHf.js +1 -0
- package/src/assets/web-panel/assets/{index-vRiGKIP0.js → index-Bbxhm1Ul.js} +1 -1
- package/src/assets/web-panel/assets/{index-CesTu01s.js → index-ByAsMed4.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1ONyBTW.js → index-CAdPA5MQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiHlSuMl.js → index-CArObCCc.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4IZsKbF.js → index-CdQo1l-5.js} +1 -1
- package/src/assets/web-panel/assets/{index-R4St_zjR.js → index-CfqytzM2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKUCl_Lk.js → index-ChNGj01l.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSrZiiAs.js → index-ClayrGw1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlEfwgzr.js → index-Cn3XO8jc.js} +1 -1
- package/src/assets/web-panel/assets/index-CnRInbAW.js +1 -0
- package/src/assets/web-panel/assets/{index-UWdT_lty.js → index-CusIdZ6c.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYEVBlCl.js → index-CxvqZN65.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnrL4s-3.js → index-D1zf7Wim.js} +1 -1
- package/src/assets/web-panel/assets/{index-BS_uqSTa.js → index-D6oYsDxS.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsLVac9n.js → index-DBdMxZA0.js} +1 -1
- package/src/assets/web-panel/assets/{index-ScgoDU9V.js → index-DLQRUzgc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcBM065G.js → index-De1Gm9FM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBazK4-7.js → index-DeXt9bwV.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8-eGLN9.js → index-Defz9gay.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPS3zv1c.js → index-DjhTxtXx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYOpAS6-.js → index-DtCVUwNB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWUmhNO-.js → index-Dx-mnFrM.js} +1 -1
- package/src/assets/web-panel/assets/{index-VAlor744.js → index-FySnx-ic.js} +1 -1
- package/src/assets/web-panel/assets/{index-0tRU1Sv-.js → index-IjNq0ymK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrZPb0On.js → index-JdyMJF72.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRluW3_r.js → index-XSnRyYOQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-4xHc2WjT.js → index-Zj25PNra.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4P4vpmv.js → index-bys1L9Zm.js} +1 -1
- package/src/assets/web-panel/assets/{index-jaHeti4i.js → index-hfXZTN5I.js} +3 -3
- package/src/assets/web-panel/assets/{index-rJDabtw4.js → index-qM_auh7p.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6J8dz7X.js → index-wlQxhwWL.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BHT0PacY.js → initDefaultProps-e8NWdeA7.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CBYsMtsC.js → motion-Dqj2qiL6.js} +1 -1
- package/src/assets/web-panel/assets/{move-DnHfFV4K.js → move-ucHeQCeV.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DfGkRYrx.js → mtc-parser-OY2ylE6m.js} +1 -1
- package/src/assets/web-panel/assets/{omit-SjLcPQA9.js → omit-M4uSoAew.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CCZHF4ep.js → pickAttrs-D2R8ck78.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DjCGn2sJ.js → placementArrow-CkAkyODp.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BCFCl2on.js → responsiveObserve-D5Z1gXYW.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DsfmuIZu.js → slide-DCLsPpEU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-hPKuJUia.js → statusUtils-COLrD1tR.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DOXKH7Kf.js → styleChecker-CyViVm6O.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-B_G7yqio.js → useFlexGapSupport-qUsgJGGb.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BXOTOm2l.js → useFs-BNGZrXAO.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRvie3g7.js → usePersonalDataHub-CI8d0Z8T.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-D9_zMmww.js → vnode-DuM7m61_.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C4tW03qI.js → zoom-SdK0_dHD.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +39 -0
- package/src/lib/model-catalog-review.js +52 -5
- package/src/lib/process-execution-broker/windows-native-evaluator.js +247 -28
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +140 -6
- package/src/lib/settings-loader.cjs +10 -1
- package/src/lib/with-file-lock.js +86 -16
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DX4XmTWm.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-Bj62Aq0M.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CB5MAcvD.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BNlpaFjH.js +0 -1
- package/src/assets/web-panel/assets/devWarning-ctnml4IW.js +0 -1
- package/src/assets/web-panel/assets/index-C7PN8mh1.js +0 -1
- 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
|
-
: {
|
|
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 <=
|
|
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 <=
|
|
116
|
+
parts.length <= limits.depth &&
|
|
46
117
|
parts.every(
|
|
47
118
|
(part) =>
|
|
48
|
-
|
|
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(
|
|
65
|
-
|
|
66
|
-
|
|
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 <=
|
|
97
|
-
|
|
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 =
|
|
113
|
-
|
|
114
|
-
|
|
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(
|
|
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
|
-
) <=
|
|
128
|
-
"stage exceeds
|
|
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(
|
|
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(
|
|
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(
|
|
366
|
+
requireCondition(
|
|
367
|
+
directories.size <= limits.directories,
|
|
368
|
+
"too many stage directories",
|
|
369
|
+
);
|
|
170
370
|
const manifest = Object.freeze({
|
|
171
|
-
version:
|
|
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:
|
|
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 });
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
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 >
|
|
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 >
|
|
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
|
-
{
|
|
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;
|