reeval 0.12.0 → 0.13.0
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 +115 -259
- package/assets/package.json +1 -1
- package/assets/pnpm-lock.yaml +41 -30
- package/dist/{adapter-DigeVFWC.js → adapter-IKKo4sZj.js} +3 -3
- package/dist/{adapter-DigeVFWC.js.map → adapter-IKKo4sZj.js.map} +1 -1
- package/dist/{agent-CatUEo5h.js → agent-ClE-g9VL.js} +5 -5
- package/dist/{agent-CatUEo5h.js.map → agent-ClE-g9VL.js.map} +1 -1
- package/dist/{analysis-Bxub9LMT.js → analysis-B0nOfAzA.js} +3 -3
- package/dist/{analysis-Bxub9LMT.js.map → analysis-B0nOfAzA.js.map} +1 -1
- package/dist/{assemble-CF063ms6.js → assemble-BO93VAk1.js} +12 -12
- package/dist/{assemble-CF063ms6.js.map → assemble-BO93VAk1.js.map} +1 -1
- package/dist/assemble-VQxyoDxK.js +1 -0
- package/dist/{attempt-CGPV3BZY.js → attempt-jI0OWxlz.js} +2 -2
- package/dist/{attempt-CGPV3BZY.js.map → attempt-jI0OWxlz.js.map} +1 -1
- package/dist/{capture-BiD7lVaM.js → capture-dn2hjSLj.js} +10 -10
- package/dist/{capture-BiD7lVaM.js.map → capture-dn2hjSLj.js.map} +1 -1
- package/dist/capture-nIJ-7kUE.js +1 -0
- package/dist/{cli-B5zQCR2r.js → cli-BCE1xlm4.js} +65 -54
- package/dist/{cli-B5zQCR2r.js.map → cli-BCE1xlm4.js.map} +1 -1
- package/dist/cli.js +2 -2
- package/dist/{command-DTgBZR0k.js → command-Bh3yhN_b.js} +8 -8
- package/dist/{command-DTgBZR0k.js.map → command-Bh3yhN_b.js.map} +1 -1
- package/dist/{command-C94CQNh8.js → command-C6vwmjS7.js} +10 -10
- package/dist/{command-C94CQNh8.js.map → command-C6vwmjS7.js.map} +1 -1
- package/dist/{command-CaZgCTIw.js → command-CANun0m0.js} +13 -13
- package/dist/command-CANun0m0.js.map +1 -0
- package/dist/{command-CVtgyqlM.js → command-CBaHukyl.js} +11 -11
- package/dist/{command-CVtgyqlM.js.map → command-CBaHukyl.js.map} +1 -1
- package/dist/{command-BMHXHqMT.js → command-D_kEnYcX.js} +14 -14
- package/dist/{command-BMHXHqMT.js.map → command-D_kEnYcX.js.map} +1 -1
- package/dist/{commands-CjpGBUWr.js → commands-7tH3EsVu.js} +9 -9
- package/dist/{commands-CjpGBUWr.js.map → commands-7tH3EsVu.js.map} +1 -1
- package/dist/{commands-BXzIDB3X.js → commands-BKGzBMgb.js} +1 -1
- package/dist/{config-Dv5MG7Rp.js → config-D7TFvUpL.js} +23 -17
- package/dist/config-D7TFvUpL.js.map +1 -0
- package/dist/{contrasts-CcoMLC_y.js → contrasts-B4nOhn0Z.js} +2 -2
- package/dist/{contrasts-CcoMLC_y.js.map → contrasts-B4nOhn0Z.js.map} +1 -1
- package/dist/{db-BeqRiWLl.js → db-DJUuFoWQ.js} +4 -4
- package/dist/{db-BeqRiWLl.js.map → db-DJUuFoWQ.js.map} +1 -1
- package/dist/db-W9EFlXnn.js +2 -0
- package/dist/{dist-CMjp3qqF.js → dist-Dp_AJgYN.js} +2 -2
- package/dist/{dist-CMjp3qqF.js.map → dist-Dp_AJgYN.js.map} +1 -1
- package/dist/{docker-CCXdbREk.js → docker-CQxucRPe.js} +7 -22
- package/dist/docker-CQxucRPe.js.map +1 -0
- package/dist/{docker-DC69dKxB.js → docker-Dac6Zalz.js} +1 -1
- package/dist/{docker-BbTlMmYi.js → docker-DuHcQidD.js} +3 -3
- package/dist/{docker-BbTlMmYi.js.map → docker-DuHcQidD.js.map} +1 -1
- package/dist/{doctor-CqttrZcs.js → doctor-De_QlOTM.js} +34 -35
- package/dist/doctor-De_QlOTM.js.map +1 -0
- package/dist/{egress-BfK_O9ce.js → egress-DEAvq8Fl.js} +32 -15
- package/dist/egress-DEAvq8Fl.js.map +1 -0
- package/dist/{environment-BBz1Yes9.js → environment-B4IuURK1.js} +26 -7
- package/dist/environment-B4IuURK1.js.map +1 -0
- package/dist/{evaluate-yjZKUexW.d.ts → evaluate-C9-ritA5.d.ts} +2 -2
- package/dist/{evaluate-CXQ1o-pd.js → evaluate-Djk1YPFL.js} +18 -18
- package/dist/{evaluate-CXQ1o-pd.js.map → evaluate-Djk1YPFL.js.map} +1 -1
- package/dist/{facts-DtENlf9s.js → facts-Cfx1H70y.js} +4 -4
- package/dist/{facts-DtENlf9s.js.map → facts-Cfx1H70y.js.map} +1 -1
- package/dist/{files-BsQbYz2i.js → files-Bs6H3HiG.js} +3 -3
- package/dist/{files-BsQbYz2i.js.map → files-Bs6H3HiG.js.map} +1 -1
- package/dist/{files-B-eUpCZ1.js → files-CN1QfenC.js} +7 -7
- package/dist/{files-B-eUpCZ1.js.map → files-CN1QfenC.js.map} +1 -1
- package/dist/files-CtjEXWXm.js +1 -0
- package/dist/{files-COHQLEHE.js → files-D27nUk0K.js} +3 -3
- package/dist/{files-COHQLEHE.js.map → files-D27nUk0K.js.map} +1 -1
- package/dist/files-D3gJa91B.js +1 -0
- package/dist/{generate-Cs2TMH_k.js → generate-BnrvBAzF.js} +10 -11
- package/dist/{generate-Cs2TMH_k.js.map → generate-BnrvBAzF.js.map} +1 -1
- package/dist/{git-DhykwgKF.js → git-rPfl7AD1.js} +3 -3
- package/dist/{git-DhykwgKF.js.map → git-rPfl7AD1.js.map} +1 -1
- package/dist/{harness-BAjw1fcr.js → harness-1OkumOAm.js} +2 -2
- package/dist/{harness-BAjw1fcr.js.map → harness-1OkumOAm.js.map} +1 -1
- package/dist/harness.d.ts +1 -1
- package/dist/harness.js +1 -1
- package/dist/{header-kSmWatcO.js → header-BVzbRnNv.js} +2 -2
- package/dist/{header-kSmWatcO.js.map → header-BVzbRnNv.js.map} +1 -1
- package/dist/{images-BmJRuTpm.js → images-m784ueGK.js} +8 -8
- package/dist/{images-BmJRuTpm.js.map → images-m784ueGK.js.map} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/{init-Bwc9byQr.js → init-hiTkiM4L.js} +7 -7
- package/dist/{init-Bwc9byQr.js.map → init-hiTkiM4L.js.map} +1 -1
- package/dist/init-kMkTgTZc.js +1 -0
- package/dist/{invocations-CQTbP092.js → invocations-D_ePcwyL.js} +2 -2
- package/dist/{invocations-CQTbP092.js.map → invocations-D_ePcwyL.js.map} +1 -1
- package/dist/{judge-Dt6KW6_O.js → judge-CEDIfRiU.js} +48 -15
- package/dist/judge-CEDIfRiU.js.map +1 -0
- package/dist/judge.d.ts +1 -1
- package/dist/judge.js +1 -1
- package/dist/model-B8H3gL69.js +1 -0
- package/dist/{model-C7tSwT7f.js → model-CTiuzkWw.js} +9 -7
- package/dist/model-CTiuzkWw.js.map +1 -0
- package/dist/model-DcIbmggK.js +1 -0
- package/dist/{model-B-mel-Gv.js → model-DpkclE4r.js} +26 -16
- package/dist/model-DpkclE4r.js.map +1 -0
- package/dist/{model-CWlAqmbU.js → model-UgHDMhI1.js} +11 -8
- package/dist/model-UgHDMhI1.js.map +1 -0
- package/dist/{node-D0mtjRAO.js → node-CGZYh4Fj.js} +2 -2
- package/dist/{node-D0mtjRAO.js.map → node-CGZYh4Fj.js.map} +1 -1
- package/dist/{opencode-providers-J9colP0-.js → opencode-providers-snfeWd6Z.js} +86 -47
- package/dist/opencode-providers-snfeWd6Z.js.map +1 -0
- package/dist/{options-CPUW97_7.js → options-CyAyLjgJ.js} +20 -7
- package/dist/options-CyAyLjgJ.js.map +1 -0
- package/dist/{patch-D4oluWzH.js → patch-uMq9rCl_.js} +2 -2
- package/dist/{patch-D4oluWzH.js.map → patch-uMq9rCl_.js.map} +1 -1
- package/dist/{plan-OkwUcJQ9.js → plan-CZYgXnZh.js} +5 -5
- package/dist/{plan-OkwUcJQ9.js.map → plan-CZYgXnZh.js.map} +1 -1
- package/dist/{plugins-CUWMnSNb.js → plugins-P-uh54VF.js} +3 -3
- package/dist/{plugins-CUWMnSNb.js.map → plugins-P-uh54VF.js.map} +1 -1
- package/dist/{preflight-B6pY89bF.js → preflight-BQlPTHVu.js} +1 -1
- package/dist/{preflight-Bjm7_ylE.js → preflight-D3HvKeAx.js} +3 -3
- package/dist/{preflight-Bjm7_ylE.js.map → preflight-D3HvKeAx.js.map} +1 -1
- package/dist/{prepare-DbdgtZKe.js → prepare-DCwM2KKD.js} +2 -2
- package/dist/{prepare-CQmK3SBn.js → prepare-Dx7dOnJB.js} +7 -7
- package/dist/{prepare-CQmK3SBn.js.map → prepare-Dx7dOnJB.js.map} +1 -1
- package/dist/{provenance-B3Y17oCC.js → provenance-C_-8y-x3.js} +3 -3
- package/dist/{provenance-B3Y17oCC.js.map → provenance-C_-8y-x3.js.map} +1 -1
- package/dist/{providers-eoniqK6w.js → providers-CF5UEaP8.js} +5 -5
- package/dist/{providers-eoniqK6w.js.map → providers-CF5UEaP8.js.map} +1 -1
- package/dist/{providers-CXN2RFz-.js → providers-CTBYvMxu.js} +1 -1
- package/dist/{query-CLRZmWBT.js → query-DXthKmq0.js} +2 -2
- package/dist/{query-CLRZmWBT.js.map → query-DXthKmq0.js.map} +1 -1
- package/dist/query-D_1ARx5q.js +1 -0
- package/dist/{questions-1XOulevV.js → questions-KENpWnfV.js} +6 -7
- package/dist/questions-KENpWnfV.js.map +1 -0
- package/dist/{redact-DJ9JCShu.js → redact-CRGdfV2s.js} +4 -2
- package/dist/redact-CRGdfV2s.js.map +1 -0
- package/dist/{remarks-DlJCqWRs.js → remarks-Ml6bcSdV.js} +8 -8
- package/dist/{remarks-DlJCqWRs.js.map → remarks-Ml6bcSdV.js.map} +1 -1
- package/dist/{remote-cE2VWfkJ.js → remote-Dkeghk8k.js} +3 -3
- package/dist/{remote-cE2VWfkJ.js.map → remote-Dkeghk8k.js.map} +1 -1
- package/dist/resolve-CMmhACr6.js +1 -0
- package/dist/{resolve-C4zeccCH.js → resolve-DzdWcIL6.js} +7 -7
- package/dist/{resolve-C4zeccCH.js.map → resolve-DzdWcIL6.js.map} +1 -1
- package/dist/{results-Drtt0uFl.js → results-D-naai_Q.js} +6 -6
- package/dist/{results-Drtt0uFl.js.map → results-D-naai_Q.js.map} +1 -1
- package/dist/{resume-BEFazUfy.js → resume-8-nnW8iO.js} +3 -3
- package/dist/{resume-BEFazUfy.js.map → resume-8-nnW8iO.js.map} +1 -1
- package/dist/{root-DICDHCtp.js → root-DNuLhaA5.js} +2 -2
- package/dist/root-DNuLhaA5.js.map +1 -0
- package/dist/{rules-BFzQqPIm.js → rules-CcV3zLDR.js} +4 -3
- package/dist/rules-CcV3zLDR.js.map +1 -0
- package/dist/{run-BeMy92Us.js → run-Bel7Vde4.js} +10 -10
- package/dist/run-Bel7Vde4.js.map +1 -0
- package/dist/{sandbox-CQaJj7n9.js → sandbox-YLbk9mO7.js} +2 -2
- package/dist/{sandbox-CQaJj7n9.js.map → sandbox-YLbk9mO7.js.map} +1 -1
- package/dist/sandbox.d.ts +1 -1
- package/dist/sandbox.js +1 -1
- package/dist/server-C-ysqkLv.js +1 -0
- package/dist/{server-xK0les4f.js → server-De8oH3bt.js} +8 -8
- package/dist/{server-xK0les4f.js.map → server-De8oH3bt.js.map} +1 -1
- package/dist/{source-Q1hEKbCs.js → source-C686eZpY.js} +2 -2
- package/dist/{source-Q1hEKbCs.js.map → source-C686eZpY.js.map} +1 -1
- package/dist/source-DjgypUHW.js +1 -0
- package/dist/src-3mP821Pt.js +2 -0
- package/dist/src-B3KCLZVk.js +3 -0
- package/dist/src-BAyg6CtQ.js +2 -0
- package/dist/{src-C0CeKiLU.js → src-BYLM3hEz.js} +4 -3
- package/dist/src-BYLM3hEz.js.map +1 -0
- package/dist/src-BezgGX4X.js +2 -0
- package/dist/{src-BhmaorOU.js → src-C7W6kg81.js} +13 -5
- package/dist/{src-BhmaorOU.js.map → src-C7W6kg81.js.map} +1 -1
- package/dist/{src-LekQrr_S.js → src-CIpKL45U.js} +593 -160
- package/dist/src-CIpKL45U.js.map +1 -0
- package/dist/src-ChBhk06Q.js +2 -0
- package/dist/{src-DXNh5mTr.js → src-CoqZYspK.js} +1 -1
- package/dist/{src-CFp86GtV.js → src-CzQrAcF8.js} +49 -49
- package/dist/src-CzQrAcF8.js.map +1 -0
- package/dist/src-D9fSTxlc.js +1 -0
- package/dist/{src-DiLMkIr9.js → src-DeY52Gyf.js} +47 -17
- package/dist/src-DeY52Gyf.js.map +1 -0
- package/dist/src-DrJSnoMR.js +2 -0
- package/dist/{src-qGoFkuJF.js → src-Xj_BZKbq.js} +58 -8
- package/dist/src-Xj_BZKbq.js.map +1 -0
- package/dist/{src-DPIjmReZ.js → src-dhSTRN50.js} +1 -1
- package/dist/{src-C7_1SqDP.js → src-djNNYIdP.js} +2 -2
- package/dist/{src-C7_1SqDP.js.map → src-djNNYIdP.js.map} +1 -1
- package/dist/state-BGWGAwdo.js +1 -0
- package/dist/{state-BmZ8QVZ8.js → state-C0i_ujQZ.js} +17 -15
- package/dist/{state-BmZ8QVZ8.js.map → state-C0i_ujQZ.js.map} +1 -1
- package/dist/studio-app/assets/{index-CnYJijyI.js → index-Bb_cid9k.js} +2 -2
- package/dist/studio-app/index.html +1 -1
- package/dist/{suite-C8sw2pox.js → suite-DmuF_ApO.js} +3 -5
- package/dist/suite-DmuF_ApO.js.map +1 -0
- package/dist/suite-mn9g4veR.js +2 -0
- package/dist/{summary-0oQNGskv.js → summary-BFMHMTz2.js} +4 -4
- package/dist/{summary-0oQNGskv.js.map → summary-BFMHMTz2.js.map} +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/dist/{types-GraZtfHN.d.ts → types-B-9b1MDV.d.ts} +6 -4
- package/dist/types-C5vI3g1C.js.map +1 -1
- package/dist/{validate-BdoXUuL_.js → validate-DWjkshl1.js} +12 -12
- package/dist/{validate-BdoXUuL_.js.map → validate-DWjkshl1.js.map} +1 -1
- package/dist/view/index.d.ts +13 -8
- package/dist/view/index.js +5 -5
- package/dist/views-DE2A1jEX.js +2 -0
- package/dist/{views-BoUoX4_A.js → views-Dk8MCQKr.js} +5 -5
- package/dist/{views-BoUoX4_A.js.map → views-Dk8MCQKr.js.map} +1 -1
- package/dist/{worker-entry-D0-GErmU.js → worker-entry-CqrzGPF4.js} +2 -2
- package/dist/{worker-entry-D0-GErmU.js.map → worker-entry-CqrzGPF4.js.map} +1 -1
- package/dist/workspace-B1eGO46M.js +1 -0
- package/dist/{workspace-tjZFkjxC.js → workspace-CLeOybkr.js} +45 -11
- package/dist/workspace-CLeOybkr.js.map +1 -0
- package/package.json +3 -1
- package/dist/assemble-CJoD_Rzr.js +0 -1
- package/dist/capture-Dp3q7K7Y.js +0 -1
- package/dist/command-CaZgCTIw.js.map +0 -1
- package/dist/config-Dv5MG7Rp.js.map +0 -1
- package/dist/db-CJDRVQal.js +0 -2
- package/dist/docker-CCXdbREk.js.map +0 -1
- package/dist/doctor-CqttrZcs.js.map +0 -1
- package/dist/egress-BfK_O9ce.js.map +0 -1
- package/dist/environment-BBz1Yes9.js.map +0 -1
- package/dist/files-DqCBW7i8.js +0 -1
- package/dist/files-Ha-jsQXf.js +0 -1
- package/dist/init-D7Y1ny1z.js +0 -1
- package/dist/judge-Dt6KW6_O.js.map +0 -1
- package/dist/model-B-mel-Gv.js.map +0 -1
- package/dist/model-BGKYVpoj.js +0 -1
- package/dist/model-C7tSwT7f.js.map +0 -1
- package/dist/model-CWlAqmbU.js.map +0 -1
- package/dist/model-DHY-dfoO.js +0 -1
- package/dist/opencode-providers-J9colP0-.js.map +0 -1
- package/dist/options-CPUW97_7.js.map +0 -1
- package/dist/query-CmBDYt0_.js +0 -1
- package/dist/questions-1XOulevV.js.map +0 -1
- package/dist/redact-DJ9JCShu.js.map +0 -1
- package/dist/resolve-D30yZYmB.js +0 -1
- package/dist/root-DICDHCtp.js.map +0 -1
- package/dist/rules-BFzQqPIm.js.map +0 -1
- package/dist/run-BeMy92Us.js.map +0 -1
- package/dist/server-V4-2w0l8.js +0 -1
- package/dist/source-BDow_oW8.js +0 -1
- package/dist/src-B9L9yGKI.js +0 -1
- package/dist/src-Bcah6ZkT.js +0 -3
- package/dist/src-C0CeKiLU.js.map +0 -1
- package/dist/src-C0xowZ3C.js +0 -2
- package/dist/src-CFp86GtV.js.map +0 -1
- package/dist/src-CHIfIKUu.js +0 -2
- package/dist/src-DLtyz4w5.js +0 -2
- package/dist/src-DiLMkIr9.js.map +0 -1
- package/dist/src-LekQrr_S.js.map +0 -1
- package/dist/src-bCnsA6Vp.js +0 -2
- package/dist/src-qGoFkuJF.js.map +0 -1
- package/dist/src-ynBVosfX.js +0 -2
- package/dist/state-BQR_BCm4.js +0 -1
- package/dist/suite-BaHPmAGy.js +0 -1
- package/dist/suite-C8sw2pox.js.map +0 -1
- package/dist/views-noVQNCUP.js +0 -2
- package/dist/workspace-B90YDU_a.js +0 -1
- package/dist/workspace-tjZFkjxC.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as ownFileURL } from "./root-
|
|
1
|
+
import { u as ownFileURL } from "./root-DNuLhaA5.js";
|
|
2
2
|
import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, renameSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { basename, dirname, join, posix, relative, win32 } from "node:path";
|
|
@@ -13,6 +13,7 @@ import http from "node:http";
|
|
|
13
13
|
import http2 from "node:http2";
|
|
14
14
|
import https from "node:https";
|
|
15
15
|
import tls from "node:tls";
|
|
16
|
+
import zlib from "node:zlib";
|
|
16
17
|
import { lookup } from "node:dns/promises";
|
|
17
18
|
//#region ../sandbox-docker/src/seccomp-profile.ts
|
|
18
19
|
const DOCKER_SECCOMP_PROFILE = {
|
|
@@ -1620,11 +1621,11 @@ const HARNESS_PACKAGES = {
|
|
|
1620
1621
|
},
|
|
1621
1622
|
antigravity: {
|
|
1622
1623
|
name: "@reeval/harness-antigravity",
|
|
1623
|
-
version: "0.1.
|
|
1624
|
+
version: "0.1.11"
|
|
1624
1625
|
},
|
|
1625
1626
|
opencode: {
|
|
1626
1627
|
name: "@reeval/harness-opencode",
|
|
1627
|
-
version: "0.2.
|
|
1628
|
+
version: "0.2.9"
|
|
1628
1629
|
},
|
|
1629
1630
|
goose: {
|
|
1630
1631
|
name: "@reeval/harness-goose",
|
|
@@ -1632,7 +1633,7 @@ const HARNESS_PACKAGES = {
|
|
|
1632
1633
|
},
|
|
1633
1634
|
pi: {
|
|
1634
1635
|
name: "@reeval/harness-pi",
|
|
1635
|
-
version: "0.2.
|
|
1636
|
+
version: "0.2.8"
|
|
1636
1637
|
},
|
|
1637
1638
|
codex: {
|
|
1638
1639
|
name: "@reeval/harness-codex",
|
|
@@ -1644,7 +1645,7 @@ const HARNESS_PACKAGES = {
|
|
|
1644
1645
|
},
|
|
1645
1646
|
"grok-build": {
|
|
1646
1647
|
name: "@reeval/harness-grok-build",
|
|
1647
|
-
version: "0.1.
|
|
1648
|
+
version: "0.1.2"
|
|
1648
1649
|
}
|
|
1649
1650
|
};
|
|
1650
1651
|
/** Every harness with an image, in {@link HARNESS_PACKAGES} order. */
|
|
@@ -1827,7 +1828,8 @@ function ipv6Bytes(address) {
|
|
|
1827
1828
|
//#region ../sandbox-docker/src/egress/mux.ts
|
|
1828
1829
|
/**
|
|
1829
1830
|
* Many TCP streams over one byte pipe (a `docker exec` stdio), with per-stream credit so a slow
|
|
1830
|
-
* reader only stalls its own stream. Frames are `type u8, id u32, length u32, payload
|
|
1831
|
+
* reader only stalls its own stream. Frames are `type u8, id u32, length u32, payload`, and a
|
|
1832
|
+
* payload is at most 64 KiB.
|
|
1831
1833
|
*
|
|
1832
1834
|
* Must stay self-contained, with no imports or references to module scope: the forwarder runs
|
|
1833
1835
|
* this function's source inside the container.
|
|
@@ -1841,9 +1843,43 @@ function createMux(options) {
|
|
|
1841
1843
|
const READY = 6;
|
|
1842
1844
|
const windowSize = options.window ?? 262144;
|
|
1843
1845
|
const maxPayload = 65536;
|
|
1846
|
+
/** The payload length each type must have, or its range. */
|
|
1847
|
+
const LENGTHS = {
|
|
1848
|
+
[OPEN]: [
|
|
1849
|
+
"OPEN",
|
|
1850
|
+
2,
|
|
1851
|
+
2
|
|
1852
|
+
],
|
|
1853
|
+
[DATA]: [
|
|
1854
|
+
"DATA",
|
|
1855
|
+
0,
|
|
1856
|
+
maxPayload
|
|
1857
|
+
],
|
|
1858
|
+
[FIN]: [
|
|
1859
|
+
"FIN",
|
|
1860
|
+
0,
|
|
1861
|
+
0
|
|
1862
|
+
],
|
|
1863
|
+
[RST]: [
|
|
1864
|
+
"RST",
|
|
1865
|
+
0,
|
|
1866
|
+
0
|
|
1867
|
+
],
|
|
1868
|
+
[WINDOW]: [
|
|
1869
|
+
"WINDOW",
|
|
1870
|
+
4,
|
|
1871
|
+
4
|
|
1872
|
+
],
|
|
1873
|
+
[READY]: [
|
|
1874
|
+
"READY",
|
|
1875
|
+
0,
|
|
1876
|
+
0
|
|
1877
|
+
]
|
|
1878
|
+
};
|
|
1844
1879
|
const streams = /* @__PURE__ */ new Map();
|
|
1845
1880
|
let nextId = options.parity === 0 ? 2 : 1;
|
|
1846
1881
|
let pending = Buffer.alloc(0);
|
|
1882
|
+
let broken = false;
|
|
1847
1883
|
const frame = (type, id, payload = Buffer.alloc(0)) => {
|
|
1848
1884
|
const header = Buffer.alloc(9);
|
|
1849
1885
|
header.writeUInt8(type, 0);
|
|
@@ -1896,6 +1932,25 @@ function createMux(options) {
|
|
|
1896
1932
|
streams.set(id, state);
|
|
1897
1933
|
return stream;
|
|
1898
1934
|
};
|
|
1935
|
+
/** Why a frame's header breaks the protocol, or null. */
|
|
1936
|
+
const malformed = (type, id, length) => {
|
|
1937
|
+
const expected = LENGTHS[type];
|
|
1938
|
+
if (!expected) return `a frame of unknown type ${type}`;
|
|
1939
|
+
const [name, min, max] = expected;
|
|
1940
|
+
if (length > maxPayload) return `a frame too large (${length} bytes)`;
|
|
1941
|
+
if (length < min || length > max) return `a malformed ${name} frame (${length} bytes, not ${min === max ? min : `0 to ${max}`} bytes)`;
|
|
1942
|
+
if (type === OPEN && (id % 2 === options.parity || streams.has(id))) return `an OPEN frame for stream ${id}, which this side allocates or has open`;
|
|
1943
|
+
return null;
|
|
1944
|
+
};
|
|
1945
|
+
const fail = (message) => {
|
|
1946
|
+
broken = true;
|
|
1947
|
+
pending = Buffer.alloc(0);
|
|
1948
|
+
for (const state of [...streams.values()]) {
|
|
1949
|
+
state.reset = true;
|
|
1950
|
+
state.stream.destroy();
|
|
1951
|
+
}
|
|
1952
|
+
options.onError?.(`the peer sent ${message}`);
|
|
1953
|
+
};
|
|
1899
1954
|
const handle = (type, id, payload) => {
|
|
1900
1955
|
if (type === READY) return options.onReady?.();
|
|
1901
1956
|
if (type === OPEN) return options.onOpen(create(id), payload.readUInt16BE(0));
|
|
@@ -1924,15 +1979,22 @@ function createMux(options) {
|
|
|
1924
1979
|
};
|
|
1925
1980
|
return {
|
|
1926
1981
|
receive(chunk) {
|
|
1982
|
+
if (broken) return;
|
|
1927
1983
|
pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1984
|
+
try {
|
|
1985
|
+
while (pending.length >= 9) {
|
|
1986
|
+
const type = pending.readUInt8(0);
|
|
1987
|
+
const id = pending.readUInt32BE(1);
|
|
1988
|
+
const length = pending.readUInt32BE(5);
|
|
1989
|
+
const problem = malformed(type, id, length);
|
|
1990
|
+
if (problem) return fail(problem);
|
|
1991
|
+
if (pending.length < 9 + length) break;
|
|
1992
|
+
const payload = pending.subarray(9, 9 + length);
|
|
1993
|
+
pending = pending.subarray(9 + length);
|
|
1994
|
+
handle(type, id, payload);
|
|
1995
|
+
}
|
|
1996
|
+
} catch (error) {
|
|
1997
|
+
fail(`a frame it couldn't handle: ${error.message}`);
|
|
1936
1998
|
}
|
|
1937
1999
|
},
|
|
1938
2000
|
open(port) {
|
|
@@ -1986,6 +2048,10 @@ function forwarderMain(io, makeMux, linkStreams, proxyPort) {
|
|
|
1986
2048
|
host: "127.0.0.1",
|
|
1987
2049
|
allowHalfOpen: true
|
|
1988
2050
|
}), stream);
|
|
2051
|
+
},
|
|
2052
|
+
onError(message) {
|
|
2053
|
+
io.error(`reeval egress forwarder: ${message}`);
|
|
2054
|
+
io.exit(1);
|
|
1989
2055
|
}
|
|
1990
2056
|
});
|
|
1991
2057
|
io.input.on("data", (chunk) => mux.receive(chunk));
|
|
@@ -2066,13 +2132,45 @@ const normalise = (host) => host.toLowerCase().replace(/\.$/, "").replace(/^\[|\
|
|
|
2066
2132
|
* mustn't resolve into them, or anyone who can add a DNS record under the pattern's domain
|
|
2067
2133
|
* reaches the worker's LAN.
|
|
2068
2134
|
*/
|
|
2069
|
-
const
|
|
2135
|
+
const PRIVATE_RANGES = [
|
|
2070
2136
|
"10.0.0.0/8",
|
|
2071
2137
|
"100.64.0.0/10",
|
|
2072
2138
|
"172.16.0.0/12",
|
|
2073
2139
|
"192.168.0.0/16",
|
|
2074
2140
|
"fc00::/7"
|
|
2141
|
+
];
|
|
2142
|
+
const PRIVATE = blockList(PRIVATE_RANGES);
|
|
2143
|
+
/**
|
|
2144
|
+
* Reachable only with an `allowedCIDRs` entry that covers them, so never under allow-all: the
|
|
2145
|
+
* gateway connects from the worker, so these would reach its LAN, the Docker host's bridge, cloud
|
|
2146
|
+
* metadata on a unique-local or CGNAT address (AWS's `fd00:ec2::254`, Alibaba's
|
|
2147
|
+
* `100.100.100.200`), multicast and broadcast. NAT64 addresses are checked by their embedded
|
|
2148
|
+
* IPv4 address as well.
|
|
2149
|
+
*/
|
|
2150
|
+
const RESERVED = blockList([
|
|
2151
|
+
...PRIVATE_RANGES,
|
|
2152
|
+
"224.0.0.0/4",
|
|
2153
|
+
"255.255.255.255/32",
|
|
2154
|
+
"ff00::/8",
|
|
2155
|
+
"64:ff9b::/96"
|
|
2075
2156
|
]);
|
|
2157
|
+
const NAT64 = blockList(["64:ff9b::/96"]);
|
|
2158
|
+
/** The IPv4 address in the last 32 bits of an IPv6 address, as `a.b.c.d`. */
|
|
2159
|
+
function embeddedIPv4(address) {
|
|
2160
|
+
const tail = address.slice(address.lastIndexOf(":") + 1);
|
|
2161
|
+
if (isIP(tail) === 4) return tail;
|
|
2162
|
+
const groups = address.split(":");
|
|
2163
|
+
const low = Number.parseInt(groups.at(-1) || "0", 16);
|
|
2164
|
+
const high = groups.length > 1 ? Number.parseInt(groups.at(-2) || "0", 16) : 0;
|
|
2165
|
+
return [
|
|
2166
|
+
high >> 8,
|
|
2167
|
+
high & 255,
|
|
2168
|
+
low >> 8,
|
|
2169
|
+
low & 255
|
|
2170
|
+
].join(".");
|
|
2171
|
+
}
|
|
2172
|
+
/** The address, and for a NAT64 address the IPv4 address it reaches too. */
|
|
2173
|
+
const reaches = (address) => isIP(address) === 6 && inList(NAT64, address) ? [address, embeddedIPv4(address)] : [address];
|
|
2076
2174
|
/** How `host` matches: listed as it is, only by a `*.` pattern (never an IP address), or not. */
|
|
2077
2175
|
function hostMatch(patterns, host) {
|
|
2078
2176
|
const h = normalise(host);
|
|
@@ -2105,7 +2203,7 @@ function createPolicyCheck(policy, options = {}) {
|
|
|
2105
2203
|
reason: "the policy denies all egress"
|
|
2106
2204
|
};
|
|
2107
2205
|
const listed = hostMatch(custom?.allowedHosts ?? [], host);
|
|
2108
|
-
if (custom && !listed && !custom.allowedCIDRs?.length) return {
|
|
2206
|
+
if (custom && !listed && !(custom.allowedCIDRs?.length && isIP(host))) return {
|
|
2109
2207
|
allowed: false,
|
|
2110
2208
|
status: 403,
|
|
2111
2209
|
reason: `${host} isn't in the allowlist`
|
|
@@ -2126,16 +2224,26 @@ function createPolicyCheck(policy, options = {}) {
|
|
|
2126
2224
|
status: 502,
|
|
2127
2225
|
reason: `${host} has no addresses`
|
|
2128
2226
|
};
|
|
2129
|
-
|
|
2227
|
+
const reached = addresses.flatMap(reaches);
|
|
2228
|
+
if (reached.some((a) => inList(always, a))) return {
|
|
2130
2229
|
allowed: false,
|
|
2131
2230
|
status: 403,
|
|
2132
2231
|
reason: `${host} is a loopback or link-local address`
|
|
2133
2232
|
};
|
|
2233
|
+
const reserved = reached.find((a) => inList(RESERVED, a) && !inList(allowedCidrs, a));
|
|
2234
|
+
if (reserved) {
|
|
2235
|
+
const which = reserved === host ? host : `${host} (${reserved})`;
|
|
2236
|
+
return {
|
|
2237
|
+
allowed: false,
|
|
2238
|
+
status: 403,
|
|
2239
|
+
reason: custom ? `${which} is a private or reserved address; list the address itself in the allowlist to reach it` : `${which} is a private or reserved address, which allow-all doesn't reach; list the address in an allowlist to reach it`
|
|
2240
|
+
};
|
|
2241
|
+
}
|
|
2134
2242
|
if (!custom) return {
|
|
2135
2243
|
allowed: true,
|
|
2136
2244
|
address: first
|
|
2137
2245
|
};
|
|
2138
|
-
if (
|
|
2246
|
+
if (reached.some((a) => inList(deniedCidrs, a))) return {
|
|
2139
2247
|
allowed: false,
|
|
2140
2248
|
status: 403,
|
|
2141
2249
|
reason: `${host} is in a denied range`
|
|
@@ -2190,6 +2298,35 @@ const onEndpoint = (endpoints, request) => endpoints.some((match) => matches({
|
|
|
2190
2298
|
}, request));
|
|
2191
2299
|
/** Whether the gateway must decrypt connections to `host`: some rule could apply there. */
|
|
2192
2300
|
const decrypts = (rules, host) => rules.some((r) => normaliseHost(r.match.host) === normaliseHost(host));
|
|
2301
|
+
/** What a credential the sandbox never sent comes back as. */
|
|
2302
|
+
const REDACTED = "reeval_redacted_credential";
|
|
2303
|
+
/** Shorter values aren't credentials, and scrubbing them would mangle ordinary text. */
|
|
2304
|
+
const MIN_CREDENTIAL = 16;
|
|
2305
|
+
const schemeAndToken = (value) => /^([A-Za-z][\w-]*) +(\S+)$/.exec(value);
|
|
2306
|
+
/**
|
|
2307
|
+
* The values credential rules for `host` set (rules keyed on a header the sandbox sends, such as
|
|
2308
|
+
* a placeholder key), each paired with what the sandbox sent in its place, for scrubbing from
|
|
2309
|
+
* responses. A value with a scheme (`Bearer <key>`) is paired as a whole and by its token too.
|
|
2310
|
+
*/
|
|
2311
|
+
function credentialValues(rules, host) {
|
|
2312
|
+
const pairs = /* @__PURE__ */ new Map();
|
|
2313
|
+
const add = (value, shown) => {
|
|
2314
|
+
if (value.length >= MIN_CREDENTIAL && value !== shown && !pairs.has(value)) pairs.set(value, shown);
|
|
2315
|
+
};
|
|
2316
|
+
for (const rule of rules) {
|
|
2317
|
+
const keyed = rule.match.headers ?? [];
|
|
2318
|
+
if (!keyed.length || normaliseHost(rule.match.host) !== normaliseHost(host)) continue;
|
|
2319
|
+
for (const [name, value] of Object.entries(rule.transform.headers)) {
|
|
2320
|
+
const sent = keyed.find((m) => m.key && "exact" in m.key && m.key.exact.toLowerCase() === name.toLowerCase())?.value;
|
|
2321
|
+
const shown = sent && "exact" in sent ? sent.exact : REDACTED;
|
|
2322
|
+
add(value, shown);
|
|
2323
|
+
const token = schemeAndToken(value);
|
|
2324
|
+
const shownToken = schemeAndToken(shown);
|
|
2325
|
+
if (token?.[2]) add(token[2], shownToken?.[2] ?? REDACTED);
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
return [...pairs];
|
|
2329
|
+
}
|
|
2193
2330
|
/**
|
|
2194
2331
|
* The request's headers after every matching rule, in order, so later rules win. Returns the
|
|
2195
2332
|
* (lower-case) names that were replaced, for logging without values. With `endpoints`, no rule
|
|
@@ -2298,59 +2435,75 @@ function scrubHeaders(subs, headers) {
|
|
|
2298
2435
|
const one = (v) => scrubValue(subs, v);
|
|
2299
2436
|
return Object.fromEntries(Object.entries(headers).map(([k, v]) => [k, v === void 0 ? v : Array.isArray(v) ? v.map(one) : one(String(v))]));
|
|
2300
2437
|
}
|
|
2438
|
+
const bufferPairs = (pairs) => pairs.filter(([from]) => from.length > 0).map(([from, to]) => [Buffer.from(from), Buffer.from(to)]);
|
|
2439
|
+
/**
|
|
2440
|
+
* Replaces every whole match in `data` (original bytes only: output is never re-scanned), and
|
|
2441
|
+
* returns where the unscanned tail starts: `data.length` when `final`, else the start of the
|
|
2442
|
+
* shortest tail that could begin a match, so only bytes a later chunk might complete are held.
|
|
2443
|
+
*/
|
|
2444
|
+
function scan(pairs, data, final) {
|
|
2445
|
+
const parts = [];
|
|
2446
|
+
let pos = 0;
|
|
2447
|
+
let count = 0;
|
|
2448
|
+
for (;;) {
|
|
2449
|
+
let best = -1;
|
|
2450
|
+
let pair;
|
|
2451
|
+
for (const p of pairs) {
|
|
2452
|
+
const i = data.indexOf(p[0], pos);
|
|
2453
|
+
if (i !== -1 && (best === -1 || i < best)) {
|
|
2454
|
+
best = i;
|
|
2455
|
+
pair = p;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
if (best === -1 || !pair) break;
|
|
2459
|
+
parts.push(data.subarray(pos, best), pair[1]);
|
|
2460
|
+
pos = best + pair[0].length;
|
|
2461
|
+
count++;
|
|
2462
|
+
}
|
|
2463
|
+
let rest = data.length;
|
|
2464
|
+
if (!final) {
|
|
2465
|
+
const hold = Math.max(0, ...pairs.map(([from]) => from.length - 1));
|
|
2466
|
+
for (let i = Math.max(pos, data.length - hold); i < data.length; i++) {
|
|
2467
|
+
const tail = data.subarray(i);
|
|
2468
|
+
if (pairs.some(([from]) => from.subarray(0, tail.length).equals(tail))) {
|
|
2469
|
+
rest = i;
|
|
2470
|
+
break;
|
|
2471
|
+
}
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
parts.push(data.subarray(pos, rest));
|
|
2475
|
+
return {
|
|
2476
|
+
out: Buffer.concat(parts),
|
|
2477
|
+
rest,
|
|
2478
|
+
count
|
|
2479
|
+
};
|
|
2480
|
+
}
|
|
2481
|
+
/** `data` with every `from` replaced by its `to`, in one pass. */
|
|
2482
|
+
const replaceAll = (data, pairs) => scan(bufferPairs(pairs), data, true).out;
|
|
2301
2483
|
/**
|
|
2302
|
-
* Replaces byte strings in a stream, including across chunk boundaries: it holds back
|
|
2303
|
-
*
|
|
2484
|
+
* Replaces byte strings in a stream, including across chunk boundaries: it holds back a chunk's
|
|
2485
|
+
* tail while it could be the start of a match, so a match split between chunks is still found.
|
|
2304
2486
|
*/
|
|
2305
2487
|
var ReplaceStream = class extends Transform {
|
|
2306
2488
|
#pairs;
|
|
2307
|
-
#hold;
|
|
2308
2489
|
#tail = Buffer.alloc(0);
|
|
2309
2490
|
/** How many replacements were made. */
|
|
2310
2491
|
count = 0;
|
|
2311
2492
|
constructor(pairs) {
|
|
2312
2493
|
super();
|
|
2313
|
-
this.#pairs = pairs
|
|
2314
|
-
this.#hold = Math.max(0, ...this.#pairs.map(([from]) => from.length - 1));
|
|
2315
|
-
}
|
|
2316
|
-
/**
|
|
2317
|
-
* Replaces every whole match in `data` (original bytes only: output is never re-scanned), and
|
|
2318
|
-
* returns where the unscanned tail starts, which is `data.length` when `final`.
|
|
2319
|
-
*/
|
|
2320
|
-
#scan(data, final) {
|
|
2321
|
-
const parts = [];
|
|
2322
|
-
let pos = 0;
|
|
2323
|
-
for (;;) {
|
|
2324
|
-
let best = -1;
|
|
2325
|
-
let pair;
|
|
2326
|
-
for (const p of this.#pairs) {
|
|
2327
|
-
const i = data.indexOf(p[0], pos);
|
|
2328
|
-
if (i !== -1 && (best === -1 || i < best)) {
|
|
2329
|
-
best = i;
|
|
2330
|
-
pair = p;
|
|
2331
|
-
}
|
|
2332
|
-
}
|
|
2333
|
-
if (best === -1 || !pair) break;
|
|
2334
|
-
parts.push(data.subarray(pos, best), pair[1]);
|
|
2335
|
-
pos = best + pair[0].length;
|
|
2336
|
-
this.count++;
|
|
2337
|
-
}
|
|
2338
|
-
const rest = final ? data.length : Math.max(pos, data.length - this.#hold);
|
|
2339
|
-
parts.push(data.subarray(pos, rest));
|
|
2340
|
-
return {
|
|
2341
|
-
out: Buffer.concat(parts),
|
|
2342
|
-
rest
|
|
2343
|
-
};
|
|
2494
|
+
this.#pairs = bufferPairs(pairs);
|
|
2344
2495
|
}
|
|
2345
2496
|
_transform(chunk, _enc, done) {
|
|
2346
2497
|
const data = Buffer.concat([this.#tail, chunk]);
|
|
2347
|
-
const { out, rest } = this.#
|
|
2498
|
+
const { out, rest, count } = scan(this.#pairs, data, false);
|
|
2499
|
+
this.count += count;
|
|
2348
2500
|
this.#tail = Buffer.from(data.subarray(rest));
|
|
2349
2501
|
if (out.length) this.push(out);
|
|
2350
2502
|
done();
|
|
2351
2503
|
}
|
|
2352
2504
|
_flush(done) {
|
|
2353
|
-
const { out } =
|
|
2505
|
+
const { out, count } = scan(this.#pairs, this.#tail, true);
|
|
2506
|
+
this.count += count;
|
|
2354
2507
|
if (out.length) this.push(out);
|
|
2355
2508
|
done();
|
|
2356
2509
|
}
|
|
@@ -2406,6 +2559,85 @@ function tokeniseResponse(derived, body, known) {
|
|
|
2406
2559
|
};
|
|
2407
2560
|
}
|
|
2408
2561
|
//#endregion
|
|
2562
|
+
//#region ../sandbox-docker/src/egress/websocket.ts
|
|
2563
|
+
/** The most a message may hold while its frames are gathered to be scrubbed. */
|
|
2564
|
+
const MAX_MESSAGE = 16777216;
|
|
2565
|
+
/** One unmasked frame, as a server sends it. */
|
|
2566
|
+
function frame(first, payload) {
|
|
2567
|
+
const n = payload.length;
|
|
2568
|
+
if (n < 126) return Buffer.concat([Buffer.from([first, n]), payload]);
|
|
2569
|
+
const head = Buffer.alloc(n < 65536 ? 4 : 10);
|
|
2570
|
+
head[0] = first;
|
|
2571
|
+
if (n < 65536) {
|
|
2572
|
+
head[1] = 126;
|
|
2573
|
+
head.writeUInt16BE(n, 2);
|
|
2574
|
+
} else {
|
|
2575
|
+
head[1] = 127;
|
|
2576
|
+
head.writeBigUInt64BE(BigInt(n), 2);
|
|
2577
|
+
}
|
|
2578
|
+
return Buffer.concat([head, payload]);
|
|
2579
|
+
}
|
|
2580
|
+
/**
|
|
2581
|
+
* Server-to-client WebSocket frames with every real value put back as its placeholder. Each data
|
|
2582
|
+
* message is gathered whole, so a value split across its frames is still found, and sent on as
|
|
2583
|
+
* one frame; control frames pass straight through, even between a message's frames. A
|
|
2584
|
+
* compressed frame (the upgrade request goes up without extensions, so a server shouldn't send
|
|
2585
|
+
* one), or a message over 16 MiB, errors the stream rather than pass unread.
|
|
2586
|
+
*/
|
|
2587
|
+
function webSocketScrubber(subs) {
|
|
2588
|
+
const pairs = subs.map((s) => [s.value, s.placeholder]);
|
|
2589
|
+
let pending = Buffer.alloc(0);
|
|
2590
|
+
let message = null;
|
|
2591
|
+
return new Transform({ transform(chunk, _encoding, done) {
|
|
2592
|
+
pending = pending.length ? Buffer.concat([pending, chunk]) : chunk;
|
|
2593
|
+
const out = [];
|
|
2594
|
+
while (pending.length >= 2) {
|
|
2595
|
+
const first = pending[0] ?? 0;
|
|
2596
|
+
const second = pending[1] ?? 0;
|
|
2597
|
+
let length = second & 127;
|
|
2598
|
+
let at = 2;
|
|
2599
|
+
if (length === 126) {
|
|
2600
|
+
if (pending.length < 4) break;
|
|
2601
|
+
length = pending.readUInt16BE(2);
|
|
2602
|
+
at = 4;
|
|
2603
|
+
} else if (length === 127) {
|
|
2604
|
+
if (pending.length < 10) break;
|
|
2605
|
+
const big = pending.readBigUInt64BE(2);
|
|
2606
|
+
if (big > BigInt(MAX_MESSAGE)) return done(/* @__PURE__ */ new Error("a WebSocket frame over 16 MiB"));
|
|
2607
|
+
length = Number(big);
|
|
2608
|
+
at = 10;
|
|
2609
|
+
}
|
|
2610
|
+
const masked = (second & 128) !== 0;
|
|
2611
|
+
const maskAt = at;
|
|
2612
|
+
if (masked) at += 4;
|
|
2613
|
+
if (pending.length < at + length) break;
|
|
2614
|
+
const payload = Buffer.from(pending.subarray(at, at + length));
|
|
2615
|
+
if (masked) for (let i = 0; i < payload.length; i++) payload[i] = (payload[i] ?? 0) ^ (pending[maskAt + i % 4] ?? 0);
|
|
2616
|
+
pending = pending.subarray(at + length);
|
|
2617
|
+
const opcode = first & 15;
|
|
2618
|
+
if (first & 112) return done(/* @__PURE__ */ new Error("a compressed or extended WebSocket frame, which can't be scrubbed"));
|
|
2619
|
+
if (opcode >= 8) {
|
|
2620
|
+
out.push(frame(first, payload));
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
if (opcode === 0 ? !message : message) return done(/* @__PURE__ */ new Error("WebSocket frames out of order"));
|
|
2624
|
+
message ??= {
|
|
2625
|
+
opcode,
|
|
2626
|
+
parts: [],
|
|
2627
|
+
size: 0
|
|
2628
|
+
};
|
|
2629
|
+
message.parts.push(payload);
|
|
2630
|
+
message.size += payload.length;
|
|
2631
|
+
if (message.size > MAX_MESSAGE) return done(/* @__PURE__ */ new Error("a WebSocket message over 16 MiB"));
|
|
2632
|
+
if (first & 128) {
|
|
2633
|
+
out.push(frame(128 | message.opcode, replaceAll(Buffer.concat(message.parts), pairs)));
|
|
2634
|
+
message = null;
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2637
|
+
done(null, out.length ? Buffer.concat(out) : void 0);
|
|
2638
|
+
} });
|
|
2639
|
+
}
|
|
2640
|
+
//#endregion
|
|
2409
2641
|
//#region ../sandbox-docker/src/egress/proxy.ts
|
|
2410
2642
|
const HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
2411
2643
|
"connection",
|
|
@@ -2429,6 +2661,11 @@ function parseAuthority(authority, defaultPort) {
|
|
|
2429
2661
|
port: Number(m?.[2] ?? defaultPort)
|
|
2430
2662
|
};
|
|
2431
2663
|
}
|
|
2664
|
+
/** `host` or `host:port` for a Host header or URL, IPv6 in brackets. */
|
|
2665
|
+
const authorityOf = (host, port) => {
|
|
2666
|
+
const name = net.isIPv6(host) ? `[${host}]` : host;
|
|
2667
|
+
return port === 443 ? name : `${name}:${port}`;
|
|
2668
|
+
};
|
|
2432
2669
|
const pathOnly = (path) => path.split("?")[0];
|
|
2433
2670
|
function reply(socket, status, reason) {
|
|
2434
2671
|
const text = http.STATUS_CODES[status] ?? "";
|
|
@@ -2439,6 +2676,15 @@ const counter = (count) => new Transform({ transform(chunk, _encoding, done) {
|
|
|
2439
2676
|
count(chunk.length);
|
|
2440
2677
|
done(null, chunk);
|
|
2441
2678
|
} });
|
|
2679
|
+
/** The response encodings the gateway can decode to scrub a body. */
|
|
2680
|
+
const DECODERS = {
|
|
2681
|
+
gzip: () => zlib.createGunzip(),
|
|
2682
|
+
"x-gzip": () => zlib.createGunzip(),
|
|
2683
|
+
deflate: () => zlib.createInflate(),
|
|
2684
|
+
br: () => zlib.createBrotliDecompress()
|
|
2685
|
+
};
|
|
2686
|
+
/** Text a credential could be echoed into, or no type at all. */
|
|
2687
|
+
const isText = (type) => !type.trim() || /^\s*(text\/|application\/(json|xml|javascript|x-www-form-urlencoded|x-ndjson|[\w.-]+\+(json|xml))\b)/i.test(type);
|
|
2442
2688
|
const plainHeaders = (headers) => Object.fromEntries(Object.entries(headers).flatMap(([k, v]) => v === void 0 ? [] : [[k, Array.isArray(v) ? v.map(String) : String(v)]]));
|
|
2443
2689
|
function createEgressProxy(options) {
|
|
2444
2690
|
const log = options.log ?? (() => {});
|
|
@@ -2685,25 +2931,39 @@ function createEgressProxy(options) {
|
|
|
2685
2931
|
} catch {
|
|
2686
2932
|
return res.writeHead(400).end("reeval egress gateway: expected an absolute URL\n");
|
|
2687
2933
|
}
|
|
2688
|
-
if (url.protocol !== "http:") return res.writeHead(400).end(
|
|
2689
|
-
const
|
|
2934
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return res.writeHead(400).end(`reeval egress gateway: expected an http:// or https:// URL, not ${url.protocol}\n`);
|
|
2935
|
+
const secure = url.protocol === "https:";
|
|
2936
|
+
const { host, port } = parseAuthority(url.host, secure ? 443 : 80);
|
|
2937
|
+
const shown = secure ? `https://${host}` : `http://${host}:${port}`;
|
|
2690
2938
|
const done = requestEvent(res, {
|
|
2691
2939
|
method: req.method ?? "GET",
|
|
2692
|
-
url:
|
|
2940
|
+
url: `${shown}${url.pathname}`,
|
|
2693
2941
|
host,
|
|
2694
2942
|
port,
|
|
2695
|
-
via: "http",
|
|
2943
|
+
via: secure ? "decrypted" : "http",
|
|
2696
2944
|
requestHeaders: plainHeaders(req.headers)
|
|
2697
2945
|
});
|
|
2698
2946
|
check(host).then((verdict) => {
|
|
2699
2947
|
if (!verdict.allowed) {
|
|
2700
|
-
log(`reeval:egress ${req.method}
|
|
2948
|
+
log(`reeval:egress ${req.method} ${shown} denied (${verdict.reason})`);
|
|
2701
2949
|
block(req.socket, host, port, verdict.reason);
|
|
2702
2950
|
done.refuse(verdict.reason);
|
|
2703
2951
|
return res.writeHead(verdict.status).end(`blocked by the reeval egress policy: ${verdict.reason}\n`);
|
|
2704
2952
|
}
|
|
2705
|
-
log(`reeval:egress ${req.method} http://${host}:${port}${url.pathname}`);
|
|
2706
2953
|
contact(req.socket, host, port);
|
|
2954
|
+
if (secure) {
|
|
2955
|
+
const headers = {
|
|
2956
|
+
...req.headers,
|
|
2957
|
+
host: url.host
|
|
2958
|
+
};
|
|
2959
|
+
onDecrypted({
|
|
2960
|
+
host,
|
|
2961
|
+
port,
|
|
2962
|
+
address: verdict.address
|
|
2963
|
+
}, req, res, url.pathname + url.search, headers, done);
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
log(`reeval:egress ${req.method} http://${host}:${port}${url.pathname}`);
|
|
2707
2967
|
const upstream = http.request({
|
|
2708
2968
|
host: verdict.address,
|
|
2709
2969
|
port,
|
|
@@ -2714,7 +2974,12 @@ function createEgressProxy(options) {
|
|
|
2714
2974
|
host: url.host
|
|
2715
2975
|
}
|
|
2716
2976
|
});
|
|
2717
|
-
forwardHttp1(req.pipe(counter(done.sent)), res, upstream,
|
|
2977
|
+
forwardHttp1(req.pipe(counter(done.sent)), res, upstream, {
|
|
2978
|
+
own: [],
|
|
2979
|
+
creds: [],
|
|
2980
|
+
url: `${shown}${url.pathname}`,
|
|
2981
|
+
t: done
|
|
2982
|
+
});
|
|
2718
2983
|
});
|
|
2719
2984
|
});
|
|
2720
2985
|
front.on("upgrade", (_req, socket) => {
|
|
@@ -2739,6 +3004,21 @@ function createEgressProxy(options) {
|
|
|
2739
3004
|
* is refused (already logged): a Host that isn't the CONNECT target, or an unbrokered endpoint.
|
|
2740
3005
|
*/
|
|
2741
3006
|
function transformed(target, method, path, headers, version) {
|
|
3007
|
+
if (!path.startsWith("/") && !(method === "OPTIONS" && path === "*")) {
|
|
3008
|
+
const reason = `reeval egress gateway: expected a path in the request line on a connection to ${target.host}, not an absolute URL.`;
|
|
3009
|
+
log(`reeval:egress ${method} https://${target.host} refused (absolute-form target)`);
|
|
3010
|
+
return {
|
|
3011
|
+
headers: {},
|
|
3012
|
+
path,
|
|
3013
|
+
own: [],
|
|
3014
|
+
names: [],
|
|
3015
|
+
credentials: [],
|
|
3016
|
+
refused: {
|
|
3017
|
+
status: 400,
|
|
3018
|
+
reason
|
|
3019
|
+
}
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
2742
3022
|
const named = String(headers[":authority"] ?? headers.host ?? "");
|
|
2743
3023
|
const asked = parseAuthority(named, target.port).host;
|
|
2744
3024
|
if (named && asked !== target.host) {
|
|
@@ -2806,7 +3086,7 @@ function createEgressProxy(options) {
|
|
|
2806
3086
|
path,
|
|
2807
3087
|
headers: {}
|
|
2808
3088
|
});
|
|
2809
|
-
if (scrubbedInBodies(own).length || exchange || readsModel) delete substituted.headers["accept-encoding"];
|
|
3089
|
+
if (scrubbedInBodies(own).length || credentialValues(rules, target.host).length || exchange || readsModel) delete substituted.headers["accept-encoding"];
|
|
2810
3090
|
const notes = [
|
|
2811
3091
|
replaced.length ? `set ${replaced.join(", ")}` : "",
|
|
2812
3092
|
substituted.names.length ? `substituted ${substituted.names.join(", ")}` : "",
|
|
@@ -2833,19 +3113,72 @@ function createEgressProxy(options) {
|
|
|
2833
3113
|
delete headers["content-length"];
|
|
2834
3114
|
return counted.pipe(bodySubstituter(own));
|
|
2835
3115
|
}
|
|
2836
|
-
/** The response body as sent to the sandbox: real values put back as placeholders. */
|
|
2837
3116
|
const scrubs = options.scrubResponses !== false;
|
|
2838
|
-
|
|
3117
|
+
/**
|
|
3118
|
+
* The harness credentials the rules set on `host`, shaped as substitutions that are never
|
|
3119
|
+
* swapped in, only scrubbed from responses.
|
|
3120
|
+
*/
|
|
3121
|
+
const credentialScrubs = (host) => credentialValues(rules, host).map(([value, shown]) => ({
|
|
3122
|
+
name: "credential",
|
|
3123
|
+
placeholder: shown,
|
|
3124
|
+
value,
|
|
3125
|
+
hosts: [host],
|
|
3126
|
+
in: []
|
|
3127
|
+
}));
|
|
3128
|
+
function scrubPlan(status, headers, own, creds) {
|
|
3129
|
+
if (!scrubs || status === 204 || status === 304) return { body: [] };
|
|
3130
|
+
const type = String(headers["content-type"] ?? "");
|
|
3131
|
+
const body = [...scrubbedInBodies(own), ...status >= 300 || isText(type) ? creds : []];
|
|
3132
|
+
const encoding = String(headers["content-encoding"] ?? "identity").toLowerCase();
|
|
3133
|
+
if (!body.length || encoding === "identity") return { body };
|
|
3134
|
+
if (encoding in DECODERS) return {
|
|
3135
|
+
body,
|
|
3136
|
+
decode: encoding
|
|
3137
|
+
};
|
|
3138
|
+
return {
|
|
3139
|
+
body,
|
|
3140
|
+
refuse: `the response is ${encoding}-encoded, so it can't be scrubbed`
|
|
3141
|
+
};
|
|
3142
|
+
}
|
|
3143
|
+
/**
|
|
3144
|
+
* The response body as sent to the sandbox: decoded if the plan says so, with real values put
|
|
3145
|
+
* back as placeholders. An error anywhere upstream of it (a corrupt encoding) errors it.
|
|
3146
|
+
*/
|
|
3147
|
+
function responseBody(up, plan, t, readsModel = false) {
|
|
2839
3148
|
if (readsModel && t) readModel(up, t);
|
|
2840
|
-
const
|
|
2841
|
-
|
|
3149
|
+
const into = (from, to) => {
|
|
3150
|
+
from.once("error", (error) => to.destroy(error));
|
|
3151
|
+
return from.pipe(to);
|
|
3152
|
+
};
|
|
3153
|
+
let body = up;
|
|
3154
|
+
const decoder = plan.decode ? DECODERS[plan.decode] : void 0;
|
|
3155
|
+
if (decoder) body = into(body, decoder());
|
|
3156
|
+
if (plan.body.length) body = into(body, scrubber(plan.body));
|
|
3157
|
+
return t ? into(body, counter(t.received)) : body;
|
|
2842
3158
|
}
|
|
2843
|
-
|
|
3159
|
+
/** Header (and trailer) values with every real value put back as its placeholder. */
|
|
3160
|
+
const scrubbedHeaders = (headers, all) => {
|
|
2844
3161
|
const out = endToEnd(headers);
|
|
2845
|
-
if (!
|
|
2846
|
-
|
|
2847
|
-
return scrubHeaders(own, out);
|
|
3162
|
+
if (!all.length || !scrubs) return out;
|
|
3163
|
+
return scrubHeaders(all, out);
|
|
2848
3164
|
};
|
|
3165
|
+
const responseHeaders = (headers, own, creds, plan) => {
|
|
3166
|
+
const out = scrubbedHeaders(headers, [...own, ...creds]);
|
|
3167
|
+
if (plan.body.length) delete out["content-length"];
|
|
3168
|
+
if (plan.decode) delete out["content-encoding"];
|
|
3169
|
+
return out;
|
|
3170
|
+
};
|
|
3171
|
+
/** Answers a response the gateway won't pass on with a 502 saying why. */
|
|
3172
|
+
function refuseResponse(res, up, url, why, t) {
|
|
3173
|
+
log(`reeval:egress ${url} response refused (${why})`);
|
|
3174
|
+
up.resume();
|
|
3175
|
+
t?.set({
|
|
3176
|
+
status: 502,
|
|
3177
|
+
reason: why
|
|
3178
|
+
});
|
|
3179
|
+
if (res.headersSent) return res.destroy();
|
|
3180
|
+
res.writeHead(502, { "content-type": "text/plain" }).end(`reeval egress gateway: ${why}\n`);
|
|
3181
|
+
}
|
|
2849
3182
|
/**
|
|
2850
3183
|
* How far into a response the gateway looks for the model that served it: OpenAI's first event
|
|
2851
3184
|
* echoes the request's instructions and tools before it.
|
|
@@ -2874,7 +3207,7 @@ function createEgressProxy(options) {
|
|
|
2874
3207
|
* A token exchange's response, read whole, with each token replaced by a placeholder. A success
|
|
2875
3208
|
* that can't be read (compressed, too big, not JSON) is a 502: the real tokens never go through.
|
|
2876
3209
|
*/
|
|
2877
|
-
function tokenised(exchange, url, status, headers, up, res, own, t) {
|
|
3210
|
+
function tokenised(exchange, url, status, headers, up, res, own, creds, t) {
|
|
2878
3211
|
const chunks = [];
|
|
2879
3212
|
let size = 0;
|
|
2880
3213
|
const refuse = (why) => {
|
|
@@ -2883,15 +3216,20 @@ function createEgressProxy(options) {
|
|
|
2883
3216
|
`);
|
|
2884
3217
|
else res.destroy();
|
|
2885
3218
|
};
|
|
2886
|
-
const
|
|
3219
|
+
const plan = scrubPlan(status, headers, own, creds);
|
|
3220
|
+
if (plan.refuse) {
|
|
3221
|
+
up.resume();
|
|
3222
|
+
return refuse(plan.refuse);
|
|
3223
|
+
}
|
|
3224
|
+
const scrubbed = responseBody(up, plan, t);
|
|
2887
3225
|
scrubbed.on("data", (chunk) => {
|
|
2888
3226
|
size += chunk.length;
|
|
2889
3227
|
if (size <= EXCHANGE_LIMIT) chunks.push(chunk);
|
|
2890
3228
|
});
|
|
2891
|
-
|
|
3229
|
+
scrubbed.once("error", (error) => refuse(error.message));
|
|
2892
3230
|
scrubbed.once("end", () => {
|
|
2893
3231
|
const body = Buffer.concat(chunks);
|
|
2894
|
-
const out = responseHeaders(headers, own);
|
|
3232
|
+
const out = responseHeaders(headers, own, creds, plan);
|
|
2895
3233
|
t?.set({ responseHeaders: plainHeaders(out) });
|
|
2896
3234
|
if (status < 200 || status >= 300) return res.writeHead(status, out).end(body);
|
|
2897
3235
|
const encoding = String(headers["content-encoding"] ?? "identity");
|
|
@@ -2913,10 +3251,10 @@ function createEgressProxy(options) {
|
|
|
2913
3251
|
}).end(text);
|
|
2914
3252
|
});
|
|
2915
3253
|
}
|
|
2916
|
-
async function onDecrypted(target, req, res) {
|
|
3254
|
+
async function onDecrypted(target, req, res, asked = req.url ?? "/", sent = req.headers, tracker) {
|
|
2917
3255
|
const method = req.method ?? "GET";
|
|
2918
|
-
const url = `https://${target.host}${pathOnly(
|
|
2919
|
-
const t = requestEvent(res, {
|
|
3256
|
+
const url = `https://${target.host}${pathOnly(asked)}`;
|
|
3257
|
+
const t = tracker ?? requestEvent(res, {
|
|
2920
3258
|
method,
|
|
2921
3259
|
url,
|
|
2922
3260
|
host: target.host,
|
|
@@ -2924,7 +3262,7 @@ function createEgressProxy(options) {
|
|
|
2924
3262
|
via: "decrypted",
|
|
2925
3263
|
requestHeaders: plainHeaders(req.headers)
|
|
2926
3264
|
});
|
|
2927
|
-
const { headers, path, own, names, credentials, refused, exchange, readsModel } = transformed(target, method,
|
|
3265
|
+
const { headers, path, own, names, credentials, refused, exchange, readsModel } = transformed(target, method, asked, sent, req.httpVersion);
|
|
2928
3266
|
t.set({
|
|
2929
3267
|
substituted: names,
|
|
2930
3268
|
credentials
|
|
@@ -2937,7 +3275,7 @@ function createEgressProxy(options) {
|
|
|
2937
3275
|
delete headers.upgrade;
|
|
2938
3276
|
delete headers.connection;
|
|
2939
3277
|
const body = requestBody(req, headers, own, t);
|
|
2940
|
-
const authority = target.
|
|
3278
|
+
const authority = authorityOf(target.host, target.port);
|
|
2941
3279
|
const session = req.httpVersionMajor === 2 ? await upstreamSession(target) : null;
|
|
2942
3280
|
if (!session) return forwardHttp1(body, res, https.request({
|
|
2943
3281
|
host: target.address,
|
|
@@ -2951,10 +3289,14 @@ function createEgressProxy(options) {
|
|
|
2951
3289
|
},
|
|
2952
3290
|
agent,
|
|
2953
3291
|
ca
|
|
2954
|
-
}),
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3292
|
+
}), {
|
|
3293
|
+
own,
|
|
3294
|
+
creds: credentialScrubs(target.host),
|
|
3295
|
+
url,
|
|
3296
|
+
...exchange ? { exchange } : {},
|
|
3297
|
+
...t ? { t } : {},
|
|
3298
|
+
readsModel: !!readsModel
|
|
3299
|
+
});
|
|
2958
3300
|
const stream = session.request({
|
|
2959
3301
|
...headers,
|
|
2960
3302
|
":method": method,
|
|
@@ -2967,30 +3309,41 @@ function createEgressProxy(options) {
|
|
|
2967
3309
|
else res.destroy();
|
|
2968
3310
|
};
|
|
2969
3311
|
stream.once("error", fail);
|
|
3312
|
+
const creds = credentialScrubs(target.host);
|
|
2970
3313
|
stream.once("response", (h) => {
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
stream.once("trailers", (trailers) => res.addTrailers(responseHeaders(trailers, own)));
|
|
2976
|
-
responseBody(stream, own, t, readsModel).pipe(res);
|
|
3314
|
+
const status = Number(h[":status"]);
|
|
3315
|
+
if (exchange) return tokenised(exchange, url, status, h, stream, res, own, creds, t);
|
|
3316
|
+
if (!respond(status, h, stream, res, own, creds, url, t, readsModel)) return;
|
|
3317
|
+
stream.once("trailers", (trailers) => res.addTrailers(scrubbedHeaders(trailers, [...own, ...creds])));
|
|
2977
3318
|
});
|
|
2978
3319
|
body.pipe(stream);
|
|
2979
3320
|
req.once("aborted", () => stream.close(http2.constants.NGHTTP2_CANCEL));
|
|
2980
3321
|
}
|
|
2981
|
-
|
|
3322
|
+
/**
|
|
3323
|
+
* Sends an upstream response on to the sandbox, scrubbed. False when it was refused instead,
|
|
3324
|
+
* already answered with a 502.
|
|
3325
|
+
*/
|
|
3326
|
+
function respond(status, headers, up, res, own, creds, url, t, readsModel = false) {
|
|
3327
|
+
const plan = scrubPlan(status, headers, own, creds);
|
|
3328
|
+
if (plan.refuse) {
|
|
3329
|
+
refuseResponse(res, up, url, plan.refuse, t);
|
|
3330
|
+
return false;
|
|
3331
|
+
}
|
|
3332
|
+
const out = responseHeaders(headers, own, creds, plan);
|
|
3333
|
+
t?.set({ responseHeaders: plainHeaders(out) });
|
|
3334
|
+
res.writeHead(status, out);
|
|
3335
|
+
responseBody(up, plan, t, readsModel).once("error", () => res.destroy()).pipe(res);
|
|
3336
|
+
return true;
|
|
3337
|
+
}
|
|
3338
|
+
function forwardHttp1(req, res, upstream, to) {
|
|
3339
|
+
const { own, creds, url, exchange, t } = to;
|
|
2982
3340
|
upstream.once("response", (up) => {
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
}
|
|
2987
|
-
const out = responseHeaders(up.headers, own);
|
|
2988
|
-
t?.set({ responseHeaders: plainHeaders(out) });
|
|
2989
|
-
res.writeHead(up.statusCode ?? 502, out);
|
|
3341
|
+
const status = up.statusCode ?? 502;
|
|
3342
|
+
if (exchange) return tokenised(exchange, url, status, up.headers, up, res, own, creds, t);
|
|
3343
|
+
if (!respond(status, up.headers, up, res, own, creds, url, t, to.readsModel)) return;
|
|
2990
3344
|
up.once("end", () => {
|
|
2991
|
-
if (Object.keys(up.trailers).length) res.addTrailers(
|
|
3345
|
+
if (Object.keys(up.trailers).length) res.addTrailers(scrubbedHeaders(up.trailers, [...own, ...creds]));
|
|
2992
3346
|
});
|
|
2993
|
-
responseBody(up, own, t, readsModel).pipe(res);
|
|
2994
3347
|
});
|
|
2995
3348
|
upstream.once("error", (error) => {
|
|
2996
3349
|
t?.set({
|
|
@@ -3006,7 +3359,9 @@ function createEgressProxy(options) {
|
|
|
3006
3359
|
const method = req.method ?? "GET";
|
|
3007
3360
|
const startedAt = Date.now();
|
|
3008
3361
|
const { headers, path, own, names, credentials, refused } = transformed(target, method, req.url ?? "/", req.headers, req.httpVersion);
|
|
3009
|
-
const
|
|
3362
|
+
const all = scrubs ? [...own, ...credentialScrubs(target.host)] : [];
|
|
3363
|
+
const scrubbed = (value) => scrubValue(all, value);
|
|
3364
|
+
if (all.length) delete headers["sec-websocket-extensions"];
|
|
3010
3365
|
const upgraded = (fields) => emit({
|
|
3011
3366
|
type: "request",
|
|
3012
3367
|
method,
|
|
@@ -3044,7 +3399,7 @@ function createEgressProxy(options) {
|
|
|
3044
3399
|
ca
|
|
3045
3400
|
});
|
|
3046
3401
|
upstream.once("upgrade", (up, upSocket, upHead) => {
|
|
3047
|
-
const out =
|
|
3402
|
+
const out = scrubHeaders(all, up.headers);
|
|
3048
3403
|
upgraded({
|
|
3049
3404
|
status: up.statusCode,
|
|
3050
3405
|
responseHeaders: plainHeaders(out)
|
|
@@ -3052,9 +3407,20 @@ function createEgressProxy(options) {
|
|
|
3052
3407
|
const lines = [`HTTP/1.1 ${up.statusCode} ${up.statusMessage}`];
|
|
3053
3408
|
for (let i = 0; i < up.rawHeaders.length; i += 2) lines.push(`${up.rawHeaders[i]}: ${scrubbed(up.rawHeaders[i + 1] ?? "")}`);
|
|
3054
3409
|
socket.write(`${lines.join("\r\n")}\r\n\r\n`);
|
|
3055
|
-
if (upHead.length) socket.write(upHead);
|
|
3056
3410
|
if (head.length) upSocket.write(head);
|
|
3057
|
-
|
|
3411
|
+
track(upSocket);
|
|
3412
|
+
if (!all.length) {
|
|
3413
|
+
if (upHead.length) socket.write(upHead);
|
|
3414
|
+
return link(socket, upSocket);
|
|
3415
|
+
}
|
|
3416
|
+
const frames = webSocketScrubber(all);
|
|
3417
|
+
if (upHead.length) frames.write(upHead);
|
|
3418
|
+
upSocket.pipe(frames);
|
|
3419
|
+
upSocket.once("error", (error) => frames.destroy(error));
|
|
3420
|
+
link(socket, Duplex.from({
|
|
3421
|
+
readable: frames,
|
|
3422
|
+
writable: upSocket
|
|
3423
|
+
}));
|
|
3058
3424
|
});
|
|
3059
3425
|
upstream.once("response", (up) => {
|
|
3060
3426
|
reply(socket, up.statusCode ?? 502, `upstream refused the upgrade (${up.statusCode})`);
|
|
@@ -3082,7 +3448,7 @@ function createEgressProxy(options) {
|
|
|
3082
3448
|
socket.destroy();
|
|
3083
3449
|
return resolve(null);
|
|
3084
3450
|
}
|
|
3085
|
-
const session = http2.connect(`https://${
|
|
3451
|
+
const session = http2.connect(`https://${authorityOf(target.host, target.port)}`, { createConnection: () => socket });
|
|
3086
3452
|
session.on("error", () => {});
|
|
3087
3453
|
session.once("close", () => sessions.get(key) === session && sessions.delete(key));
|
|
3088
3454
|
session.once("goaway", () => sessions.get(key) === session && sessions.delete(key));
|
|
@@ -3192,6 +3558,7 @@ async function startGateway(containerId, options, signal, engine) {
|
|
|
3192
3558
|
});
|
|
3193
3559
|
child.stdin.on("error", () => {});
|
|
3194
3560
|
let closed = false;
|
|
3561
|
+
let started = false;
|
|
3195
3562
|
const mux = createMux({
|
|
3196
3563
|
Duplex,
|
|
3197
3564
|
parity: 0,
|
|
@@ -3202,7 +3569,15 @@ async function startGateway(containerId, options, signal, engine) {
|
|
|
3202
3569
|
if (port === 43128) proxy.handle(stream);
|
|
3203
3570
|
else stream.destroy();
|
|
3204
3571
|
},
|
|
3205
|
-
onReady: () =>
|
|
3572
|
+
onReady: () => {
|
|
3573
|
+
started = true;
|
|
3574
|
+
ready.resolve();
|
|
3575
|
+
},
|
|
3576
|
+
onError: (message) => {
|
|
3577
|
+
if (!started) return;
|
|
3578
|
+
stop(`the forwarder's output broke the protocol: ${message}`);
|
|
3579
|
+
child.kill("SIGKILL");
|
|
3580
|
+
}
|
|
3206
3581
|
});
|
|
3207
3582
|
const ready = {};
|
|
3208
3583
|
ready.promise = new Promise((resolve, reject) => Object.assign(ready, {
|
|
@@ -3214,17 +3589,18 @@ async function startGateway(containerId, options, signal, engine) {
|
|
|
3214
3589
|
const listeners = /* @__PURE__ */ new Map();
|
|
3215
3590
|
const tunnels = /* @__PURE__ */ new Set();
|
|
3216
3591
|
let stopped;
|
|
3217
|
-
|
|
3592
|
+
/** Stops on its own: nothing reaches the container any more, so connections are refused. */
|
|
3593
|
+
const stop = (reason) => {
|
|
3218
3594
|
const asked = closed;
|
|
3219
3595
|
closed = true;
|
|
3220
3596
|
mux.destroy();
|
|
3221
|
-
const reason = `the forwarder exited (${code}): ${stderr.trim().slice(-500)}`.replace(/: $/, "");
|
|
3222
3597
|
ready.reject(new Error(reason));
|
|
3223
3598
|
if (asked) return;
|
|
3224
3599
|
stopped = reason;
|
|
3225
3600
|
for (const socket of tunnels) socket.destroy();
|
|
3226
3601
|
for (const l of listeners.values()) l.then((s) => s.close());
|
|
3227
|
-
}
|
|
3602
|
+
};
|
|
3603
|
+
child.once("close", (code) => stop(`the forwarder exited (${code}): ${stderr.trim().slice(-500)}`.replace(/: $/, "")));
|
|
3228
3604
|
const timer = setTimeout(() => ready.reject(/* @__PURE__ */ new Error(`the forwarder didn't start within ${READY_TIMEOUT_MS / 1e3}s`)), READY_TIMEOUT_MS);
|
|
3229
3605
|
const onAbort = () => ready.reject(signal?.reason);
|
|
3230
3606
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -3336,6 +3712,21 @@ const ROOT_CAPABILITIES = [
|
|
|
3336
3712
|
"SETUID",
|
|
3337
3713
|
"SETGID"
|
|
3338
3714
|
];
|
|
3715
|
+
/**
|
|
3716
|
+
* Every container's capabilities, for {@link DockerSession.runPrivileged}'s root execs: reading
|
|
3717
|
+
* and owning any file. The container's own process is root, and each exec names its user: an
|
|
3718
|
+
* exec as {@link SANDBOX_USER} holds none of them, and `no-new-privileges` keeps it from gaining
|
|
3719
|
+
* them. Podman gives a container's non-root user its added capabilities as ambient ones, which is
|
|
3720
|
+
* why the container itself isn't {@link SANDBOX_USER}.
|
|
3721
|
+
*/
|
|
3722
|
+
const PRIVILEGED_CAPABILITIES = [
|
|
3723
|
+
"CHOWN",
|
|
3724
|
+
"DAC_OVERRIDE",
|
|
3725
|
+
"DAC_READ_SEARCH",
|
|
3726
|
+
"FOWNER"
|
|
3727
|
+
];
|
|
3728
|
+
/** The user each of the sandbox's own execs runs as. */
|
|
3729
|
+
const execUser = (options) => options.root ? "0:0" : SANDBOX_USER;
|
|
3339
3730
|
const hostCpus = /* @__PURE__ */ new Map();
|
|
3340
3731
|
/** Docker refuses `--cpus` above the host's count, so cap it there. */
|
|
3341
3732
|
async function cpuLimit(cpus, engine = "docker") {
|
|
@@ -3348,18 +3739,23 @@ async function cpuLimit(cpus, engine = "docker") {
|
|
|
3348
3739
|
return String(available ? Math.min(cpus, available) : cpus);
|
|
3349
3740
|
}
|
|
3350
3741
|
/**
|
|
3351
|
-
* The `docker run` flags that confine the container: its
|
|
3352
|
-
*
|
|
3742
|
+
* The `docker run` flags that confine the container: its capabilities and limits, and on Podman
|
|
3743
|
+
* the bridge network and Docker's seccomp profile. Its own process is root's `sleep`; every exec
|
|
3744
|
+
* names its user.
|
|
3353
3745
|
*/
|
|
3354
3746
|
async function confinementArgs(options) {
|
|
3355
3747
|
const limits = {
|
|
3356
3748
|
...DEFAULT_LIMITS,
|
|
3357
3749
|
...options.limits
|
|
3358
3750
|
};
|
|
3359
|
-
const caps = [
|
|
3751
|
+
const caps = [
|
|
3752
|
+
...PRIVILEGED_CAPABILITIES,
|
|
3753
|
+
...options.root ? ROOT_CAPABILITIES : [],
|
|
3754
|
+
...options.capabilities ?? []
|
|
3755
|
+
];
|
|
3360
3756
|
return [
|
|
3361
3757
|
"--user",
|
|
3362
|
-
|
|
3758
|
+
"0:0",
|
|
3363
3759
|
"--cap-drop",
|
|
3364
3760
|
"ALL",
|
|
3365
3761
|
...[...new Set(caps)].flatMap((c) => ["--cap-add", c]),
|
|
@@ -3394,9 +3790,11 @@ const EXEC_UMASK = "umask 022";
|
|
|
3394
3790
|
* `memory.events` and v1's `memory.oom_control` both have an `oom_kill` line. Without either,
|
|
3395
3791
|
* Docker's `OOMKilled` says only whether there was one.
|
|
3396
3792
|
*/
|
|
3397
|
-
async function oomKills(id, engine) {
|
|
3793
|
+
async function oomKills(id, engine, user = SANDBOX_USER) {
|
|
3398
3794
|
const events = await docker([
|
|
3399
3795
|
"exec",
|
|
3796
|
+
"--user",
|
|
3797
|
+
user,
|
|
3400
3798
|
id,
|
|
3401
3799
|
"sh",
|
|
3402
3800
|
"-c",
|
|
@@ -3474,12 +3872,7 @@ async function createDockerSession(options) {
|
|
|
3474
3872
|
env,
|
|
3475
3873
|
engine
|
|
3476
3874
|
}).catch((error) => {
|
|
3477
|
-
|
|
3478
|
-
if (signal?.aborted) sweep();
|
|
3479
|
-
if (/unable to find user|no matching entries in passwd/i.test(error.message)) {
|
|
3480
|
-
sweep();
|
|
3481
|
-
throw new Error(`${options.image} has no ${SANDBOX_USER} user to run the sandbox as. Images reeval builds have one: rebuild it (reeval image build), or start from reeval's image or ${spec.fn}({ from }). An eval that needs root at run time can opt in with ${spec.fn}({ root: true }).`);
|
|
3482
|
-
}
|
|
3875
|
+
if (signal?.aborted) sweepLabelDetached(`${LABEL.session}=${nonce}`, void 0, engine);
|
|
3483
3876
|
throw error;
|
|
3484
3877
|
});
|
|
3485
3878
|
let gateway;
|
|
@@ -3493,27 +3886,33 @@ async function createDockerSession(options) {
|
|
|
3493
3886
|
await remove();
|
|
3494
3887
|
throw signal.reason;
|
|
3495
3888
|
}
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3889
|
+
const NO_USER = "reeval:no-sandbox-user";
|
|
3890
|
+
const made = await docker([
|
|
3891
|
+
"exec",
|
|
3892
|
+
"--user",
|
|
3893
|
+
"0:0",
|
|
3894
|
+
id,
|
|
3895
|
+
"/bin/sh",
|
|
3896
|
+
"-c",
|
|
3897
|
+
[
|
|
3898
|
+
EXEC_UMASK,
|
|
3899
|
+
...options.root ? [] : [`{ id -u ${SANDBOX_USER} >/dev/null 2>&1 || { echo ${NO_USER}; exit 1; }; }`],
|
|
3900
|
+
...spec.createsWorkdir ? [] : [`mkdir -p ${SANDBOX_ROOT}`],
|
|
3901
|
+
`mkdir -p ${SANDBOX_STATE} && chown 0:0 ${SANDBOX_STATE} && chmod 700 ${SANDBOX_STATE}`
|
|
3902
|
+
].join(" && ")
|
|
3903
|
+
], {
|
|
3904
|
+
engine,
|
|
3905
|
+
signal
|
|
3906
|
+
}).catch((error) => ({
|
|
3907
|
+
exitCode: 1,
|
|
3908
|
+
stdout: Buffer.alloc(0),
|
|
3909
|
+
stderr: error.message
|
|
3910
|
+
}));
|
|
3911
|
+
if (made.exitCode !== 0) {
|
|
3912
|
+
await remove();
|
|
3913
|
+
if (signal?.aborted) throw signal.reason;
|
|
3914
|
+
if (made.stdout.toString("utf8").includes(NO_USER)) throw new Error(`${options.image} has no ${SANDBOX_USER} user to run the sandbox as. Images reeval builds have one: rebuild it (reeval image build), or start from reeval's image or ${spec.fn}({ from }). An eval that needs root at run time can opt in with ${spec.fn}({ root: true }).`);
|
|
3915
|
+
throw new Error(`Couldn't set up the container: ${made.stderr.trim()}`);
|
|
3517
3916
|
}
|
|
3518
3917
|
const egressOptions = options.egress === true ? {} : options.egress || void 0;
|
|
3519
3918
|
let egress;
|
|
@@ -3565,10 +3964,12 @@ async function createDockerSession(options) {
|
|
|
3565
3964
|
...gateway.env,
|
|
3566
3965
|
...env
|
|
3567
3966
|
} : env;
|
|
3967
|
+
const user = ["--user", execUser(options)];
|
|
3568
3968
|
let execs = 0;
|
|
3569
3969
|
const execArgs = (command, marker, cwd, env) => [
|
|
3570
3970
|
"exec",
|
|
3571
3971
|
"--interactive",
|
|
3972
|
+
...user,
|
|
3572
3973
|
"--workdir",
|
|
3573
3974
|
cwd ? posix.resolve(SANDBOX_ROOT, cwd) : SANDBOX_ROOT,
|
|
3574
3975
|
"--env",
|
|
@@ -3584,10 +3985,16 @@ async function createDockerSession(options) {
|
|
|
3584
3985
|
* Killing the host's `docker exec` client doesn't stop the command in the container, so kill
|
|
3585
3986
|
* every process there that inherited this exec's marker: the command and all it started.
|
|
3586
3987
|
*/
|
|
3587
|
-
const killExec = (marker) => docker([
|
|
3988
|
+
const killExec = (marker, asRoot = false) => docker([
|
|
3588
3989
|
"exec",
|
|
3990
|
+
...asRoot ? [
|
|
3991
|
+
"--user",
|
|
3992
|
+
"0:0",
|
|
3993
|
+
"--env",
|
|
3994
|
+
`PATH=${SYSTEM_PATH}`
|
|
3995
|
+
] : user,
|
|
3589
3996
|
id,
|
|
3590
|
-
"sh",
|
|
3997
|
+
"/bin/sh",
|
|
3591
3998
|
"-c",
|
|
3592
3999
|
`for e in /proc/[0-9]*/environ; do grep -qsxzF ${shellQuote(`${EXEC_MARKER}=${marker}`)} "$e" && kill -9 "$(basename "$(dirname "$e")")" 2>/dev/null; done; true`
|
|
3593
4000
|
], { engine }).then(() => {}, () => {});
|
|
@@ -3614,11 +4021,10 @@ async function createDockerSession(options) {
|
|
|
3614
4021
|
const marker = nextMarker();
|
|
3615
4022
|
const args = [
|
|
3616
4023
|
"exec",
|
|
3617
|
-
"--privileged",
|
|
3618
4024
|
"--user",
|
|
3619
4025
|
"0:0",
|
|
3620
4026
|
"--workdir",
|
|
3621
|
-
|
|
4027
|
+
SANDBOX_STATE,
|
|
3622
4028
|
"--env",
|
|
3623
4029
|
`${EXEC_MARKER}=${marker}`,
|
|
3624
4030
|
"--env",
|
|
@@ -3639,13 +4045,14 @@ async function createDockerSession(options) {
|
|
|
3639
4045
|
stderr: r.stderr
|
|
3640
4046
|
};
|
|
3641
4047
|
} catch (error) {
|
|
3642
|
-
if (o.abortSignal?.aborted) await killExec(marker);
|
|
4048
|
+
if (o.abortSignal?.aborted) await killExec(marker, true);
|
|
3643
4049
|
throw error;
|
|
3644
4050
|
}
|
|
3645
4051
|
};
|
|
3646
4052
|
const readBinaryFile = async (o) => {
|
|
3647
4053
|
const r = await docker([
|
|
3648
4054
|
"exec",
|
|
4055
|
+
...user,
|
|
3649
4056
|
id,
|
|
3650
4057
|
"cat",
|
|
3651
4058
|
"--",
|
|
@@ -3658,13 +4065,29 @@ async function createDockerSession(options) {
|
|
|
3658
4065
|
};
|
|
3659
4066
|
const writeBinaryFile = async (o) => {
|
|
3660
4067
|
const path = posix.resolve(SANDBOX_ROOT, o.path);
|
|
4068
|
+
const make = `${EXEC_UMASK} && mkdir -p ${shellQuote(posix.dirname(path))}`;
|
|
4069
|
+
if (o.content.byteLength === 0) {
|
|
4070
|
+
await dockerOk([
|
|
4071
|
+
"exec",
|
|
4072
|
+
...user,
|
|
4073
|
+
id,
|
|
4074
|
+
"sh",
|
|
4075
|
+
"-c",
|
|
4076
|
+
`${make} && : > ${shellQuote(path)}`
|
|
4077
|
+
], {
|
|
4078
|
+
signal: o.abortSignal,
|
|
4079
|
+
engine
|
|
4080
|
+
});
|
|
4081
|
+
return;
|
|
4082
|
+
}
|
|
3661
4083
|
await dockerOk([
|
|
3662
4084
|
"exec",
|
|
3663
4085
|
"--interactive",
|
|
4086
|
+
...user,
|
|
3664
4087
|
id,
|
|
3665
4088
|
"sh",
|
|
3666
4089
|
"-c",
|
|
3667
|
-
`${
|
|
4090
|
+
`${make} && cat > ${shellQuote(path)}`
|
|
3668
4091
|
], {
|
|
3669
4092
|
input: Buffer.from(o.content),
|
|
3670
4093
|
signal: o.abortSignal,
|
|
@@ -3703,7 +4126,7 @@ async function createDockerSession(options) {
|
|
|
3703
4126
|
},
|
|
3704
4127
|
...brokering,
|
|
3705
4128
|
...substituting,
|
|
3706
|
-
oomKills: () => oomKills(id, engine),
|
|
4129
|
+
oomKills: () => oomKills(id, engine, execUser(options)),
|
|
3707
4130
|
run,
|
|
3708
4131
|
runPrivileged,
|
|
3709
4132
|
async spawn(o) {
|
|
@@ -3850,22 +4273,22 @@ const BOOTSTRAPS = {
|
|
|
3850
4273
|
name: "Cursor",
|
|
3851
4274
|
cli: "@cursor/sdk",
|
|
3852
4275
|
version: `node -p "'@cursor/sdk ' + require(process.env.HOME + '/.ai-sdk-harness/.harness-bootstrap/cursor-sdk/node_modules/@cursor/sdk/package.json').version"`,
|
|
3853
|
-
adapter: async () => (await import("./src-
|
|
4276
|
+
adapter: async () => (await import("./src-DrJSnoMR.js")).createCursorSdk()
|
|
3854
4277
|
},
|
|
3855
4278
|
"claude-code": {
|
|
3856
4279
|
name: "Claude Code",
|
|
3857
4280
|
cli: "claude",
|
|
3858
|
-
adapter: async () => (await import("./dist-
|
|
4281
|
+
adapter: async () => (await import("./dist-Dp_AJgYN.js")).createClaudeCode()
|
|
3859
4282
|
},
|
|
3860
4283
|
antigravity: {
|
|
3861
4284
|
name: "Antigravity",
|
|
3862
4285
|
cli: "agy",
|
|
3863
|
-
adapter: async () => (await import("./src-
|
|
4286
|
+
adapter: async () => (await import("./src-BAyg6CtQ.js")).createAntigravity()
|
|
3864
4287
|
},
|
|
3865
4288
|
opencode: {
|
|
3866
4289
|
name: "OpenCode",
|
|
3867
4290
|
cli: "opencode",
|
|
3868
|
-
adapter: async () => (await import("./src-
|
|
4291
|
+
adapter: async () => (await import("./src-B3KCLZVk.js")).createOpenCode({
|
|
3869
4292
|
auth: { GOOGLE_GENERATIVE_AI_API_KEY: "unused" },
|
|
3870
4293
|
model: "google/unused"
|
|
3871
4294
|
})
|
|
@@ -3873,7 +4296,7 @@ const BOOTSTRAPS = {
|
|
|
3873
4296
|
goose: {
|
|
3874
4297
|
name: "Goose",
|
|
3875
4298
|
cli: "goose",
|
|
3876
|
-
adapter: async () => (await import("./src-
|
|
4299
|
+
adapter: async () => (await import("./src-dhSTRN50.js")).createGoose({
|
|
3877
4300
|
auth: { GOOGLE_API_KEY: "unused" },
|
|
3878
4301
|
model: "unused"
|
|
3879
4302
|
})
|
|
@@ -3881,17 +4304,17 @@ const BOOTSTRAPS = {
|
|
|
3881
4304
|
pi: {
|
|
3882
4305
|
name: "pi",
|
|
3883
4306
|
cli: "pi",
|
|
3884
|
-
adapter: async () => (await import("./src-
|
|
4307
|
+
adapter: async () => (await import("./src-BezgGX4X.js")).createPi({ auth: { GEMINI_API_KEY: "unused" } })
|
|
3885
4308
|
},
|
|
3886
4309
|
codex: {
|
|
3887
4310
|
name: "Codex",
|
|
3888
4311
|
cli: "codex",
|
|
3889
|
-
adapter: async () => (await import("./src-
|
|
4312
|
+
adapter: async () => (await import("./src-ChBhk06Q.js")).createCodex({ auth: { OPENAI_API_KEY: "unused" } })
|
|
3890
4313
|
},
|
|
3891
4314
|
fx: {
|
|
3892
4315
|
name: "fx",
|
|
3893
4316
|
cli: "fx",
|
|
3894
|
-
adapter: async () => (await import("./src-
|
|
4317
|
+
adapter: async () => (await import("./src-CoqZYspK.js")).createFx({
|
|
3895
4318
|
auth: { AI_GATEWAY_API_KEY: "unused" },
|
|
3896
4319
|
model: "unused/unused"
|
|
3897
4320
|
})
|
|
@@ -3899,7 +4322,7 @@ const BOOTSTRAPS = {
|
|
|
3899
4322
|
"grok-build": {
|
|
3900
4323
|
name: "Grok Build",
|
|
3901
4324
|
cli: "grok",
|
|
3902
|
-
adapter: async () => (await import("./src-
|
|
4325
|
+
adapter: async () => (await import("./src-3mP821Pt.js")).createGrokBuild({
|
|
3903
4326
|
auth: { XAI_API_KEY: "unused" },
|
|
3904
4327
|
model: "unused"
|
|
3905
4328
|
})
|
|
@@ -3907,11 +4330,21 @@ const BOOTSTRAPS = {
|
|
|
3907
4330
|
};
|
|
3908
4331
|
/** Keeps the image's `PATH` in login shells, which Debian's `/etc/profile` resets. */
|
|
3909
4332
|
const profilePathScript = () => readFileSync(join(DOCKER_CONTEXT, "reeval-path.sh")).toString("base64");
|
|
4333
|
+
/** `from` as it goes after `FROM`: one word, so it can't add a stage name or another instruction. */
|
|
4334
|
+
function imageReference(from) {
|
|
4335
|
+
if (!/^\S+$/.test(from)) throw new Error(`docker({ from }) is an image reference without spaces or line breaks (got ${JSON.stringify(from)})`);
|
|
4336
|
+
return from;
|
|
4337
|
+
}
|
|
4338
|
+
/**
|
|
4339
|
+
* A CLI's `--version` output as a label value in `docker commit --change`: its first line, without
|
|
4340
|
+
* quotes, backslashes or anything else that could end the value and add an instruction.
|
|
4341
|
+
*/
|
|
4342
|
+
const versionLabel = (output) => (output.split(/\r?\n/)[0] ?? "").replace(/[^\w .+:/()@,~-]/g, "").trim().slice(0, 100) || "unknown";
|
|
3910
4343
|
/**
|
|
3911
4344
|
* Adds what the harness bridge needs to a Debian or Ubuntu based toolchain image: Node (copied
|
|
3912
4345
|
* from the official image, so the base's own toolchain is untouched), pnpm, git, curl and bash.
|
|
3913
4346
|
*/
|
|
3914
|
-
const toolchainDockerfile = (from) => `FROM ${from}
|
|
4347
|
+
const toolchainDockerfile = (from) => `FROM ${imageReference(from)}
|
|
3915
4348
|
SHELL ["/bin/sh", "-c"]
|
|
3916
4349
|
RUN command -v apt-get >/dev/null || { echo "reeval needs a Debian or Ubuntu based image (glibc and apt-get)" >&2; exit 1; } \\
|
|
3917
4350
|
&& apt-get update \\
|
|
@@ -4225,7 +4658,7 @@ async function bake(session, bootstrap) {
|
|
|
4225
4658
|
}
|
|
4226
4659
|
const version = await session.run({ command: probe });
|
|
4227
4660
|
if (bootstrap.version && version.exitCode !== 0) throw new Error(`The harness bootstrap did not install ${bootstrap.cli}:\n${tail(version.stderr)}`);
|
|
4228
|
-
return version.stdout.trim()
|
|
4661
|
+
return versionLabel(version.stdout.trim());
|
|
4229
4662
|
}
|
|
4230
4663
|
/**
|
|
4231
4664
|
* The image `docker({ install })` runs in: the commands on top of `parent`, tagged by the parent's
|
|
@@ -4532,6 +4965,6 @@ async function buildDockerfile(dockerfile, options) {
|
|
|
4532
4965
|
*/
|
|
4533
4966
|
const MCP_TIMER = fileURLToPath(ownFileURL("bin/mcp-timer.cjs"));
|
|
4534
4967
|
//#endregion
|
|
4535
|
-
export {
|
|
4968
|
+
export { isBakedHarness as $, placeholder as A, BASE_IMAGE as B, DEFAULT_LIMITS as C, dockerSandboxProvider as D, createDockerSession as E, HARNESS_HOSTS as F, SANDBOX_HOME as G, DEFAULT_IMAGE as H, PACKAGE_HOSTS as I, SANDBOX_USER as J, SANDBOX_ROOT as K, defaultAllowedHosts as L, matches as M, onEndpoint as N, CA_PATH as O, DOCKER_EGRESS as P, imageFor as Q, hostAllowed as R, usesReevalBase as S, normalizeImageId as St, ROOT_CAPABILITIES as T, HARNESS_PACKAGES as U, BRIDGE_PORT as V, LABEL as W, adapterVersion as X, WORKSPACE_DIR as Y, harnessLabel as Z, pluginBuildKey as _, parseBuildOutput as _t, buildDockerfile as a, imageInfo as at, resolveFrom as b, dockerSeccompFile as bt, buildPluginLayer as c, removeContainerDetached as ct, dockerfileTag as d, shellQuote as dt, docker as et, dockerignoreMatcher as f, ImageBuildError as ft, pluginBuildDir as g, isTransient as gt, installScript as h, failureCause as ht, REEVAL_BASE_ARG as i, dockerState as it, applyTransformations as j, PROXY_PORT as k, contextFiles as l, onDockerCommand as lt, installLayerTag as m, causeLine as mt, DOCKER_CONTEXT as n, dockerCapacity as nt, buildImage as o, imageTags as ot, harnessImageFor as p, RETRY_DELAYS as pt, SANDBOX_STATE as q, PLUGIN_BUILDS_DIR as r, dockerOk as rt, buildInstallLayer as s, isRegistryDigest as st, MCP_TIMER as t, dockerAvailable as tt, contextHash as u, run as ut, pluginLayerTag as v, retryTransient as vt, PRIVILEGED_CAPABILITIES as w, sourceTreeHash as x, engineSpec as xt, pruneImages as y, ENGINES as yt, BAKED_HARNESSES as z };
|
|
4536
4969
|
|
|
4537
|
-
//# sourceMappingURL=src-
|
|
4970
|
+
//# sourceMappingURL=src-CIpKL45U.js.map
|