reeval 0.8.0 → 0.10.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 +52 -40
- package/THIRD_PARTY_NOTICES.md +218 -11
- package/assets/package.json +1 -1
- package/assets/pnpm-lock.yaml +30 -30
- package/dist/adapter-DWlpOtRn.js +279 -0
- package/dist/adapter-DWlpOtRn.js.map +1 -0
- package/dist/{agent-B72lZ6w5.js → agent-CURAwx_q.js} +6 -6
- package/dist/{agent-B72lZ6w5.js.map → agent-CURAwx_q.js.map} +1 -1
- package/dist/{agent-Cfj1wD4D.js → agent-CaS_2Sce.js} +92 -26
- package/dist/agent-CaS_2Sce.js.map +1 -0
- package/dist/{analysis-BTNnKh1w.js → analysis-Bxub9LMT.js} +4 -4
- package/dist/{analysis-BTNnKh1w.js.map → analysis-Bxub9LMT.js.map} +1 -1
- package/dist/{assemble-BbFVjHeS.js → assemble-B9Alds-z.js} +290 -30
- package/dist/assemble-B9Alds-z.js.map +1 -0
- package/dist/assemble-J5MukZFY.js +1 -0
- package/dist/{client-CwzU9CiW.js → assert-EUIoRdIt.js} +38 -112
- package/dist/assert-EUIoRdIt.js.map +1 -0
- package/dist/bridge/index.mjs +248 -55
- package/dist/{capture-BXhBfmsM.js → capture-CNb9SuU5.js} +204 -39
- package/dist/capture-CNb9SuU5.js.map +1 -0
- package/dist/capture-jquXZBKK.js +1 -0
- package/dist/{cli-BMjTKY-D.js → cli-D303Vj5N.js} +257 -146
- package/dist/cli-D303Vj5N.js.map +1 -0
- package/dist/{cli-spec-DSvKsOK4.js → cli-spec-BPZbQG2p.js} +38 -19
- package/dist/cli-spec-BPZbQG2p.js.map +1 -0
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/client-fcwgwOAD.js +106 -0
- package/dist/client-fcwgwOAD.js.map +1 -0
- package/dist/{command-JPGNzFsA.js → command-BLfT7vnQ.js} +19 -18
- package/dist/command-BLfT7vnQ.js.map +1 -0
- package/dist/{command-BK4kOgkH.js → command-BT9a37ZR.js} +381 -78
- package/dist/command-BT9a37ZR.js.map +1 -0
- package/dist/{command-BqJtFiPy.js → command-Bge8F7Ko.js} +384 -101
- package/dist/command-Bge8F7Ko.js.map +1 -0
- package/dist/command-BkFjijN2.js +1 -0
- package/dist/{command-BdstSEHu.js → command-CMJtHKRa.js} +14 -14
- package/dist/{command-BdstSEHu.js.map → command-CMJtHKRa.js.map} +1 -1
- package/dist/commands-1p-bIWhN.js +1 -0
- package/dist/{commands-B618jg3C.js → commands-BFx16WB8.js} +8 -8
- package/dist/commands-BFx16WB8.js.map +1 -0
- package/dist/config-DEgnUiU-.js +756 -0
- package/dist/config-DEgnUiU-.js.map +1 -0
- package/dist/{context-CvUv7DUb.js → context-C1mxXh80.js} +11 -7
- package/dist/context-C1mxXh80.js.map +1 -0
- package/dist/{db-DFbtGIYy.js → db-CdHizc4_.js} +582 -53
- package/dist/db-CdHizc4_.js.map +1 -0
- package/dist/db-Dm1cRJJP.js +2 -0
- package/dist/debug-D7bEv_UV.js +1 -0
- package/dist/{dist-CTzThfu_.js → dist-B8hJImYt.js} +17 -4
- package/dist/dist-B8hJImYt.js.map +1 -0
- package/dist/{docker-2qns7hJ2.js → docker-7b_2SaH7.js} +35 -22
- package/dist/docker-7b_2SaH7.js.map +1 -0
- package/dist/{docker-IFRW8K5f.js → docker-sZrNrr82.js} +3 -3
- package/dist/docker-sZrNrr82.js.map +1 -0
- package/dist/docker-yauK-9X8.js +1 -0
- package/dist/{doctor-B6oYTPoR.js → doctor-BYNok4UO.js} +263 -84
- package/dist/doctor-BYNok4UO.js.map +1 -0
- package/dist/{duration-JsJrIWXq.js → duration-CEOQww97.js} +2 -2
- package/dist/{duration-JsJrIWXq.js.map → duration-CEOQww97.js.map} +1 -1
- package/dist/{egress-IUKdeayc.js → egress-C4PZTSSe.js} +147 -14
- package/dist/egress-C4PZTSSe.js.map +1 -0
- package/dist/environment-DNjRk0nU.js +1133 -0
- package/dist/environment-DNjRk0nU.js.map +1 -0
- package/dist/{errors-yDAEwmZo.js → errors-B6BSMs03.js} +2 -2
- package/dist/errors-B6BSMs03.js.map +1 -0
- package/dist/{errors-C9w_ud7g.js → errors-r025B0Zb.js} +4 -4
- package/dist/errors-r025B0Zb.js.map +1 -0
- package/dist/evaluate-C1UVL21l.d.ts +92 -0
- package/dist/evaluate-D4zeRGBF.js +2351 -0
- package/dist/evaluate-D4zeRGBF.js.map +1 -0
- package/dist/family-D2b7WD10.js +67 -0
- package/dist/family-D2b7WD10.js.map +1 -0
- package/dist/files-BAt0i00X.js +1 -0
- package/dist/{files-CMSUXAEg.js → files-BXUdY8tr.js} +10 -9
- package/dist/{files-CMSUXAEg.js.map → files-BXUdY8tr.js.map} +1 -1
- package/dist/files-BhyltlDB.js +1 -0
- package/dist/files-Bk1mC3AN.js +43 -0
- package/dist/files-Bk1mC3AN.js.map +1 -0
- package/dist/{files-B6aVVGrk.js → files-DQmHE7pb.js} +5 -5
- package/dist/{files-B6aVVGrk.js.map → files-DQmHE7pb.js.map} +1 -1
- package/dist/files-ZZPqfAo6.js +1 -0
- package/dist/{git-EmdFJ4DY.js → git-DfG-YJ1u.js} +2 -2
- package/dist/{git-EmdFJ4DY.js.map → git-DfG-YJ1u.js.map} +1 -1
- package/dist/harness-B8QC9u0O.js +323 -0
- package/dist/harness-B8QC9u0O.js.map +1 -0
- package/dist/harness.d.ts +19 -31
- package/dist/harness.js +1 -2
- package/dist/{header-_F0EWob3.js → header-hK-Z_Po9.js} +10 -3
- package/dist/{header-_F0EWob3.js.map → header-hK-Z_Po9.js.map} +1 -1
- package/dist/{images-CscddrJU.js → images-0On0cztu.js} +114 -45
- package/dist/images-0On0cztu.js.map +1 -0
- package/dist/index.d.ts +10 -26
- package/dist/index.js +6 -2108
- package/dist/{init-NQfIvi-0.js → init-B77Rpcqc.js} +26 -8
- package/dist/init-B77Rpcqc.js.map +1 -0
- package/dist/init-BrOWin3W.js +1 -0
- package/dist/{invocations-nPvrlZPj.js → invocations-DbGfBAA_.js} +4 -4
- package/dist/{invocations-nPvrlZPj.js.map → invocations-DbGfBAA_.js.map} +1 -1
- package/dist/{judge-CeYPN8P_.js → judge-DW_GDOmB.js} +11 -19
- package/dist/{judge-CeYPN8P_.js.map → judge-DW_GDOmB.js.map} +1 -1
- package/dist/judge.d.ts +2 -2
- package/dist/judge.js +1 -1
- package/dist/{live-S0qODb6o.js → live-jlfszAbb.js} +9 -20
- package/dist/live-jlfszAbb.js.map +1 -0
- package/dist/matchers-Ddmc2z7H.js +118 -0
- package/dist/matchers-Ddmc2z7H.js.map +1 -0
- package/dist/{model-DEQo3FkZ.js → model-DaVCY3rb.js} +2 -2
- package/dist/{model-DEQo3FkZ.js.map → model-DaVCY3rb.js.map} +1 -1
- package/dist/model-NM3ID0uR.js +1 -0
- package/dist/{model-Cl507trk.js → model-h_cDtcRr.js} +19 -18
- package/dist/model-h_cDtcRr.js.map +1 -0
- package/dist/model-nwWJvaUR.js +1 -0
- package/dist/{model-BndnmcLm.js → model-oqcDplJG.js} +271 -34
- package/dist/model-oqcDplJG.js.map +1 -0
- package/dist/{node-Co7EXge0.js → node-B3ydHo6t.js} +10 -47
- package/dist/node-B3ydHo6t.js.map +1 -0
- package/dist/object-CLf2-tNq.js +9 -0
- package/dist/object-CLf2-tNq.js.map +1 -0
- package/dist/{opencode-providers-ByD85Q0l.js → opencode-providers-J9colP0-.js} +96 -21
- package/dist/opencode-providers-J9colP0-.js.map +1 -0
- package/dist/options-Bh2W63dK.js +1119 -0
- package/dist/options-Bh2W63dK.js.map +1 -0
- package/dist/{pairs-DDsjgm5F.js → pairs-CnInRu-G.js} +76 -2
- package/dist/{pairs-DDsjgm5F.js.map → pairs-CnInRu-G.js.map} +1 -1
- package/dist/{patch-CGCVipPu.js → patch-Zac5aIRM.js} +6 -6
- package/dist/{patch-CGCVipPu.js.map → patch-Zac5aIRM.js.map} +1 -1
- package/dist/{plan-PPc4-6xd.js → plan-C9Cy27Ay.js} +75 -32
- package/dist/plan-C9Cy27Ay.js.map +1 -0
- package/dist/preflight-CPWpQeBI.js +1 -0
- package/dist/{preflight-CddycK92.js → preflight-D7iFPKeK.js} +8 -7
- package/dist/preflight-D7iFPKeK.js.map +1 -0
- package/dist/prepare-BGjePFQl.js +2 -0
- package/dist/{prepare-CsP-057p.js → prepare-doGxaAGR.js} +38 -30
- package/dist/prepare-doGxaAGR.js.map +1 -0
- package/dist/process-CSVO_jsc.js +119 -0
- package/dist/process-CSVO_jsc.js.map +1 -0
- package/dist/{provenance-CsX6PHDR.js → provenance-DmE9uPVt.js} +4 -4
- package/dist/{provenance-CsX6PHDR.js.map → provenance-DmE9uPVt.js.map} +1 -1
- package/dist/provider-DehR4CMN.js +1 -0
- package/dist/providers-gDMko8fl.js +1 -0
- package/dist/providers-tAOD1NQN.js +165 -0
- package/dist/providers-tAOD1NQN.js.map +1 -0
- package/dist/{query-BUcwCYsI.js → query-Bes9Kh6Y.js} +3 -3
- package/dist/{query-BUcwCYsI.js.map → query-Bes9Kh6Y.js.map} +1 -1
- package/dist/query-C_G_svLU.js +1 -0
- package/dist/questions-DjAwMxZ3.js +524 -0
- package/dist/questions-DjAwMxZ3.js.map +1 -0
- package/dist/{record-BRP9vZrj.js → record-BKmMvZ6O.js} +9 -1
- package/dist/{record-BRP9vZrj.js.map → record-BKmMvZ6O.js.map} +1 -1
- package/dist/remote-HDRt16ew.js +378 -0
- package/dist/remote-HDRt16ew.js.map +1 -0
- package/dist/{results-BcSHWbgB.js → results-DuauQI3Z.js} +4 -4
- package/dist/{results-BcSHWbgB.js.map → results-DuauQI3Z.js.map} +1 -1
- package/dist/{resume-DZ9AmfPv.js → resume-BRfa8E_s.js} +10 -10
- package/dist/{resume-DZ9AmfPv.js.map → resume-BRfa8E_s.js.map} +1 -1
- package/dist/{root-CvdRlRZX.js → root-DICDHCtp.js} +4 -4
- package/dist/{root-CvdRlRZX.js.map → root-DICDHCtp.js.map} +1 -1
- package/dist/{runtime-Dfb2r-cS.js → runtime-PX_oXiCS.js} +105 -11
- package/dist/runtime-PX_oXiCS.js.map +1 -0
- package/dist/sandbox-D3A8oUb7.js +70 -0
- package/dist/sandbox-D3A8oUb7.js.map +1 -0
- package/dist/sandbox.d.ts +22 -8
- package/dist/sandbox.js +1 -2
- package/dist/server-C7sg1ndo.js +1 -0
- package/dist/{server-Da60oe4b.js → server-XDleKaF_.js} +11 -11
- package/dist/server-XDleKaF_.js.map +1 -0
- package/dist/shared-DYpIb1BI.js +166 -0
- package/dist/shared-DYpIb1BI.js.map +1 -0
- package/dist/{source-BVOnL3sn.js → source-C8xumIWj.js} +2 -2
- package/dist/{source-BVOnL3sn.js.map → source-C8xumIWj.js.map} +1 -1
- package/dist/source-fGd0SaKP.js +1 -0
- package/dist/{src-TdJuDYqB.js → src-B7treWa8.js} +1597 -292
- package/dist/src-B7treWa8.js.map +1 -0
- package/dist/{src-C1E9HDWO.js → src-C7_1SqDP.js} +3 -3
- package/dist/{src-C1E9HDWO.js.map → src-C7_1SqDP.js.map} +1 -1
- package/dist/{src-DDNXsD9H.js → src-CF17Ug08.js} +5 -4
- package/dist/src-CF17Ug08.js.map +1 -0
- package/dist/{src-D_cXqmnI.js → src-CFp86GtV.js} +60 -60
- package/dist/src-CFp86GtV.js.map +1 -0
- package/dist/src-C_lPtowT.js +2 -0
- package/dist/src-DIL0jST0.js +368 -0
- package/dist/src-DIL0jST0.js.map +1 -0
- package/dist/src-DNnG4e4R.js +2 -0
- package/dist/src-DTbrY1hi.js +2 -0
- package/dist/src-DdyJEh2v.js +2 -0
- package/dist/src-Dj9PAroc.js +2 -0
- package/dist/src-Du8H85Qq.js +2 -0
- package/dist/src-DvCg0NBs.js +324 -0
- package/dist/src-DvCg0NBs.js.map +1 -0
- package/dist/{src-CxTC5kNI.js → src-DvK0EIZx.js} +8 -10
- package/dist/{src-CxTC5kNI.js.map → src-DvK0EIZx.js.map} +1 -1
- package/dist/{src-DVy58-zZ.js → src-FGXhkEkH.js} +5 -5
- package/dist/{src-DVy58-zZ.js.map → src-FGXhkEkH.js.map} +1 -1
- package/dist/src-jzagbuqH.js +2 -0
- package/dist/src-ofUaYbuY.js +3 -0
- package/dist/src-pJZOrYR9.js +1 -0
- package/dist/{src-D95O-Smv.js → src-u8_ixn_F.js} +15 -11
- package/dist/src-u8_ixn_F.js.map +1 -0
- package/dist/{state-B72h_jEw.js → state-C__9swoG.js} +36 -24
- package/dist/state-C__9swoG.js.map +1 -0
- package/dist/state-CeR6oz0k.js +66 -0
- package/dist/state-CeR6oz0k.js.map +1 -0
- package/dist/state-CgjQo29K.js +1 -0
- package/dist/studio-app/assets/index-CwQN3f_h.css +2 -0
- package/dist/studio-app/assets/{index-Bgr-efZm.js → index-DrQc2Yxd.js} +12 -12
- package/dist/studio-app/index.html +2 -2
- package/dist/{summary-k3mhWWXv.js → summary-BLAVLySU.js} +202 -17
- package/dist/summary-BLAVLySU.js.map +1 -0
- package/dist/{summary-CJdxaRdM.js → summary-DgyjUh_l.js} +7 -7
- package/dist/{summary-CJdxaRdM.js.map → summary-DgyjUh_l.js.map} +1 -1
- package/dist/testing.d.ts +3 -2
- package/dist/testing.js +3 -2
- package/dist/{tool-target-BU1JnSE9.js → tool-target-D2mcYlSQ.js} +14 -7
- package/dist/tool-target-D2mcYlSQ.js.map +1 -0
- package/dist/{tools-D0gF2QoV.js → tools-GCyNYSc1.js} +7 -7
- package/dist/tools-GCyNYSc1.js.map +1 -0
- package/dist/transcript-BNNANU8A.js +64 -0
- package/dist/transcript-BNNANU8A.js.map +1 -0
- package/dist/types-C5vI3g1C.js.map +1 -1
- package/dist/{types-_LOFdPUf.d.ts → types-g17bRAzt.d.ts} +578 -49
- package/dist/{usage-DDF_6tkV.js → usage-CleNKkly.js} +4 -2
- package/dist/{usage-DDF_6tkV.js.map → usage-CleNKkly.js.map} +1 -1
- package/dist/view/index.d.ts +83 -17
- package/dist/view/index.js +7 -6
- package/dist/views-BW6oCDUQ.js +2 -0
- package/dist/{views-DZ1YDqey.js → views-BmQF4UCI.js} +13 -8
- package/dist/views-BmQF4UCI.js.map +1 -0
- package/dist/{worker-entry-DXeyWY-m.js → worker-entry-yOchGYYv.js} +16 -225
- package/dist/worker-entry-yOchGYYv.js.map +1 -0
- package/dist/{workspace-DpXdrslo.js → workspace-CgyHEM0l.js} +7 -7
- package/dist/{workspace-DpXdrslo.js.map → workspace-CgyHEM0l.js.map} +1 -1
- package/dist/workspace-D8sSgpiy.js +1 -0
- package/docker/Dockerfile +7 -5
- package/package.json +12 -12
- package/dist/agent-Cfj1wD4D.js.map +0 -1
- package/dist/assemble-BbFVjHeS.js.map +0 -1
- package/dist/assemble-CXiTVyHD.js +0 -2
- package/dist/capture-BXhBfmsM.js.map +0 -1
- package/dist/capture-N7r3yeak.js +0 -2
- package/dist/cli-BMjTKY-D.js.map +0 -1
- package/dist/cli-spec-DSvKsOK4.js.map +0 -1
- package/dist/client-CwzU9CiW.js.map +0 -1
- package/dist/command-BK4kOgkH.js.map +0 -1
- package/dist/command-BqJtFiPy.js.map +0 -1
- package/dist/command-CTVGuH8y.js +0 -2
- package/dist/command-JPGNzFsA.js.map +0 -1
- package/dist/commands-B618jg3C.js.map +0 -1
- package/dist/commands-Duo2ur3r.js +0 -2
- package/dist/config-B-E0xRkf.js +0 -447
- package/dist/config-B-E0xRkf.js.map +0 -1
- package/dist/context-CvUv7DUb.js.map +0 -1
- package/dist/db-CnoUy-FZ.js +0 -3
- package/dist/db-DFbtGIYy.js.map +0 -1
- package/dist/debug-SEmW4w8f.js +0 -2
- package/dist/dist-CTzThfu_.js.map +0 -1
- package/dist/docker-2qns7hJ2.js.map +0 -1
- package/dist/docker-D6t23fZl.js +0 -2
- package/dist/docker-IFRW8K5f.js.map +0 -1
- package/dist/doctor-B6oYTPoR.js.map +0 -1
- package/dist/egress-IUKdeayc.js.map +0 -1
- package/dist/environment-DiCljXMD.js +0 -657
- package/dist/environment-DiCljXMD.js.map +0 -1
- package/dist/errors-C9w_ud7g.js.map +0 -1
- package/dist/errors-yDAEwmZo.js.map +0 -1
- package/dist/fake-instructions-CUI33I_F.js +0 -32
- package/dist/fake-instructions-CUI33I_F.js.map +0 -1
- package/dist/files-BM78_nnF.js +0 -2
- package/dist/files-BpYgF2z0.js +0 -2
- package/dist/files-YCCVaTQC.js +0 -2
- package/dist/harness-iqWeJQMM.js +0 -154
- package/dist/harness-iqWeJQMM.js.map +0 -1
- package/dist/images-CscddrJU.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/init-DBHyi92L.js +0 -2
- package/dist/init-NQfIvi-0.js.map +0 -1
- package/dist/jiti-BcJHCtrb.js +0 -46
- package/dist/jiti-BcJHCtrb.js.map +0 -1
- package/dist/live-S0qODb6o.js.map +0 -1
- package/dist/model-BbLGRtOs.js +0 -2
- package/dist/model-BndnmcLm.js.map +0 -1
- package/dist/model-Cl507trk.js.map +0 -1
- package/dist/model-DMOraCcm.js +0 -2
- package/dist/node-Co7EXge0.js.map +0 -1
- package/dist/opencode-providers-ByD85Q0l.js.map +0 -1
- package/dist/options-DPUlm4FI.js +0 -735
- package/dist/options-DPUlm4FI.js.map +0 -1
- package/dist/plan-PPc4-6xd.js.map +0 -1
- package/dist/preflight-CddycK92.js.map +0 -1
- package/dist/preflight-Zumy0adT.js +0 -2
- package/dist/prepare-CKdMoC7e.js +0 -3
- package/dist/prepare-CsP-057p.js.map +0 -1
- package/dist/provider-BDbNOQhQ.js +0 -2
- package/dist/providers-DkwiLKDS.js +0 -107
- package/dist/providers-DkwiLKDS.js.map +0 -1
- package/dist/providers-yXZZcPfx.js +0 -2
- package/dist/query-noGbekGF.js +0 -2
- package/dist/questions-BEPj98fZ.js +0 -234
- package/dist/questions-BEPj98fZ.js.map +0 -1
- package/dist/runtime-Dfb2r-cS.js.map +0 -1
- package/dist/sandbox--ZXuscAZ.js +0 -134
- package/dist/sandbox--ZXuscAZ.js.map +0 -1
- package/dist/server-Da60oe4b.js.map +0 -1
- package/dist/server-DiGR4JxK.js +0 -2
- package/dist/source-Dqu0jKwx.js +0 -2
- package/dist/src-BHNiP-5A.js +0 -4
- package/dist/src-BT4dB5CO.js +0 -2
- package/dist/src-BT5pwco0.js +0 -3
- package/dist/src-BrMbt1B2.js +0 -3
- package/dist/src-D13vpMh0.js +0 -3
- package/dist/src-D95O-Smv.js.map +0 -1
- package/dist/src-DDNXsD9H.js.map +0 -1
- package/dist/src-D_cXqmnI.js.map +0 -1
- package/dist/src-DdRlaiRc.js +0 -3
- package/dist/src-TdJuDYqB.js.map +0 -1
- package/dist/src-xczuQbE6.js +0 -3
- package/dist/state-B72h_jEw.js.map +0 -1
- package/dist/state-Bf_Vq_pV.js +0 -2
- package/dist/studio-app/assets/index-CUx6GoL5.css +0 -2
- package/dist/summary-k3mhWWXv.js.map +0 -1
- package/dist/tool-target-BU1JnSE9.js.map +0 -1
- package/dist/tools-D0gF2QoV.js.map +0 -1
- package/dist/views-DZ1YDqey.js.map +0 -1
- package/dist/views-Ds0GXWtX.js +0 -3
- package/dist/worker-entry-DXeyWY-m.js.map +0 -1
- package/dist/workspace-DzNL-3iO.js +0 -2
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { u as ownFileURL } from "./root-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { u as ownFileURL } from "./root-DICDHCtp.js";
|
|
2
|
+
import { n as shellQuote, t as run } from "./process-CSVO_jsc.js";
|
|
3
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, readlinkSync, renameSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { basename, dirname, join, posix, relative } from "node:path";
|
|
4
6
|
import net, { BlockList, isIP } from "node:net";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { createHash, generateKeyPairSync, randomBytes, randomInt, sign } from "node:crypto";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
7
10
|
import { createHarnessSandboxTemplate } from "@ai-sdk/harness/agent";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
11
|
import { isUtf8 } from "node:buffer";
|
|
10
12
|
import { Duplex, Readable, Transform } from "node:stream";
|
|
11
13
|
import http from "node:http";
|
|
@@ -13,6 +15,1073 @@ import http2 from "node:http2";
|
|
|
13
15
|
import https from "node:https";
|
|
14
16
|
import tls from "node:tls";
|
|
15
17
|
import { lookup } from "node:dns/promises";
|
|
18
|
+
//#region ../sandbox-docker/src/seccomp-profile.ts
|
|
19
|
+
const DOCKER_SECCOMP_PROFILE = {
|
|
20
|
+
defaultAction: "SCMP_ACT_ERRNO",
|
|
21
|
+
defaultErrnoRet: 1,
|
|
22
|
+
archMap: [
|
|
23
|
+
{
|
|
24
|
+
architecture: "SCMP_ARCH_X86_64",
|
|
25
|
+
subArchitectures: ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
architecture: "SCMP_ARCH_AARCH64",
|
|
29
|
+
subArchitectures: ["SCMP_ARCH_ARM"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
architecture: "SCMP_ARCH_MIPS64",
|
|
33
|
+
subArchitectures: ["SCMP_ARCH_MIPS", "SCMP_ARCH_MIPS64N32"]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
architecture: "SCMP_ARCH_MIPS64N32",
|
|
37
|
+
subArchitectures: ["SCMP_ARCH_MIPS", "SCMP_ARCH_MIPS64"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
architecture: "SCMP_ARCH_MIPSEL64",
|
|
41
|
+
subArchitectures: ["SCMP_ARCH_MIPSEL", "SCMP_ARCH_MIPSEL64N32"]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
architecture: "SCMP_ARCH_MIPSEL64N32",
|
|
45
|
+
subArchitectures: ["SCMP_ARCH_MIPSEL", "SCMP_ARCH_MIPSEL64"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
architecture: "SCMP_ARCH_S390X",
|
|
49
|
+
subArchitectures: ["SCMP_ARCH_S390"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
architecture: "SCMP_ARCH_RISCV64",
|
|
53
|
+
subArchitectures: null
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
architecture: "SCMP_ARCH_LOONGARCH64",
|
|
57
|
+
subArchitectures: null
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
syscalls: [
|
|
61
|
+
{
|
|
62
|
+
names: [
|
|
63
|
+
"accept",
|
|
64
|
+
"accept4",
|
|
65
|
+
"access",
|
|
66
|
+
"adjtimex",
|
|
67
|
+
"alarm",
|
|
68
|
+
"bind",
|
|
69
|
+
"brk",
|
|
70
|
+
"cachestat",
|
|
71
|
+
"capget",
|
|
72
|
+
"capset",
|
|
73
|
+
"chdir",
|
|
74
|
+
"chmod",
|
|
75
|
+
"chown",
|
|
76
|
+
"chown32",
|
|
77
|
+
"clock_adjtime",
|
|
78
|
+
"clock_adjtime64",
|
|
79
|
+
"clock_getres",
|
|
80
|
+
"clock_getres_time64",
|
|
81
|
+
"clock_gettime",
|
|
82
|
+
"clock_gettime64",
|
|
83
|
+
"clock_nanosleep",
|
|
84
|
+
"clock_nanosleep_time64",
|
|
85
|
+
"close",
|
|
86
|
+
"close_range",
|
|
87
|
+
"connect",
|
|
88
|
+
"copy_file_range",
|
|
89
|
+
"creat",
|
|
90
|
+
"dup",
|
|
91
|
+
"dup2",
|
|
92
|
+
"dup3",
|
|
93
|
+
"epoll_create",
|
|
94
|
+
"epoll_create1",
|
|
95
|
+
"epoll_ctl",
|
|
96
|
+
"epoll_ctl_old",
|
|
97
|
+
"epoll_pwait",
|
|
98
|
+
"epoll_pwait2",
|
|
99
|
+
"epoll_wait",
|
|
100
|
+
"epoll_wait_old",
|
|
101
|
+
"eventfd",
|
|
102
|
+
"eventfd2",
|
|
103
|
+
"execve",
|
|
104
|
+
"execveat",
|
|
105
|
+
"exit",
|
|
106
|
+
"exit_group",
|
|
107
|
+
"faccessat",
|
|
108
|
+
"faccessat2",
|
|
109
|
+
"fadvise64",
|
|
110
|
+
"fadvise64_64",
|
|
111
|
+
"fallocate",
|
|
112
|
+
"fanotify_mark",
|
|
113
|
+
"fchdir",
|
|
114
|
+
"fchmod",
|
|
115
|
+
"fchmodat",
|
|
116
|
+
"fchmodat2",
|
|
117
|
+
"fchown",
|
|
118
|
+
"fchown32",
|
|
119
|
+
"fchownat",
|
|
120
|
+
"fcntl",
|
|
121
|
+
"fcntl64",
|
|
122
|
+
"fdatasync",
|
|
123
|
+
"fgetxattr",
|
|
124
|
+
"flistxattr",
|
|
125
|
+
"flock",
|
|
126
|
+
"fork",
|
|
127
|
+
"fremovexattr",
|
|
128
|
+
"fsetxattr",
|
|
129
|
+
"fstat",
|
|
130
|
+
"fstat64",
|
|
131
|
+
"fstatat64",
|
|
132
|
+
"fstatfs",
|
|
133
|
+
"fstatfs64",
|
|
134
|
+
"fsync",
|
|
135
|
+
"ftruncate",
|
|
136
|
+
"ftruncate64",
|
|
137
|
+
"futex",
|
|
138
|
+
"futex_requeue",
|
|
139
|
+
"futex_time64",
|
|
140
|
+
"futex_wait",
|
|
141
|
+
"futex_waitv",
|
|
142
|
+
"futex_wake",
|
|
143
|
+
"futimesat",
|
|
144
|
+
"getcpu",
|
|
145
|
+
"getcwd",
|
|
146
|
+
"getdents",
|
|
147
|
+
"getdents64",
|
|
148
|
+
"getegid",
|
|
149
|
+
"getegid32",
|
|
150
|
+
"geteuid",
|
|
151
|
+
"geteuid32",
|
|
152
|
+
"getgid",
|
|
153
|
+
"getgid32",
|
|
154
|
+
"getgroups",
|
|
155
|
+
"getgroups32",
|
|
156
|
+
"getitimer",
|
|
157
|
+
"getpeername",
|
|
158
|
+
"getpgid",
|
|
159
|
+
"getpgrp",
|
|
160
|
+
"getpid",
|
|
161
|
+
"getppid",
|
|
162
|
+
"getpriority",
|
|
163
|
+
"getrandom",
|
|
164
|
+
"getresgid",
|
|
165
|
+
"getresgid32",
|
|
166
|
+
"getresuid",
|
|
167
|
+
"getresuid32",
|
|
168
|
+
"getrlimit",
|
|
169
|
+
"get_robust_list",
|
|
170
|
+
"getrusage",
|
|
171
|
+
"getsid",
|
|
172
|
+
"getsockname",
|
|
173
|
+
"getsockopt",
|
|
174
|
+
"get_thread_area",
|
|
175
|
+
"gettid",
|
|
176
|
+
"gettimeofday",
|
|
177
|
+
"getuid",
|
|
178
|
+
"getuid32",
|
|
179
|
+
"getxattr",
|
|
180
|
+
"getxattrat",
|
|
181
|
+
"inotify_add_watch",
|
|
182
|
+
"inotify_init",
|
|
183
|
+
"inotify_init1",
|
|
184
|
+
"inotify_rm_watch",
|
|
185
|
+
"io_cancel",
|
|
186
|
+
"ioctl",
|
|
187
|
+
"io_destroy",
|
|
188
|
+
"io_getevents",
|
|
189
|
+
"io_pgetevents",
|
|
190
|
+
"io_pgetevents_time64",
|
|
191
|
+
"ioprio_get",
|
|
192
|
+
"ioprio_set",
|
|
193
|
+
"io_setup",
|
|
194
|
+
"io_submit",
|
|
195
|
+
"ipc",
|
|
196
|
+
"kill",
|
|
197
|
+
"landlock_add_rule",
|
|
198
|
+
"landlock_create_ruleset",
|
|
199
|
+
"landlock_restrict_self",
|
|
200
|
+
"lchown",
|
|
201
|
+
"lchown32",
|
|
202
|
+
"lgetxattr",
|
|
203
|
+
"link",
|
|
204
|
+
"linkat",
|
|
205
|
+
"listen",
|
|
206
|
+
"listmount",
|
|
207
|
+
"listxattr",
|
|
208
|
+
"listxattrat",
|
|
209
|
+
"llistxattr",
|
|
210
|
+
"_llseek",
|
|
211
|
+
"lremovexattr",
|
|
212
|
+
"lseek",
|
|
213
|
+
"lsetxattr",
|
|
214
|
+
"lstat",
|
|
215
|
+
"lstat64",
|
|
216
|
+
"madvise",
|
|
217
|
+
"map_shadow_stack",
|
|
218
|
+
"membarrier",
|
|
219
|
+
"memfd_create",
|
|
220
|
+
"memfd_secret",
|
|
221
|
+
"mincore",
|
|
222
|
+
"mkdir",
|
|
223
|
+
"mkdirat",
|
|
224
|
+
"mknod",
|
|
225
|
+
"mknodat",
|
|
226
|
+
"mlock",
|
|
227
|
+
"mlock2",
|
|
228
|
+
"mlockall",
|
|
229
|
+
"mmap",
|
|
230
|
+
"mmap2",
|
|
231
|
+
"mprotect",
|
|
232
|
+
"mq_getsetattr",
|
|
233
|
+
"mq_notify",
|
|
234
|
+
"mq_open",
|
|
235
|
+
"mq_timedreceive",
|
|
236
|
+
"mq_timedreceive_time64",
|
|
237
|
+
"mq_timedsend",
|
|
238
|
+
"mq_timedsend_time64",
|
|
239
|
+
"mq_unlink",
|
|
240
|
+
"mremap",
|
|
241
|
+
"mseal",
|
|
242
|
+
"msgctl",
|
|
243
|
+
"msgget",
|
|
244
|
+
"msgrcv",
|
|
245
|
+
"msgsnd",
|
|
246
|
+
"msync",
|
|
247
|
+
"munlock",
|
|
248
|
+
"munlockall",
|
|
249
|
+
"munmap",
|
|
250
|
+
"name_to_handle_at",
|
|
251
|
+
"nanosleep",
|
|
252
|
+
"newfstatat",
|
|
253
|
+
"_newselect",
|
|
254
|
+
"open",
|
|
255
|
+
"openat",
|
|
256
|
+
"openat2",
|
|
257
|
+
"pause",
|
|
258
|
+
"pidfd_open",
|
|
259
|
+
"pidfd_send_signal",
|
|
260
|
+
"pipe",
|
|
261
|
+
"pipe2",
|
|
262
|
+
"pkey_alloc",
|
|
263
|
+
"pkey_free",
|
|
264
|
+
"pkey_mprotect",
|
|
265
|
+
"poll",
|
|
266
|
+
"ppoll",
|
|
267
|
+
"ppoll_time64",
|
|
268
|
+
"prctl",
|
|
269
|
+
"pread64",
|
|
270
|
+
"preadv",
|
|
271
|
+
"preadv2",
|
|
272
|
+
"prlimit64",
|
|
273
|
+
"process_mrelease",
|
|
274
|
+
"pselect6",
|
|
275
|
+
"pselect6_time64",
|
|
276
|
+
"pwrite64",
|
|
277
|
+
"pwritev",
|
|
278
|
+
"pwritev2",
|
|
279
|
+
"read",
|
|
280
|
+
"readahead",
|
|
281
|
+
"readlink",
|
|
282
|
+
"readlinkat",
|
|
283
|
+
"readv",
|
|
284
|
+
"recv",
|
|
285
|
+
"recvfrom",
|
|
286
|
+
"recvmmsg",
|
|
287
|
+
"recvmmsg_time64",
|
|
288
|
+
"recvmsg",
|
|
289
|
+
"remap_file_pages",
|
|
290
|
+
"removexattr",
|
|
291
|
+
"removexattrat",
|
|
292
|
+
"rename",
|
|
293
|
+
"renameat",
|
|
294
|
+
"renameat2",
|
|
295
|
+
"restart_syscall",
|
|
296
|
+
"riscv_hwprobe",
|
|
297
|
+
"rmdir",
|
|
298
|
+
"rseq",
|
|
299
|
+
"rt_sigaction",
|
|
300
|
+
"rt_sigpending",
|
|
301
|
+
"rt_sigprocmask",
|
|
302
|
+
"rt_sigqueueinfo",
|
|
303
|
+
"rt_sigreturn",
|
|
304
|
+
"rt_sigsuspend",
|
|
305
|
+
"rt_sigtimedwait",
|
|
306
|
+
"rt_sigtimedwait_time64",
|
|
307
|
+
"rt_tgsigqueueinfo",
|
|
308
|
+
"sched_getaffinity",
|
|
309
|
+
"sched_getattr",
|
|
310
|
+
"sched_getparam",
|
|
311
|
+
"sched_get_priority_max",
|
|
312
|
+
"sched_get_priority_min",
|
|
313
|
+
"sched_getscheduler",
|
|
314
|
+
"sched_rr_get_interval",
|
|
315
|
+
"sched_rr_get_interval_time64",
|
|
316
|
+
"sched_setaffinity",
|
|
317
|
+
"sched_setattr",
|
|
318
|
+
"sched_setparam",
|
|
319
|
+
"sched_setscheduler",
|
|
320
|
+
"sched_yield",
|
|
321
|
+
"seccomp",
|
|
322
|
+
"select",
|
|
323
|
+
"semctl",
|
|
324
|
+
"semget",
|
|
325
|
+
"semop",
|
|
326
|
+
"semtimedop",
|
|
327
|
+
"semtimedop_time64",
|
|
328
|
+
"send",
|
|
329
|
+
"sendfile",
|
|
330
|
+
"sendfile64",
|
|
331
|
+
"sendmmsg",
|
|
332
|
+
"sendmsg",
|
|
333
|
+
"sendto",
|
|
334
|
+
"setfsgid",
|
|
335
|
+
"setfsgid32",
|
|
336
|
+
"setfsuid",
|
|
337
|
+
"setfsuid32",
|
|
338
|
+
"setgid",
|
|
339
|
+
"setgid32",
|
|
340
|
+
"setgroups",
|
|
341
|
+
"setgroups32",
|
|
342
|
+
"setitimer",
|
|
343
|
+
"setpgid",
|
|
344
|
+
"setpriority",
|
|
345
|
+
"setregid",
|
|
346
|
+
"setregid32",
|
|
347
|
+
"setresgid",
|
|
348
|
+
"setresgid32",
|
|
349
|
+
"setresuid",
|
|
350
|
+
"setresuid32",
|
|
351
|
+
"setreuid",
|
|
352
|
+
"setreuid32",
|
|
353
|
+
"setrlimit",
|
|
354
|
+
"set_robust_list",
|
|
355
|
+
"setsid",
|
|
356
|
+
"setsockopt",
|
|
357
|
+
"set_thread_area",
|
|
358
|
+
"set_tid_address",
|
|
359
|
+
"setuid",
|
|
360
|
+
"setuid32",
|
|
361
|
+
"setxattr",
|
|
362
|
+
"setxattrat",
|
|
363
|
+
"shmat",
|
|
364
|
+
"shmctl",
|
|
365
|
+
"shmdt",
|
|
366
|
+
"shmget",
|
|
367
|
+
"shutdown",
|
|
368
|
+
"sigaltstack",
|
|
369
|
+
"signalfd",
|
|
370
|
+
"signalfd4",
|
|
371
|
+
"sigprocmask",
|
|
372
|
+
"sigreturn",
|
|
373
|
+
"socketcall",
|
|
374
|
+
"socketpair",
|
|
375
|
+
"splice",
|
|
376
|
+
"stat",
|
|
377
|
+
"stat64",
|
|
378
|
+
"statfs",
|
|
379
|
+
"statfs64",
|
|
380
|
+
"statmount",
|
|
381
|
+
"statx",
|
|
382
|
+
"symlink",
|
|
383
|
+
"symlinkat",
|
|
384
|
+
"sync",
|
|
385
|
+
"sync_file_range",
|
|
386
|
+
"syncfs",
|
|
387
|
+
"sysinfo",
|
|
388
|
+
"tee",
|
|
389
|
+
"tgkill",
|
|
390
|
+
"time",
|
|
391
|
+
"timer_create",
|
|
392
|
+
"timer_delete",
|
|
393
|
+
"timer_getoverrun",
|
|
394
|
+
"timer_gettime",
|
|
395
|
+
"timer_gettime64",
|
|
396
|
+
"timer_settime",
|
|
397
|
+
"timer_settime64",
|
|
398
|
+
"timerfd_create",
|
|
399
|
+
"timerfd_gettime",
|
|
400
|
+
"timerfd_gettime64",
|
|
401
|
+
"timerfd_settime",
|
|
402
|
+
"timerfd_settime64",
|
|
403
|
+
"times",
|
|
404
|
+
"tkill",
|
|
405
|
+
"truncate",
|
|
406
|
+
"truncate64",
|
|
407
|
+
"ugetrlimit",
|
|
408
|
+
"umask",
|
|
409
|
+
"uname",
|
|
410
|
+
"unlink",
|
|
411
|
+
"unlinkat",
|
|
412
|
+
"uretprobe",
|
|
413
|
+
"utime",
|
|
414
|
+
"utimensat",
|
|
415
|
+
"utimensat_time64",
|
|
416
|
+
"utimes",
|
|
417
|
+
"vfork",
|
|
418
|
+
"vmsplice",
|
|
419
|
+
"wait4",
|
|
420
|
+
"waitid",
|
|
421
|
+
"waitpid",
|
|
422
|
+
"write",
|
|
423
|
+
"writev"
|
|
424
|
+
],
|
|
425
|
+
action: "SCMP_ACT_ALLOW"
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
names: [
|
|
429
|
+
"process_vm_readv",
|
|
430
|
+
"process_vm_writev",
|
|
431
|
+
"ptrace"
|
|
432
|
+
],
|
|
433
|
+
action: "SCMP_ACT_ALLOW",
|
|
434
|
+
includes: { minKernel: "4.8" }
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
names: ["socket"],
|
|
438
|
+
action: "SCMP_ACT_ALLOW",
|
|
439
|
+
args: [{
|
|
440
|
+
index: 0,
|
|
441
|
+
value: 3,
|
|
442
|
+
op: "SCMP_CMP_LT"
|
|
443
|
+
}]
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
names: ["socket"],
|
|
447
|
+
action: "SCMP_ACT_ALLOW",
|
|
448
|
+
args: [{
|
|
449
|
+
index: 0,
|
|
450
|
+
value: 7,
|
|
451
|
+
op: "SCMP_CMP_EQ"
|
|
452
|
+
}]
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
names: ["socket"],
|
|
456
|
+
action: "SCMP_ACT_ALLOW",
|
|
457
|
+
args: [{
|
|
458
|
+
index: 0,
|
|
459
|
+
value: 8,
|
|
460
|
+
op: "SCMP_CMP_EQ"
|
|
461
|
+
}]
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
names: ["socket"],
|
|
465
|
+
action: "SCMP_ACT_ALLOW",
|
|
466
|
+
args: [{
|
|
467
|
+
index: 0,
|
|
468
|
+
value: 9,
|
|
469
|
+
op: "SCMP_CMP_EQ"
|
|
470
|
+
}]
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
names: ["socket"],
|
|
474
|
+
action: "SCMP_ACT_ALLOW",
|
|
475
|
+
args: [{
|
|
476
|
+
index: 0,
|
|
477
|
+
value: 10,
|
|
478
|
+
op: "SCMP_CMP_EQ"
|
|
479
|
+
}]
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
names: ["socket"],
|
|
483
|
+
action: "SCMP_ACT_ALLOW",
|
|
484
|
+
args: [{
|
|
485
|
+
index: 0,
|
|
486
|
+
value: 13,
|
|
487
|
+
op: "SCMP_CMP_EQ"
|
|
488
|
+
}]
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
names: ["socket"],
|
|
492
|
+
action: "SCMP_ACT_ALLOW",
|
|
493
|
+
args: [{
|
|
494
|
+
index: 0,
|
|
495
|
+
value: 14,
|
|
496
|
+
op: "SCMP_CMP_EQ"
|
|
497
|
+
}]
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
names: ["socket"],
|
|
501
|
+
action: "SCMP_ACT_ALLOW",
|
|
502
|
+
args: [{
|
|
503
|
+
index: 0,
|
|
504
|
+
value: 15,
|
|
505
|
+
op: "SCMP_CMP_EQ"
|
|
506
|
+
}]
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
names: ["socket"],
|
|
510
|
+
action: "SCMP_ACT_ALLOW",
|
|
511
|
+
args: [{
|
|
512
|
+
index: 0,
|
|
513
|
+
value: 16,
|
|
514
|
+
op: "SCMP_CMP_EQ"
|
|
515
|
+
}]
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
names: ["socket"],
|
|
519
|
+
action: "SCMP_ACT_ALLOW",
|
|
520
|
+
args: [{
|
|
521
|
+
index: 0,
|
|
522
|
+
value: 17,
|
|
523
|
+
op: "SCMP_CMP_EQ"
|
|
524
|
+
}]
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
names: ["socket"],
|
|
528
|
+
action: "SCMP_ACT_ALLOW",
|
|
529
|
+
args: [{
|
|
530
|
+
index: 0,
|
|
531
|
+
value: 18,
|
|
532
|
+
op: "SCMP_CMP_EQ"
|
|
533
|
+
}]
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
names: ["socket"],
|
|
537
|
+
action: "SCMP_ACT_ALLOW",
|
|
538
|
+
args: [{
|
|
539
|
+
index: 0,
|
|
540
|
+
value: 21,
|
|
541
|
+
op: "SCMP_CMP_EQ"
|
|
542
|
+
}]
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
names: ["socket"],
|
|
546
|
+
action: "SCMP_ACT_ALLOW",
|
|
547
|
+
args: [{
|
|
548
|
+
index: 0,
|
|
549
|
+
value: 22,
|
|
550
|
+
op: "SCMP_CMP_EQ"
|
|
551
|
+
}]
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
names: ["socket"],
|
|
555
|
+
action: "SCMP_ACT_ALLOW",
|
|
556
|
+
args: [{
|
|
557
|
+
index: 0,
|
|
558
|
+
value: 24,
|
|
559
|
+
op: "SCMP_CMP_EQ"
|
|
560
|
+
}]
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
names: ["socket"],
|
|
564
|
+
action: "SCMP_ACT_ALLOW",
|
|
565
|
+
args: [{
|
|
566
|
+
index: 0,
|
|
567
|
+
value: 26,
|
|
568
|
+
op: "SCMP_CMP_EQ"
|
|
569
|
+
}]
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
names: ["socket"],
|
|
573
|
+
action: "SCMP_ACT_ALLOW",
|
|
574
|
+
args: [{
|
|
575
|
+
index: 0,
|
|
576
|
+
value: 27,
|
|
577
|
+
op: "SCMP_CMP_EQ"
|
|
578
|
+
}]
|
|
579
|
+
},
|
|
580
|
+
{
|
|
581
|
+
names: ["socket"],
|
|
582
|
+
action: "SCMP_ACT_ALLOW",
|
|
583
|
+
args: [{
|
|
584
|
+
index: 0,
|
|
585
|
+
value: 28,
|
|
586
|
+
op: "SCMP_CMP_EQ"
|
|
587
|
+
}]
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
names: ["socket"],
|
|
591
|
+
action: "SCMP_ACT_ALLOW",
|
|
592
|
+
args: [{
|
|
593
|
+
index: 0,
|
|
594
|
+
value: 29,
|
|
595
|
+
op: "SCMP_CMP_EQ"
|
|
596
|
+
}]
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
names: ["socket"],
|
|
600
|
+
action: "SCMP_ACT_ALLOW",
|
|
601
|
+
args: [{
|
|
602
|
+
index: 0,
|
|
603
|
+
value: 30,
|
|
604
|
+
op: "SCMP_CMP_EQ"
|
|
605
|
+
}]
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
names: ["socket"],
|
|
609
|
+
action: "SCMP_ACT_ALLOW",
|
|
610
|
+
args: [{
|
|
611
|
+
index: 0,
|
|
612
|
+
value: 31,
|
|
613
|
+
op: "SCMP_CMP_EQ"
|
|
614
|
+
}]
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
names: ["socket"],
|
|
618
|
+
action: "SCMP_ACT_ALLOW",
|
|
619
|
+
args: [{
|
|
620
|
+
index: 0,
|
|
621
|
+
value: 32,
|
|
622
|
+
op: "SCMP_CMP_EQ"
|
|
623
|
+
}]
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
names: ["socket"],
|
|
627
|
+
action: "SCMP_ACT_ALLOW",
|
|
628
|
+
args: [{
|
|
629
|
+
index: 0,
|
|
630
|
+
value: 33,
|
|
631
|
+
op: "SCMP_CMP_EQ"
|
|
632
|
+
}]
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
names: ["socket"],
|
|
636
|
+
action: "SCMP_ACT_ALLOW",
|
|
637
|
+
args: [{
|
|
638
|
+
index: 0,
|
|
639
|
+
value: 35,
|
|
640
|
+
op: "SCMP_CMP_EQ"
|
|
641
|
+
}]
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
names: ["socket"],
|
|
645
|
+
action: "SCMP_ACT_ALLOW",
|
|
646
|
+
args: [{
|
|
647
|
+
index: 0,
|
|
648
|
+
value: 36,
|
|
649
|
+
op: "SCMP_CMP_EQ"
|
|
650
|
+
}]
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
names: ["socket"],
|
|
654
|
+
action: "SCMP_ACT_ALLOW",
|
|
655
|
+
args: [{
|
|
656
|
+
index: 0,
|
|
657
|
+
value: 39,
|
|
658
|
+
op: "SCMP_CMP_EQ"
|
|
659
|
+
}]
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
names: ["socket"],
|
|
663
|
+
action: "SCMP_ACT_ALLOW",
|
|
664
|
+
args: [{
|
|
665
|
+
index: 0,
|
|
666
|
+
value: 41,
|
|
667
|
+
op: "SCMP_CMP_EQ"
|
|
668
|
+
}]
|
|
669
|
+
},
|
|
670
|
+
{
|
|
671
|
+
names: ["socket"],
|
|
672
|
+
action: "SCMP_ACT_ALLOW",
|
|
673
|
+
args: [{
|
|
674
|
+
index: 0,
|
|
675
|
+
value: 42,
|
|
676
|
+
op: "SCMP_CMP_EQ"
|
|
677
|
+
}]
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
names: ["socket"],
|
|
681
|
+
action: "SCMP_ACT_ALLOW",
|
|
682
|
+
args: [{
|
|
683
|
+
index: 0,
|
|
684
|
+
value: 43,
|
|
685
|
+
op: "SCMP_CMP_EQ"
|
|
686
|
+
}]
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
names: ["socket"],
|
|
690
|
+
action: "SCMP_ACT_ALLOW",
|
|
691
|
+
args: [{
|
|
692
|
+
index: 0,
|
|
693
|
+
value: 44,
|
|
694
|
+
op: "SCMP_CMP_EQ"
|
|
695
|
+
}]
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
names: ["socket"],
|
|
699
|
+
action: "SCMP_ACT_ALLOW",
|
|
700
|
+
args: [{
|
|
701
|
+
index: 0,
|
|
702
|
+
value: 45,
|
|
703
|
+
op: "SCMP_CMP_EQ"
|
|
704
|
+
}]
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
names: ["personality"],
|
|
708
|
+
action: "SCMP_ACT_ALLOW",
|
|
709
|
+
args: [{
|
|
710
|
+
index: 0,
|
|
711
|
+
value: 0,
|
|
712
|
+
op: "SCMP_CMP_EQ"
|
|
713
|
+
}]
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
names: ["personality"],
|
|
717
|
+
action: "SCMP_ACT_ALLOW",
|
|
718
|
+
args: [{
|
|
719
|
+
index: 0,
|
|
720
|
+
value: 8,
|
|
721
|
+
op: "SCMP_CMP_EQ"
|
|
722
|
+
}]
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
names: ["personality"],
|
|
726
|
+
action: "SCMP_ACT_ALLOW",
|
|
727
|
+
args: [{
|
|
728
|
+
index: 0,
|
|
729
|
+
value: 131072,
|
|
730
|
+
op: "SCMP_CMP_EQ"
|
|
731
|
+
}]
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
names: ["personality"],
|
|
735
|
+
action: "SCMP_ACT_ALLOW",
|
|
736
|
+
args: [{
|
|
737
|
+
index: 0,
|
|
738
|
+
value: 131080,
|
|
739
|
+
op: "SCMP_CMP_EQ"
|
|
740
|
+
}]
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
names: ["personality"],
|
|
744
|
+
action: "SCMP_ACT_ALLOW",
|
|
745
|
+
args: [{
|
|
746
|
+
index: 0,
|
|
747
|
+
value: 4294967295,
|
|
748
|
+
op: "SCMP_CMP_EQ"
|
|
749
|
+
}]
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
names: ["sync_file_range2", "swapcontext"],
|
|
753
|
+
action: "SCMP_ACT_ALLOW",
|
|
754
|
+
includes: { arches: ["ppc64le"] }
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
names: [
|
|
758
|
+
"arm_fadvise64_64",
|
|
759
|
+
"arm_sync_file_range",
|
|
760
|
+
"sync_file_range2",
|
|
761
|
+
"breakpoint",
|
|
762
|
+
"cacheflush",
|
|
763
|
+
"set_tls"
|
|
764
|
+
],
|
|
765
|
+
action: "SCMP_ACT_ALLOW",
|
|
766
|
+
includes: { arches: ["arm", "arm64"] }
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
names: ["arch_prctl"],
|
|
770
|
+
action: "SCMP_ACT_ALLOW",
|
|
771
|
+
includes: { arches: ["amd64", "x32"] }
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
names: ["modify_ldt"],
|
|
775
|
+
action: "SCMP_ACT_ALLOW",
|
|
776
|
+
includes: { arches: [
|
|
777
|
+
"amd64",
|
|
778
|
+
"x32",
|
|
779
|
+
"x86"
|
|
780
|
+
] }
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
names: [
|
|
784
|
+
"s390_pci_mmio_read",
|
|
785
|
+
"s390_pci_mmio_write",
|
|
786
|
+
"s390_runtime_instr"
|
|
787
|
+
],
|
|
788
|
+
action: "SCMP_ACT_ALLOW",
|
|
789
|
+
includes: { arches: ["s390", "s390x"] }
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
names: ["riscv_flush_icache"],
|
|
793
|
+
action: "SCMP_ACT_ALLOW",
|
|
794
|
+
includes: { arches: ["riscv64"] }
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
names: ["open_by_handle_at"],
|
|
798
|
+
action: "SCMP_ACT_ALLOW",
|
|
799
|
+
includes: { caps: ["CAP_DAC_READ_SEARCH"] }
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
names: [
|
|
803
|
+
"bpf",
|
|
804
|
+
"clone",
|
|
805
|
+
"clone3",
|
|
806
|
+
"fanotify_init",
|
|
807
|
+
"fsconfig",
|
|
808
|
+
"fsmount",
|
|
809
|
+
"fsopen",
|
|
810
|
+
"fspick",
|
|
811
|
+
"lookup_dcookie",
|
|
812
|
+
"lsm_get_self_attr",
|
|
813
|
+
"lsm_list_modules",
|
|
814
|
+
"lsm_set_self_attr",
|
|
815
|
+
"mount",
|
|
816
|
+
"mount_setattr",
|
|
817
|
+
"move_mount",
|
|
818
|
+
"open_tree",
|
|
819
|
+
"perf_event_open",
|
|
820
|
+
"quotactl",
|
|
821
|
+
"quotactl_fd",
|
|
822
|
+
"setdomainname",
|
|
823
|
+
"sethostname",
|
|
824
|
+
"setns",
|
|
825
|
+
"syslog",
|
|
826
|
+
"umount",
|
|
827
|
+
"umount2",
|
|
828
|
+
"unshare"
|
|
829
|
+
],
|
|
830
|
+
action: "SCMP_ACT_ALLOW",
|
|
831
|
+
includes: { caps: ["CAP_SYS_ADMIN"] }
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
names: ["clone"],
|
|
835
|
+
action: "SCMP_ACT_ALLOW",
|
|
836
|
+
args: [{
|
|
837
|
+
index: 0,
|
|
838
|
+
value: 2114060288,
|
|
839
|
+
op: "SCMP_CMP_MASKED_EQ"
|
|
840
|
+
}],
|
|
841
|
+
excludes: {
|
|
842
|
+
caps: ["CAP_SYS_ADMIN"],
|
|
843
|
+
arches: ["s390", "s390x"]
|
|
844
|
+
}
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
names: ["clone"],
|
|
848
|
+
action: "SCMP_ACT_ALLOW",
|
|
849
|
+
args: [{
|
|
850
|
+
index: 1,
|
|
851
|
+
value: 2114060288,
|
|
852
|
+
op: "SCMP_CMP_MASKED_EQ"
|
|
853
|
+
}],
|
|
854
|
+
comment: "s390 parameter ordering for clone is different",
|
|
855
|
+
includes: { arches: ["s390", "s390x"] },
|
|
856
|
+
excludes: { caps: ["CAP_SYS_ADMIN"] }
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
names: ["clone3"],
|
|
860
|
+
action: "SCMP_ACT_ERRNO",
|
|
861
|
+
errnoRet: 38,
|
|
862
|
+
excludes: { caps: ["CAP_SYS_ADMIN"] }
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
names: ["reboot"],
|
|
866
|
+
action: "SCMP_ACT_ALLOW",
|
|
867
|
+
includes: { caps: ["CAP_SYS_BOOT"] }
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
names: ["chroot"],
|
|
871
|
+
action: "SCMP_ACT_ALLOW",
|
|
872
|
+
includes: { caps: ["CAP_SYS_CHROOT"] }
|
|
873
|
+
},
|
|
874
|
+
{
|
|
875
|
+
names: [
|
|
876
|
+
"delete_module",
|
|
877
|
+
"init_module",
|
|
878
|
+
"finit_module"
|
|
879
|
+
],
|
|
880
|
+
action: "SCMP_ACT_ALLOW",
|
|
881
|
+
includes: { caps: ["CAP_SYS_MODULE"] }
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
names: ["acct"],
|
|
885
|
+
action: "SCMP_ACT_ALLOW",
|
|
886
|
+
includes: { caps: ["CAP_SYS_PACCT"] }
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
names: [
|
|
890
|
+
"kcmp",
|
|
891
|
+
"pidfd_getfd",
|
|
892
|
+
"process_madvise",
|
|
893
|
+
"process_vm_readv",
|
|
894
|
+
"process_vm_writev",
|
|
895
|
+
"ptrace"
|
|
896
|
+
],
|
|
897
|
+
action: "SCMP_ACT_ALLOW",
|
|
898
|
+
includes: { caps: ["CAP_SYS_PTRACE"] }
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
names: ["iopl", "ioperm"],
|
|
902
|
+
action: "SCMP_ACT_ALLOW",
|
|
903
|
+
includes: { caps: ["CAP_SYS_RAWIO"] }
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
names: [
|
|
907
|
+
"settimeofday",
|
|
908
|
+
"stime",
|
|
909
|
+
"clock_settime",
|
|
910
|
+
"clock_settime64"
|
|
911
|
+
],
|
|
912
|
+
action: "SCMP_ACT_ALLOW",
|
|
913
|
+
includes: { caps: ["CAP_SYS_TIME"] }
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
names: ["vhangup"],
|
|
917
|
+
action: "SCMP_ACT_ALLOW",
|
|
918
|
+
includes: { caps: ["CAP_SYS_TTY_CONFIG"] }
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
names: [
|
|
922
|
+
"get_mempolicy",
|
|
923
|
+
"mbind",
|
|
924
|
+
"set_mempolicy",
|
|
925
|
+
"set_mempolicy_home_node"
|
|
926
|
+
],
|
|
927
|
+
action: "SCMP_ACT_ALLOW",
|
|
928
|
+
includes: { caps: ["CAP_SYS_NICE"] }
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
names: ["syslog"],
|
|
932
|
+
action: "SCMP_ACT_ALLOW",
|
|
933
|
+
includes: { caps: ["CAP_SYSLOG"] }
|
|
934
|
+
},
|
|
935
|
+
{
|
|
936
|
+
names: ["bpf"],
|
|
937
|
+
action: "SCMP_ACT_ALLOW",
|
|
938
|
+
includes: { caps: ["CAP_BPF"] }
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
names: ["perf_event_open"],
|
|
942
|
+
action: "SCMP_ACT_ALLOW",
|
|
943
|
+
includes: { caps: ["CAP_PERFMON"] }
|
|
944
|
+
}
|
|
945
|
+
]
|
|
946
|
+
};
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region ../sandbox-docker/src/engine.ts
|
|
949
|
+
let seccompFile;
|
|
950
|
+
/**
|
|
951
|
+
* Docker's default seccomp profile as a file Podman can read, written once per content under the
|
|
952
|
+
* temp directory (a compiled reeval has no real file of its own to point at).
|
|
953
|
+
*/
|
|
954
|
+
function dockerSeccompFile() {
|
|
955
|
+
if (seccompFile && existsSync(seccompFile)) return seccompFile;
|
|
956
|
+
const json = JSON.stringify(DOCKER_SECCOMP_PROFILE);
|
|
957
|
+
const hash = createHash("sha256").update(json).digest("hex").slice(0, 16);
|
|
958
|
+
const path = join(tmpdir(), `reeval-seccomp-${hash}.json`);
|
|
959
|
+
if (!existsSync(path)) {
|
|
960
|
+
const partial = `${path}.${process.pid}.tmp`;
|
|
961
|
+
writeFileSync(partial, json);
|
|
962
|
+
renameSync(partial, path);
|
|
963
|
+
}
|
|
964
|
+
seccompFile = path;
|
|
965
|
+
return path;
|
|
966
|
+
}
|
|
967
|
+
const ENGINES = {
|
|
968
|
+
docker: {
|
|
969
|
+
bin: "docker",
|
|
970
|
+
name: "Docker",
|
|
971
|
+
fn: "docker",
|
|
972
|
+
stateFormat: "{{.ServerVersion}} {{.OSType}}",
|
|
973
|
+
capacityFormat: "{{.NCPU}} {{.MemTotal}}",
|
|
974
|
+
imageId: ".Id",
|
|
975
|
+
rmForce: ["rm", "-f"],
|
|
976
|
+
runArgs: () => [],
|
|
977
|
+
createsWorkdir: true
|
|
978
|
+
},
|
|
979
|
+
podman: {
|
|
980
|
+
bin: "podman",
|
|
981
|
+
name: "Podman",
|
|
982
|
+
fn: "podman",
|
|
983
|
+
stateFormat: "{{.Version.Version}} {{.Host.OS}}",
|
|
984
|
+
capacityFormat: "{{.Host.CPUs}} {{.Host.MemTotal}}",
|
|
985
|
+
imageId: ".ID",
|
|
986
|
+
rmForce: [
|
|
987
|
+
"rm",
|
|
988
|
+
"-f",
|
|
989
|
+
"--time",
|
|
990
|
+
"0"
|
|
991
|
+
],
|
|
992
|
+
runArgs: () => [
|
|
993
|
+
"--network",
|
|
994
|
+
"bridge",
|
|
995
|
+
"--security-opt",
|
|
996
|
+
`seccomp=${dockerSeccompFile()}`
|
|
997
|
+
],
|
|
998
|
+
createsWorkdir: false
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
const engineSpec = (engine = "docker") => ENGINES[engine];
|
|
1002
|
+
/**
|
|
1003
|
+
* The `CONTAINER_HOST` that reaches a `podman machine` straight through its API socket (macOS) or
|
|
1004
|
+
* named pipe (Windows), from `podman machine inspect`'s JSON.
|
|
1005
|
+
*/
|
|
1006
|
+
function machineHost(inspect, platform) {
|
|
1007
|
+
const machine = Array.isArray(inspect) ? inspect[0] : void 0;
|
|
1008
|
+
const info = machine?.ConnectionInfo;
|
|
1009
|
+
const name = machine?.Name ?? "podman-machine-default";
|
|
1010
|
+
if (platform === "darwin" && info?.PodmanSocket?.Path) {
|
|
1011
|
+
const path = info.PodmanSocket.Path;
|
|
1012
|
+
return {
|
|
1013
|
+
host: `unix://${path}`,
|
|
1014
|
+
path,
|
|
1015
|
+
name
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
if (platform === "win32" && info?.PodmanPipe?.Path) {
|
|
1019
|
+
const path = info.PodmanPipe.Path;
|
|
1020
|
+
return {
|
|
1021
|
+
host: `npipe://${path.replaceAll("\\", "/")}`,
|
|
1022
|
+
path,
|
|
1023
|
+
name
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* The environment every podman command gets. On macOS and Windows the client talks to the
|
|
1030
|
+
* machine's VM over SSH, a connection per command, and sshd's `MaxStartups` drops some of a burst
|
|
1031
|
+
* (exit 125, `ssh: handshake failed`), so commands go through the machine's API socket instead.
|
|
1032
|
+
* Not when the user picked a host (`CONTAINER_HOST`, `CONTAINER_CONNECTION`, or a default
|
|
1033
|
+
* connection that isn't the machine's), or the socket isn't there.
|
|
1034
|
+
*/
|
|
1035
|
+
function podmanHostEnv(inputs) {
|
|
1036
|
+
const { platform, env } = inputs;
|
|
1037
|
+
if (platform !== "darwin" && platform !== "win32") return {};
|
|
1038
|
+
if (env.CONTAINER_HOST || env.CONTAINER_CONNECTION) return {};
|
|
1039
|
+
const machine = machineHost(inputs.inspect(), platform);
|
|
1040
|
+
if (!machine || !inputs.exists(machine.path)) return {};
|
|
1041
|
+
const connections = inputs.connections();
|
|
1042
|
+
const chosen = Array.isArray(connections) ? connections.find((c) => c.Default)?.Name : void 0;
|
|
1043
|
+
if (chosen && chosen !== machine.name && chosen !== `${machine.name}-root`) return {};
|
|
1044
|
+
return { CONTAINER_HOST: machine.host };
|
|
1045
|
+
}
|
|
1046
|
+
const readJson = (bin, args) => {
|
|
1047
|
+
try {
|
|
1048
|
+
const out = execFileSync(bin, args, {
|
|
1049
|
+
encoding: "utf8",
|
|
1050
|
+
stdio: [
|
|
1051
|
+
"ignore",
|
|
1052
|
+
"pipe",
|
|
1053
|
+
"ignore"
|
|
1054
|
+
],
|
|
1055
|
+
timeout: 1e4,
|
|
1056
|
+
windowsHide: true
|
|
1057
|
+
});
|
|
1058
|
+
return JSON.parse(out);
|
|
1059
|
+
} catch {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
};
|
|
1063
|
+
let podmanEnv;
|
|
1064
|
+
/** What every command on `engine` adds to its environment; read once per process. */
|
|
1065
|
+
function engineEnv(engine = "docker") {
|
|
1066
|
+
if (engine !== "podman") return {};
|
|
1067
|
+
podmanEnv ??= podmanHostEnv({
|
|
1068
|
+
platform: process.platform,
|
|
1069
|
+
env: process.env,
|
|
1070
|
+
inspect: () => readJson("podman", ["machine", "inspect"]),
|
|
1071
|
+
connections: () => readJson("podman", [
|
|
1072
|
+
"system",
|
|
1073
|
+
"connection",
|
|
1074
|
+
"list",
|
|
1075
|
+
"--format",
|
|
1076
|
+
"json"
|
|
1077
|
+
]),
|
|
1078
|
+
exists: existsSync
|
|
1079
|
+
});
|
|
1080
|
+
return podmanEnv;
|
|
1081
|
+
}
|
|
1082
|
+
/** Docker's ids are `sha256:…`; Podman's are the bare hex. */
|
|
1083
|
+
const normalizeImageId = (id) => id.includes(":") ? id : `sha256:${id}`;
|
|
1084
|
+
//#endregion
|
|
16
1085
|
//#region ../sandbox-docker/src/build-failure.ts
|
|
17
1086
|
var ImageBuildError = class extends Error {
|
|
18
1087
|
name = "ImageBuildError";
|
|
@@ -26,6 +1095,7 @@ const stripAnsi = (text) => text.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "");
|
|
|
26
1095
|
/** The failing step and its output from `--progress plain` build output. */
|
|
27
1096
|
function parseBuildOutput(text) {
|
|
28
1097
|
const lines = stripAnsi(text).split(/\r?\n/);
|
|
1098
|
+
if (!lines.some((l) => /^(?:#\d+ )?ERROR: /.test(l)) && lines.some((l) => l.startsWith("Error: "))) return parseBuildahOutput(lines);
|
|
29
1099
|
let step = null;
|
|
30
1100
|
let error = null;
|
|
31
1101
|
for (const line of lines) {
|
|
@@ -46,14 +1116,35 @@ function parseBuildOutput(text) {
|
|
|
46
1116
|
exitCode: code ? Number(code) : null
|
|
47
1117
|
};
|
|
48
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* buildah's output (`podman build`): `STEP n/m: …` headers, each step's output as is, then
|
|
1121
|
+
* `Error: building at STEP "…": …`. Its own warnings are `time="…" level=…` lines.
|
|
1122
|
+
*/
|
|
1123
|
+
function parseBuildahOutput(lines) {
|
|
1124
|
+
let error = null;
|
|
1125
|
+
let output = [];
|
|
1126
|
+
for (const line of lines) if (/^STEP \d+\/\d+: /.test(line)) output = [];
|
|
1127
|
+
else if (line.startsWith("Error: ")) error = line.slice(7);
|
|
1128
|
+
else if (!error && line.trim() && !/^time="[^"]*" level=|^--> /.test(line)) output.push(line);
|
|
1129
|
+
error = (error ?? "").replace(/^building at STEP "(?:[^"\\]|\\.)*": /, "").replace(/ &imagebuilder\.Step\{.*$/, "").trim() || null;
|
|
1130
|
+
const code = /exit status (\d+)$/.exec(error ?? "")?.[1];
|
|
1131
|
+
return {
|
|
1132
|
+
error,
|
|
1133
|
+
output: output.slice(-8),
|
|
1134
|
+
exitCode: code ? Number(code) : null
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
49
1137
|
/** ENOSPC or a SIGKILL (137, usually the out-of-memory killer), from anything the build printed. */
|
|
50
1138
|
function failureCause(text, exitCode) {
|
|
51
1139
|
if (/no space left on device|ENOSPC/i.test(text)) return "enospc";
|
|
52
|
-
if (exitCode === 137 || /exit(?:ed| code
|
|
1140
|
+
if (exitCode === 137 || /exit(?:ed| code:?| status) 137\b|signal: killed|OOMKilled/i.test(text)) return "oom";
|
|
53
1141
|
return null;
|
|
54
1142
|
}
|
|
55
|
-
const causeLine = (cause
|
|
56
|
-
const
|
|
1143
|
+
const causeLine = (cause, engine) => {
|
|
1144
|
+
const { name, bin } = engineSpec(engine);
|
|
1145
|
+
return cause === "enospc" ? `out of disk space (ENOSPC): free space for ${name} (${bin} system df, reeval clean --images) and rerun` : `killed (exit 137): usually out of memory; give ${name} more memory or lower the concurrency`;
|
|
1146
|
+
};
|
|
1147
|
+
const DETERMINISTIC = /did not complete successfully|while running runtime: exit status|did not resolve to an alias|requested access to the resource is denied|dockerfile parse error|unknown instruction|should not be blank|pull access denied|repository does not exist|manifest unknown|not found: manifest|: not found\b|unauthorized|denied:|reeval needs a Debian or Ubuntu based image/i;
|
|
57
1148
|
const TRANSIENT = /EAI_AGAIN|ECONNRESET|ETIMEDOUT|ECONNREFUSED|i\/o timeout|TLS handshake timeout|no such host|temporary failure in name resolution|connection reset by peer|dial tcp|net\/http: request canceled|unexpected EOF|\b(?:500|502|503|504) |429 Too Many Requests|toomanyrequests|Service Unavailable|Bad Gateway|Gateway Timeout|ENOTFOUND/i;
|
|
58
1149
|
/** A registry or network error: a pull, a metadata lookup, or a download in the harness bootstrap. */
|
|
59
1150
|
const isTransient = (text) => !DETERMINISTIC.test(text) && TRANSIENT.test(text);
|
|
@@ -76,7 +1167,7 @@ const RETRY_DELAYS = [2e3, 8e3];
|
|
|
76
1167
|
*/
|
|
77
1168
|
function failureMessage(parts) {
|
|
78
1169
|
const lines = [parts.headline];
|
|
79
|
-
if (parts.cause) lines.push(causeLine(parts.cause));
|
|
1170
|
+
if (parts.cause) lines.push(causeLine(parts.cause, parts.engine));
|
|
80
1171
|
if (parts.output.length) lines.push(...parts.output.map((l) => ` | ${l}`));
|
|
81
1172
|
if (parts.log) lines.push(`(full log: ${parts.log})`);
|
|
82
1173
|
return lines.join("\n");
|
|
@@ -86,119 +1177,6 @@ function outputTail(text, max = 8) {
|
|
|
86
1177
|
return stripAnsi(text).split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim()).slice(-max);
|
|
87
1178
|
}
|
|
88
1179
|
//#endregion
|
|
89
|
-
//#region ../sandbox-docker/src/process.ts
|
|
90
|
-
const CMD_META = /([()\][%!^"`<>&|;, *?])/g;
|
|
91
|
-
const cmdEscape = (arg, twice) => {
|
|
92
|
-
const quoted = `"${arg.replace(/(\\*)"/g, "$1$1\\\"").replace(/(\\*)$/, "$1$1")}"`.replace(CMD_META, "^$1");
|
|
93
|
-
return twice ? quoted.replace(CMD_META, "^$1") : quoted;
|
|
94
|
-
};
|
|
95
|
-
/**
|
|
96
|
-
* How to spawn `cmd` on Windows, where npm, npx and pnpm are `.cmd` scripts that only cmd.exe
|
|
97
|
-
* runs: the program PATH and PATHEXT find, and for a script, cmd.exe with every argument escaped.
|
|
98
|
-
*/
|
|
99
|
-
function windowsSpawn(cmd, args, env = process.env, exists = existsSync) {
|
|
100
|
-
const exts = (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
101
|
-
const file = (/[\\/]/.test(cmd) ? [""] : (env.PATH ?? env.Path ?? "").split(";").filter(Boolean)).flatMap((dir) => {
|
|
102
|
-
const base = dir ? win32.join(dir, cmd) : cmd;
|
|
103
|
-
return [...win32.extname(cmd) ? [base] : [], ...exts.map((ext) => base + ext.toLowerCase())];
|
|
104
|
-
}).find(exists) ?? cmd;
|
|
105
|
-
if (!/\.(cmd|bat)$/i.test(file)) return {
|
|
106
|
-
file,
|
|
107
|
-
args,
|
|
108
|
-
verbatim: false
|
|
109
|
-
};
|
|
110
|
-
const twice = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i.test(file);
|
|
111
|
-
const line = [win32.normalize(file).replace(CMD_META, "^$1"), ...args.map((a) => cmdEscape(a, twice))].join(" ");
|
|
112
|
-
return {
|
|
113
|
-
file: env.ComSpec ?? "cmd.exe",
|
|
114
|
-
args: [
|
|
115
|
-
"/d",
|
|
116
|
-
"/s",
|
|
117
|
-
"/c",
|
|
118
|
-
`"${line}"`
|
|
119
|
-
],
|
|
120
|
-
verbatim: true
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Kills a process and everything it started: its process group on POSIX (a detached child leads
|
|
125
|
-
* one), its process tree with `taskkill` on Windows, which has neither groups nor signals.
|
|
126
|
-
*/
|
|
127
|
-
function killTree(pid) {
|
|
128
|
-
if (process.platform === "win32") {
|
|
129
|
-
spawn("taskkill", [
|
|
130
|
-
"/pid",
|
|
131
|
-
String(pid),
|
|
132
|
-
"/t",
|
|
133
|
-
"/f"
|
|
134
|
-
], {
|
|
135
|
-
stdio: "ignore",
|
|
136
|
-
windowsHide: true
|
|
137
|
-
}).on("error", () => {});
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
try {
|
|
141
|
-
process.kill(-pid, "SIGKILL");
|
|
142
|
-
} catch {
|
|
143
|
-
try {
|
|
144
|
-
process.kill(pid, "SIGKILL");
|
|
145
|
-
} catch {}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/** `s` as one single-quoted POSIX shell word. */
|
|
149
|
-
const shellQuote = (s) => `'${s.replaceAll("'", `'\\''`)}'`;
|
|
150
|
-
function run(cmd, args, options = {}) {
|
|
151
|
-
const { signal } = options;
|
|
152
|
-
return new Promise((resolve, reject) => {
|
|
153
|
-
if (signal?.aborted) return reject(signal.reason);
|
|
154
|
-
const detached = options.detached ?? true;
|
|
155
|
-
const target = process.platform === "win32" ? windowsSpawn(cmd, args, options.env ? {
|
|
156
|
-
...process.env,
|
|
157
|
-
...options.env
|
|
158
|
-
} : process.env) : {
|
|
159
|
-
file: cmd,
|
|
160
|
-
args,
|
|
161
|
-
verbatim: false
|
|
162
|
-
};
|
|
163
|
-
const child = spawn(target.file, target.args, {
|
|
164
|
-
cwd: options.cwd,
|
|
165
|
-
stdio: "pipe",
|
|
166
|
-
detached: detached && !target.verbatim,
|
|
167
|
-
windowsHide: true,
|
|
168
|
-
windowsVerbatimArguments: target.verbatim,
|
|
169
|
-
env: options.env ? {
|
|
170
|
-
...process.env,
|
|
171
|
-
...options.env
|
|
172
|
-
} : process.env
|
|
173
|
-
});
|
|
174
|
-
const out = [];
|
|
175
|
-
let stderr = "";
|
|
176
|
-
child.stdout.on("data", (d) => out.push(d));
|
|
177
|
-
child.stderr.on("data", (d) => {
|
|
178
|
-
stderr += d;
|
|
179
|
-
});
|
|
180
|
-
const onAbort = () => {
|
|
181
|
-
if ((detached || process.platform === "win32") && child.pid) killTree(child.pid);
|
|
182
|
-
else child.kill("SIGKILL");
|
|
183
|
-
};
|
|
184
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
185
|
-
child.on("error", reject);
|
|
186
|
-
child.on("close", (code) => {
|
|
187
|
-
signal?.removeEventListener("abort", onAbort);
|
|
188
|
-
if (signal?.aborted) return reject(signal.reason);
|
|
189
|
-
const stdout = Buffer.concat(out);
|
|
190
|
-
resolve({
|
|
191
|
-
exitCode: code ?? 1,
|
|
192
|
-
stdout: options.binary ? stdout : stdout.toString(),
|
|
193
|
-
stderr
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
if (child.pid) options.onSpawn?.(child.pid);
|
|
197
|
-
child.stdin.on("error", () => {});
|
|
198
|
-
child.stdin.end(options.input ?? "");
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
//#endregion
|
|
202
1180
|
//#region ../sandbox-docker/src/trace.ts
|
|
203
1181
|
/** Most of each stream a trace keeps; the rest is counted. */
|
|
204
1182
|
const MAX_STREAM = 4096;
|
|
@@ -216,9 +1194,10 @@ function streamPreview(data) {
|
|
|
216
1194
|
if (data.includes(0) || !isUtf8(data)) return `<${data.length} bytes>`;
|
|
217
1195
|
return clip(data.toString("utf8"), MAX_STREAM);
|
|
218
1196
|
}
|
|
219
|
-
function traceDocker(args, startedAt, result) {
|
|
1197
|
+
function traceDocker(args, startedAt, result, bin = "docker") {
|
|
220
1198
|
if (!listener) return;
|
|
221
1199
|
const base = {
|
|
1200
|
+
bin,
|
|
222
1201
|
args: args.map((a) => clip(a, MAX_ARG)),
|
|
223
1202
|
ms: Date.now() - startedAt
|
|
224
1203
|
};
|
|
@@ -245,107 +1224,199 @@ function traceDocker(args, startedAt, result) {
|
|
|
245
1224
|
}
|
|
246
1225
|
//#endregion
|
|
247
1226
|
//#region ../sandbox-docker/src/docker-cli.ts
|
|
248
|
-
/**
|
|
1227
|
+
/** How long to wait before each retry of a command that never reached Podman. */
|
|
1228
|
+
const CONNECTION_RETRY_MS = [
|
|
1229
|
+
100,
|
|
1230
|
+
250,
|
|
1231
|
+
500,
|
|
1232
|
+
1e3,
|
|
1233
|
+
2e3
|
|
1234
|
+
];
|
|
1235
|
+
/**
|
|
1236
|
+
* Whether a podman command failed before reaching the engine: the remote client's SSH handshake
|
|
1237
|
+
* or socket connection to `podman machine`, so running it again is safe.
|
|
1238
|
+
*/
|
|
1239
|
+
const isConnectionFailure = (r) => r.exitCode === 125 && /ssh: handshake failed|unable to connect to Podman socket/.test(r.stderr);
|
|
1240
|
+
const sleep = (ms, signal) => new Promise((resolve) => {
|
|
1241
|
+
const timer = setTimeout(done, ms);
|
|
1242
|
+
function done() {
|
|
1243
|
+
clearTimeout(timer);
|
|
1244
|
+
signal?.removeEventListener("abort", done);
|
|
1245
|
+
resolve();
|
|
1246
|
+
}
|
|
1247
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
1248
|
+
});
|
|
1249
|
+
/**
|
|
1250
|
+
* Runs `attempt` again, after each of `delays`, while its result is a connection failure (see
|
|
1251
|
+
* {@link isConnectionFailure}), and returns the last result.
|
|
1252
|
+
*/
|
|
1253
|
+
async function retryConnection(attempt, options = {}) {
|
|
1254
|
+
const { delays = CONNECTION_RETRY_MS, signal, wait = sleep } = options;
|
|
1255
|
+
let r = await attempt();
|
|
1256
|
+
for (const ms of delays) {
|
|
1257
|
+
if (!isConnectionFailure(r) || signal?.aborted) break;
|
|
1258
|
+
await wait(ms, signal);
|
|
1259
|
+
r = await attempt();
|
|
1260
|
+
}
|
|
1261
|
+
return r;
|
|
1262
|
+
}
|
|
1263
|
+
/** Runs the docker (or podman) CLI. `env` values are passed via the CLI's environment, never argv. */
|
|
249
1264
|
async function docker(args, options = {}) {
|
|
250
|
-
const
|
|
1265
|
+
const { engine, ...rest } = options;
|
|
1266
|
+
const { bin } = engineSpec(engine);
|
|
251
1267
|
const stdin = options.input?.length ?? 0;
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
1268
|
+
const env = {
|
|
1269
|
+
...engineEnv(engine),
|
|
1270
|
+
...rest.env
|
|
1271
|
+
};
|
|
1272
|
+
const once = async () => {
|
|
1273
|
+
const startedAt = Date.now();
|
|
1274
|
+
try {
|
|
1275
|
+
const r = await run(bin, args, {
|
|
1276
|
+
...rest,
|
|
1277
|
+
env,
|
|
1278
|
+
binary: true
|
|
1279
|
+
});
|
|
1280
|
+
traceDocker(args, startedAt, {
|
|
1281
|
+
...r,
|
|
1282
|
+
stdin
|
|
1283
|
+
}, bin);
|
|
1284
|
+
return r;
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
traceDocker(args, startedAt, { error }, bin);
|
|
1287
|
+
throw error;
|
|
1288
|
+
}
|
|
1289
|
+
};
|
|
1290
|
+
return engine === "podman" ? retryConnection(once, { signal: options.signal }) : once();
|
|
266
1291
|
}
|
|
267
1292
|
async function dockerOk(args, opts) {
|
|
268
1293
|
const r = await docker(args, opts);
|
|
269
|
-
if (r.exitCode !== 0) throw new Error(
|
|
1294
|
+
if (r.exitCode !== 0) throw new Error(`${engineSpec(opts?.engine).bin} ${args[0]} failed: ${r.stderr.trim()}`);
|
|
270
1295
|
return r.stdout.toString("utf8").trim();
|
|
271
1296
|
}
|
|
272
|
-
/**
|
|
273
|
-
|
|
274
|
-
|
|
1297
|
+
/** Whether `rm -f` left the container gone: it removed it, or it was already gone. */
|
|
1298
|
+
const removalDone = (r) => r.exitCode === 0 || /no such container/i.test(r.stderr);
|
|
1299
|
+
/** How long to wait before each retry of a removal that failed. */
|
|
1300
|
+
const REMOVAL_RETRY_MS = [
|
|
1301
|
+
250,
|
|
1302
|
+
500,
|
|
1303
|
+
1e3,
|
|
1304
|
+
2e3,
|
|
1305
|
+
4e3
|
|
1306
|
+
];
|
|
1307
|
+
/**
|
|
1308
|
+
* Removes a container in its own process group, so removal survives this process being killed,
|
|
1309
|
+
* and tries again while it fails. Resolves either way.
|
|
1310
|
+
*/
|
|
1311
|
+
async function removeContainerDetached(id, engine, options = {}) {
|
|
1312
|
+
const { bin, rmForce } = engineSpec(engine);
|
|
1313
|
+
const { delays = REMOVAL_RETRY_MS, wait = sleep } = options;
|
|
1314
|
+
const args = [...rmForce, id];
|
|
1315
|
+
const env = {
|
|
1316
|
+
...process.env,
|
|
1317
|
+
...engineEnv(engine)
|
|
1318
|
+
};
|
|
1319
|
+
const once = () => new Promise((resolve) => {
|
|
275
1320
|
const keepAlive = setInterval(() => {}, 1e3);
|
|
276
1321
|
const startedAt = Date.now();
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
"-f",
|
|
280
|
-
id
|
|
281
|
-
];
|
|
1322
|
+
let stderr = "";
|
|
1323
|
+
let settled = false;
|
|
282
1324
|
const done = (exitCode) => {
|
|
1325
|
+
if (settled) return;
|
|
1326
|
+
settled = true;
|
|
283
1327
|
clearInterval(keepAlive);
|
|
284
|
-
traceDocker(args, startedAt, {
|
|
285
|
-
|
|
1328
|
+
traceDocker(args, startedAt, {
|
|
1329
|
+
exitCode,
|
|
1330
|
+
stderr
|
|
1331
|
+
}, bin);
|
|
1332
|
+
resolve({
|
|
1333
|
+
exitCode,
|
|
1334
|
+
stderr
|
|
1335
|
+
});
|
|
286
1336
|
};
|
|
287
|
-
const child = spawn(
|
|
1337
|
+
const child = spawn(bin, args, {
|
|
288
1338
|
detached: true,
|
|
289
|
-
stdio:
|
|
290
|
-
|
|
1339
|
+
stdio: [
|
|
1340
|
+
"ignore",
|
|
1341
|
+
"ignore",
|
|
1342
|
+
"pipe"
|
|
1343
|
+
],
|
|
1344
|
+
windowsHide: true,
|
|
1345
|
+
env
|
|
1346
|
+
});
|
|
1347
|
+
child.stderr.on("data", (d) => {
|
|
1348
|
+
stderr += d;
|
|
1349
|
+
});
|
|
1350
|
+
child.on("error", (error) => {
|
|
1351
|
+
stderr += String(error);
|
|
1352
|
+
done(null);
|
|
291
1353
|
});
|
|
292
|
-
child.on("error", () => done(null));
|
|
293
1354
|
child.on("close", done);
|
|
294
1355
|
});
|
|
1356
|
+
let r = await once();
|
|
1357
|
+
for (const ms of delays) {
|
|
1358
|
+
if (removalDone(r) || r.exitCode === null) return;
|
|
1359
|
+
await wait(ms);
|
|
1360
|
+
r = await once();
|
|
1361
|
+
}
|
|
295
1362
|
}
|
|
296
1363
|
/**
|
|
297
1364
|
* Removes every container with `label` (`key=value`) for the next few seconds, in its own process
|
|
298
1365
|
* group so it outlives this process. For a `docker run` killed mid-way: the daemon may create the
|
|
299
1366
|
* container after the CLI dies, and its id never came back.
|
|
300
1367
|
*/
|
|
301
|
-
function sweepLabelDetached(label, seconds = 15) {
|
|
1368
|
+
function sweepLabelDetached(label, seconds = 15, engine) {
|
|
1369
|
+
const { bin, rmForce } = engineSpec(engine);
|
|
302
1370
|
const child = spawn(process.execPath, [
|
|
303
1371
|
"-e",
|
|
304
1372
|
`const { execFileSync } = require('node:child_process')
|
|
305
|
-
const [filter, seconds] = process.argv.slice(1)
|
|
1373
|
+
const [filter, seconds, bin, ...rm] = process.argv.slice(1)
|
|
306
1374
|
const o = { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true }
|
|
307
1375
|
for (let i = 0; i < Number(seconds); i++) {
|
|
308
1376
|
try {
|
|
309
|
-
const ids = execFileSync(
|
|
310
|
-
if (ids.length) execFileSync(
|
|
1377
|
+
const ids = execFileSync(bin, ['ps', '-aq', '--filter', filter], o).split(/\\s+/).filter(Boolean)
|
|
1378
|
+
if (ids.length) execFileSync(bin, [...rm, ...ids], o)
|
|
311
1379
|
} catch {}
|
|
312
1380
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1000)
|
|
313
1381
|
}`,
|
|
314
1382
|
`label=${label}`,
|
|
315
|
-
String(seconds)
|
|
1383
|
+
String(seconds),
|
|
1384
|
+
bin,
|
|
1385
|
+
...rmForce
|
|
316
1386
|
], {
|
|
317
1387
|
detached: true,
|
|
318
1388
|
stdio: "ignore",
|
|
319
1389
|
windowsHide: true,
|
|
320
1390
|
env: {
|
|
321
1391
|
...process.env,
|
|
1392
|
+
...engineEnv(engine),
|
|
322
1393
|
BUN_BE_BUN: "1"
|
|
323
1394
|
}
|
|
324
1395
|
});
|
|
325
1396
|
child.on("error", () => {});
|
|
326
1397
|
child.unref();
|
|
327
1398
|
}
|
|
328
|
-
async function dockerState() {
|
|
1399
|
+
async function dockerState(engine) {
|
|
329
1400
|
const r = await docker([
|
|
330
1401
|
"info",
|
|
331
1402
|
"--format",
|
|
332
|
-
|
|
333
|
-
]).catch(() => null);
|
|
1403
|
+
engineSpec(engine).stateFormat
|
|
1404
|
+
], { engine }).catch(() => null);
|
|
334
1405
|
const [version, os] = r?.exitCode === 0 ? r.stdout.toString("utf8").trim().split(/\s+/) : [];
|
|
335
1406
|
if (!version) return "down";
|
|
336
1407
|
return os === "windows" ? "windows-containers" : "running";
|
|
337
1408
|
}
|
|
338
|
-
/** Whether Docker is running Linux containers, as every sandbox needs. */
|
|
339
|
-
async function dockerAvailable() {
|
|
340
|
-
return await dockerState() === "running";
|
|
1409
|
+
/** Whether Docker (or Podman) is running Linux containers, as every sandbox needs. */
|
|
1410
|
+
async function dockerAvailable(engine) {
|
|
1411
|
+
return await dockerState(engine) === "running";
|
|
341
1412
|
}
|
|
342
|
-
/** CPUs and memory of the machine containers run on (the VM, under Docker Desktop). */
|
|
343
|
-
async function dockerCapacity() {
|
|
1413
|
+
/** CPUs and memory of the machine containers run on (the VM, under Docker Desktop or `podman machine`). */
|
|
1414
|
+
async function dockerCapacity(engine) {
|
|
344
1415
|
const r = await docker([
|
|
345
1416
|
"info",
|
|
346
1417
|
"--format",
|
|
347
|
-
|
|
348
|
-
]).catch(() => null);
|
|
1418
|
+
engineSpec(engine).capacityFormat
|
|
1419
|
+
], { engine }).catch(() => null);
|
|
349
1420
|
if (r?.exitCode !== 0) return null;
|
|
350
1421
|
const [cpus, memoryBytes] = r.stdout.toString("utf8").trim().split(/\s+/).map(Number);
|
|
351
1422
|
return cpus && memoryBytes ? {
|
|
@@ -359,23 +1430,51 @@ async function dockerCapacity() {
|
|
|
359
1430
|
* their bare local name (`reeval-sandbox@sha256:…`). A bare name is Docker Hub's `library/`
|
|
360
1431
|
* namespace, which only official images can push to, so only a digest whose name has a registry
|
|
361
1432
|
* host or a namespace counts. Official images such as `busybox` count as local; no image with
|
|
362
|
-
* the harness baked in is one.
|
|
1433
|
+
* the harness baked in is one. Podman names local images `localhost/…`, which no one pulls from.
|
|
1434
|
+
*/
|
|
1435
|
+
const isRegistryDigest = (digest) => {
|
|
1436
|
+
const name = digest.split("@")[0] ?? "";
|
|
1437
|
+
return name.includes("/") && !name.startsWith("localhost/");
|
|
1438
|
+
};
|
|
1439
|
+
/** `repository:tag` of every local image in `repository`; none when the CLI fails. */
|
|
1440
|
+
async function imageTags(repository, engine) {
|
|
1441
|
+
const r = await docker([
|
|
1442
|
+
"image",
|
|
1443
|
+
"ls",
|
|
1444
|
+
"--format",
|
|
1445
|
+
"{{.Repository}}:{{.Tag}}",
|
|
1446
|
+
repository
|
|
1447
|
+
], { engine }).catch(() => null);
|
|
1448
|
+
if (r?.exitCode !== 0) return [];
|
|
1449
|
+
return r.stdout.toString("utf8").split(/\r?\n/).filter(Boolean).map((t) => t.replace(/^localhost\//, ""));
|
|
1450
|
+
}
|
|
1451
|
+
/** `image inspect`'s answer when the engine has no such image (Docker's wording, then Podman's). */
|
|
1452
|
+
const isNoSuchImage = (stderr) => /no such image|image not known/i.test(stderr);
|
|
1453
|
+
/**
|
|
1454
|
+
* The image, or null when the engine says it has no such image. A CLI that can't start or a daemon
|
|
1455
|
+
* that doesn't answer throws instead, naming the error: that's not knowing, not a missing image.
|
|
363
1456
|
*/
|
|
364
|
-
|
|
365
|
-
|
|
1457
|
+
async function imageInfo(image, engine) {
|
|
1458
|
+
const { bin, imageId } = engineSpec(engine);
|
|
366
1459
|
const r = await docker([
|
|
367
1460
|
"image",
|
|
368
1461
|
"inspect",
|
|
369
1462
|
"--format",
|
|
370
|
-
|
|
1463
|
+
`{"config":{{json .Config}},"id":{{json ${imageId}}},"repoDigests":{{json .RepoDigests}}}`,
|
|
371
1464
|
image
|
|
372
|
-
]).catch(() =>
|
|
373
|
-
|
|
1465
|
+
], { engine }).catch((error) => {
|
|
1466
|
+
throw new Error(`${bin} image inspect ${image} couldn't run: ${error.message}`);
|
|
1467
|
+
});
|
|
1468
|
+
if (r.exitCode !== 0) {
|
|
1469
|
+
const stderr = r.stderr.trim();
|
|
1470
|
+
if (isNoSuchImage(stderr)) return null;
|
|
1471
|
+
throw new Error(`${bin} image inspect ${image} failed: ${stderr || `exit ${r.exitCode}`}`);
|
|
1472
|
+
}
|
|
374
1473
|
const info = JSON.parse(r.stdout.toString("utf8"));
|
|
375
1474
|
const repoDigests = (info.repoDigests ?? []).filter(isRegistryDigest);
|
|
376
1475
|
return {
|
|
377
1476
|
labels: info.config?.Labels ?? {},
|
|
378
|
-
id: info.id,
|
|
1477
|
+
id: normalizeImageId(info.id),
|
|
379
1478
|
repoDigests
|
|
380
1479
|
};
|
|
381
1480
|
}
|
|
@@ -390,32 +1489,40 @@ async function imageInfo(image) {
|
|
|
390
1489
|
const HARNESS_PACKAGES = {
|
|
391
1490
|
cursor: {
|
|
392
1491
|
name: "@reeval/harness-cursor-sdk",
|
|
393
|
-
version: "0.1.
|
|
1492
|
+
version: "0.1.5"
|
|
394
1493
|
},
|
|
395
1494
|
"claude-code": {
|
|
396
1495
|
name: "@ai-sdk/harness-claude-code",
|
|
397
|
-
version: "1.0.
|
|
398
|
-
patch: "
|
|
1496
|
+
version: "1.0.158",
|
|
1497
|
+
patch: "6c1cff12"
|
|
399
1498
|
},
|
|
400
1499
|
antigravity: {
|
|
401
1500
|
name: "@reeval/harness-antigravity",
|
|
402
|
-
version: "0.1.
|
|
1501
|
+
version: "0.1.8"
|
|
403
1502
|
},
|
|
404
1503
|
opencode: {
|
|
405
1504
|
name: "@reeval/harness-opencode",
|
|
406
|
-
version: "0.2.
|
|
1505
|
+
version: "0.2.8"
|
|
407
1506
|
},
|
|
408
1507
|
goose: {
|
|
409
1508
|
name: "@reeval/harness-goose",
|
|
410
|
-
version: "0.1.
|
|
1509
|
+
version: "0.1.3"
|
|
411
1510
|
},
|
|
412
1511
|
pi: {
|
|
413
1512
|
name: "@reeval/harness-pi",
|
|
414
|
-
version: "0.2.
|
|
1513
|
+
version: "0.2.7"
|
|
415
1514
|
},
|
|
416
1515
|
codex: {
|
|
417
1516
|
name: "@reeval/harness-codex",
|
|
418
|
-
version: "0.1.
|
|
1517
|
+
version: "0.1.7"
|
|
1518
|
+
},
|
|
1519
|
+
fx: {
|
|
1520
|
+
name: "@reeval/harness-fx",
|
|
1521
|
+
version: "0.1.0"
|
|
1522
|
+
},
|
|
1523
|
+
"grok-build": {
|
|
1524
|
+
name: "@reeval/harness-grok-build",
|
|
1525
|
+
version: "0.1.0"
|
|
419
1526
|
}
|
|
420
1527
|
};
|
|
421
1528
|
/** Every harness with an image, in {@link HARNESS_PACKAGES} order. */
|
|
@@ -780,7 +1887,9 @@ const HARNESS_HOSTS = {
|
|
|
780
1887
|
opencode: ["generativelanguage.googleapis.com", "mcp.exa.ai"],
|
|
781
1888
|
goose: ["generativelanguage.googleapis.com"],
|
|
782
1889
|
pi: ["generativelanguage.googleapis.com"],
|
|
783
|
-
codex: ["api.openai.com"]
|
|
1890
|
+
codex: ["api.openai.com"],
|
|
1891
|
+
fx: ["ai-gateway.vercel.sh"],
|
|
1892
|
+
"grok-build": ["api.x.ai"]
|
|
784
1893
|
};
|
|
785
1894
|
/** The package registries and GitHub, for a restricted policy that still installs. */
|
|
786
1895
|
const PACKAGE_HOSTS = Object.freeze([
|
|
@@ -1894,14 +3003,14 @@ const PROXY_PORT = 43128;
|
|
|
1894
3003
|
const CA_PATH = "/usr/local/share/ca-certificates/reeval-egress.crt";
|
|
1895
3004
|
/** Debian's system bundle, which `update-ca-certificates` rebuilds with the gateway's CA in it. */
|
|
1896
3005
|
const CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
|
|
1897
|
-
const BUNDLED = "
|
|
3006
|
+
const BUNDLED = "reeval:ca-bundle";
|
|
1898
3007
|
const READY_TIMEOUT_MS = 15e3;
|
|
1899
3008
|
/**
|
|
1900
3009
|
* The gateway's side of the container runs as root, whoever the sandbox runs as: the sandbox user
|
|
1901
3010
|
* can't signal, trace or replace the forwarder, or change the CA it installed.
|
|
1902
3011
|
*/
|
|
1903
3012
|
const AS_ROOT = ["--user", "0:0"];
|
|
1904
|
-
const installCa = (containerId, cert, signal) => docker([
|
|
3013
|
+
const installCa = (containerId, cert, signal, engine) => docker([
|
|
1905
3014
|
"exec",
|
|
1906
3015
|
"--interactive",
|
|
1907
3016
|
...AS_ROOT,
|
|
@@ -1911,15 +3020,16 @@ const installCa = (containerId, cert, signal) => docker([
|
|
|
1911
3020
|
`umask 022 && mkdir -p "$(dirname ${CA_PATH})" && cat > ${CA_PATH} && { command -v update-ca-certificates >/dev/null && update-ca-certificates >/dev/null 2>&1 && grep -q "$(sed -n 2p ${CA_PATH})" ${CA_BUNDLE} && echo ${BUNDLED}; true; }`
|
|
1912
3021
|
], {
|
|
1913
3022
|
input: cert,
|
|
1914
|
-
signal
|
|
3023
|
+
signal,
|
|
3024
|
+
engine
|
|
1915
3025
|
});
|
|
1916
3026
|
/**
|
|
1917
3027
|
* Starts the egress gateway for one container: a forwarder inside it, tunnelled over one
|
|
1918
3028
|
* `docker exec -i`, and the proxy in this process, which holds the real credentials.
|
|
1919
3029
|
*/
|
|
1920
|
-
async function startGateway(containerId, options, signal) {
|
|
3030
|
+
async function startGateway(containerId, options, signal, engine) {
|
|
1921
3031
|
const ca = createCertificateAuthority();
|
|
1922
|
-
const installed = await installCa(containerId, ca.cert, signal);
|
|
3032
|
+
const installed = await installCa(containerId, ca.cert, signal, engine);
|
|
1923
3033
|
if (installed.exitCode !== 0) throw new Error(`couldn't install the gateway CA: ${installed.stderr.trim()}`);
|
|
1924
3034
|
const bundled = installed.stdout.toString("utf8").includes(BUNDLED);
|
|
1925
3035
|
const proxy = createEgressProxy({
|
|
@@ -1936,14 +3046,19 @@ async function startGateway(containerId, options, signal) {
|
|
|
1936
3046
|
forwarderScript(PROXY_PORT)
|
|
1937
3047
|
];
|
|
1938
3048
|
const startedAt = Date.now();
|
|
1939
|
-
const
|
|
3049
|
+
const { bin } = engineSpec(engine);
|
|
3050
|
+
const child = spawn(bin, args, {
|
|
1940
3051
|
stdio: "pipe",
|
|
1941
|
-
detached: true
|
|
3052
|
+
detached: true,
|
|
3053
|
+
env: {
|
|
3054
|
+
...process.env,
|
|
3055
|
+
...engineEnv(engine)
|
|
3056
|
+
}
|
|
1942
3057
|
});
|
|
1943
3058
|
child.once("close", (code) => traceDocker(args, startedAt, {
|
|
1944
3059
|
exitCode: code,
|
|
1945
3060
|
stderr
|
|
1946
|
-
}));
|
|
3061
|
+
}, bin));
|
|
1947
3062
|
let stderr = "";
|
|
1948
3063
|
child.stderr.on("data", (d) => {
|
|
1949
3064
|
stderr += d;
|
|
@@ -2057,6 +3172,35 @@ const DEFAULT_LIMITS = {
|
|
|
2057
3172
|
cpus: 2,
|
|
2058
3173
|
pids: 1024
|
|
2059
3174
|
};
|
|
3175
|
+
const UNITS = {
|
|
3176
|
+
b: 1,
|
|
3177
|
+
k: 1024,
|
|
3178
|
+
m: 1024 ** 2,
|
|
3179
|
+
g: 1024 ** 3
|
|
3180
|
+
};
|
|
3181
|
+
/** A Docker memory size in bytes, as Docker reads it (binary units). */
|
|
3182
|
+
function sizeBytes(size) {
|
|
3183
|
+
if (typeof size === "number") return size;
|
|
3184
|
+
const m = /^(\d+(?:\.\d+)?)([bkmg])?$/i.exec(size.trim());
|
|
3185
|
+
if (!m) throw new Error(`not a Docker memory size: ${JSON.stringify(size)}`);
|
|
3186
|
+
const unit = m[2]?.toLowerCase() ?? "b";
|
|
3187
|
+
return Math.floor(Number(m[1]) * UNITS[unit]);
|
|
3188
|
+
}
|
|
3189
|
+
/** Bytes in the largest unit that divides them evenly: `8g`, `1536m`, or plain bytes. */
|
|
3190
|
+
function formatSize(bytes) {
|
|
3191
|
+
for (const unit of [
|
|
3192
|
+
"g",
|
|
3193
|
+
"m",
|
|
3194
|
+
"k"
|
|
3195
|
+
]) if (bytes % UNITS[unit] === 0) return `${bytes / UNITS[unit]}${unit}`;
|
|
3196
|
+
return String(bytes);
|
|
3197
|
+
}
|
|
3198
|
+
/** Docker's `--memory-swap`: the total of memory and swap. */
|
|
3199
|
+
function memorySwap(limits) {
|
|
3200
|
+
const memory = sizeBytes(limits.memory);
|
|
3201
|
+
const swap = limits.swap === void 0 ? memory : sizeBytes(limits.swap);
|
|
3202
|
+
return swap === 0 ? String(limits.memory) : formatSize(memory + swap);
|
|
3203
|
+
}
|
|
2060
3204
|
/** What `root` adds back: what `apt-get` (and its `_apt` sandbox) and `dpkg` need as root. */
|
|
2061
3205
|
const ROOT_CAPABILITIES = [
|
|
2062
3206
|
"CHOWN",
|
|
@@ -2065,14 +3209,21 @@ const ROOT_CAPABILITIES = [
|
|
|
2065
3209
|
"SETUID",
|
|
2066
3210
|
"SETGID"
|
|
2067
3211
|
];
|
|
2068
|
-
|
|
3212
|
+
const hostCpus = /* @__PURE__ */ new Map();
|
|
2069
3213
|
/** Docker refuses `--cpus` above the host's count, so cap it there. */
|
|
2070
|
-
async function cpuLimit(cpus) {
|
|
2071
|
-
|
|
2072
|
-
|
|
3214
|
+
async function cpuLimit(cpus, engine = "docker") {
|
|
3215
|
+
let known = hostCpus.get(engine);
|
|
3216
|
+
if (!known) {
|
|
3217
|
+
known = dockerCapacity(engine).then((c) => c?.cpus, () => void 0);
|
|
3218
|
+
hostCpus.set(engine, known);
|
|
3219
|
+
}
|
|
3220
|
+
const available = await known;
|
|
2073
3221
|
return String(available ? Math.min(cpus, available) : cpus);
|
|
2074
3222
|
}
|
|
2075
|
-
/**
|
|
3223
|
+
/**
|
|
3224
|
+
* The `docker run` flags that confine the container: its user, capabilities and limits, and on
|
|
3225
|
+
* Podman the bridge network and Docker's seccomp profile.
|
|
3226
|
+
*/
|
|
2076
3227
|
async function confinementArgs(options) {
|
|
2077
3228
|
const limits = {
|
|
2078
3229
|
...DEFAULT_LIMITS,
|
|
@@ -2090,27 +3241,63 @@ async function confinementArgs(options) {
|
|
|
2090
3241
|
"--memory",
|
|
2091
3242
|
String(limits.memory),
|
|
2092
3243
|
"--memory-swap",
|
|
2093
|
-
|
|
3244
|
+
memorySwap(limits),
|
|
2094
3245
|
"--pids-limit",
|
|
2095
3246
|
String(limits.pids),
|
|
2096
3247
|
"--cpus",
|
|
2097
|
-
await cpuLimit(limits.cpus)
|
|
3248
|
+
await cpuLimit(limits.cpus, options.engine),
|
|
3249
|
+
...engineSpec(options.engine).runArgs()
|
|
2098
3250
|
];
|
|
2099
3251
|
}
|
|
2100
3252
|
/** Runtime `npm i -g` goes to the sandbox user's own prefix, whose `bin` the images put on PATH. */
|
|
2101
3253
|
const userEnv = (options) => options.root ? {} : { NPM_CONFIG_PREFIX: `${SANDBOX_HOME}/.local` };
|
|
2102
3254
|
/** Set on every exec, so a kill can find the command's whole process tree in the container. */
|
|
2103
3255
|
const EXEC_MARKER = "REEVAL_EXEC_ID";
|
|
3256
|
+
/**
|
|
3257
|
+
* The container's cgroup counts every process the kernel killed at its memory limit: cgroup v2's
|
|
3258
|
+
* `memory.events` and v1's `memory.oom_control` both have an `oom_kill` line. Without either,
|
|
3259
|
+
* Docker's `OOMKilled` says only whether there was one.
|
|
3260
|
+
*/
|
|
3261
|
+
async function oomKills(id, engine) {
|
|
3262
|
+
const events = await docker([
|
|
3263
|
+
"exec",
|
|
3264
|
+
id,
|
|
3265
|
+
"sh",
|
|
3266
|
+
"-c",
|
|
3267
|
+
"cat /sys/fs/cgroup/memory.events 2>/dev/null || cat /sys/fs/cgroup/memory/memory.oom_control"
|
|
3268
|
+
], { engine }).catch(() => null);
|
|
3269
|
+
const count = events?.stdout.toString("utf8").match(/^oom_kill (\d+)$/m)?.[1];
|
|
3270
|
+
if (events?.exitCode === 0 && count !== void 0) return Number(count);
|
|
3271
|
+
const inspected = await docker([
|
|
3272
|
+
"inspect",
|
|
3273
|
+
"--format",
|
|
3274
|
+
"{{.State.OOMKilled}}",
|
|
3275
|
+
id
|
|
3276
|
+
], { engine }).catch(() => null);
|
|
3277
|
+
if (inspected?.exitCode !== 0) return null;
|
|
3278
|
+
const killed = inspected.stdout.toString("utf8").trim();
|
|
3279
|
+
return killed === "true" ? 1 : killed === "false" ? 0 : null;
|
|
3280
|
+
}
|
|
2104
3281
|
/** The Docker networks a container is attached to. */
|
|
2105
|
-
async function networksOf(id) {
|
|
3282
|
+
async function networksOf(id, engine) {
|
|
2106
3283
|
const out = await dockerOk([
|
|
2107
3284
|
"inspect",
|
|
2108
3285
|
"--format",
|
|
2109
3286
|
"{{json .NetworkSettings.Networks}}",
|
|
2110
3287
|
id
|
|
2111
|
-
]);
|
|
3288
|
+
], { engine });
|
|
2112
3289
|
return Object.keys(JSON.parse(out) ?? {});
|
|
2113
3290
|
}
|
|
3291
|
+
/** How the container is networked when it's on no network: `host`, `slirp4netns`, `pasta`… */
|
|
3292
|
+
async function networkMode(id, engine) {
|
|
3293
|
+
const r = await docker([
|
|
3294
|
+
"inspect",
|
|
3295
|
+
"--format",
|
|
3296
|
+
"{{.HostConfig.NetworkMode}}",
|
|
3297
|
+
id
|
|
3298
|
+
], { engine }).catch(() => null);
|
|
3299
|
+
return r?.exitCode === 0 ? r.stdout.toString("utf8").trim() || "unknown" : "unknown";
|
|
3300
|
+
}
|
|
2114
3301
|
/**
|
|
2115
3302
|
* A harness sandbox session backed by one local Docker container. Commands and file I/O go
|
|
2116
3303
|
* through `docker exec`. With the egress gateway, ports are reached through its tunnel whatever
|
|
@@ -2125,6 +3312,8 @@ async function createDockerSession(options) {
|
|
|
2125
3312
|
};
|
|
2126
3313
|
const lifetime = Math.ceil((options.lifetimeMs ?? 864e5) / 1e3);
|
|
2127
3314
|
const signal = options.abortSignal;
|
|
3315
|
+
const { engine } = options;
|
|
3316
|
+
const spec = engineSpec(engine);
|
|
2128
3317
|
const env = {
|
|
2129
3318
|
...userEnv(options),
|
|
2130
3319
|
...options.env
|
|
@@ -2139,20 +3328,21 @@ async function createDockerSession(options) {
|
|
|
2139
3328
|
...Object.keys(env).flatMap((k) => ["--env", k]),
|
|
2140
3329
|
"--publish",
|
|
2141
3330
|
`127.0.0.1::${BRIDGE_PORT}`,
|
|
2142
|
-
"--workdir",
|
|
2143
|
-
SANDBOX_ROOT,
|
|
3331
|
+
...spec.createsWorkdir ? ["--workdir", SANDBOX_ROOT] : [],
|
|
2144
3332
|
"--entrypoint",
|
|
2145
3333
|
"sleep",
|
|
2146
3334
|
options.image,
|
|
2147
3335
|
String(lifetime)
|
|
2148
3336
|
], {
|
|
2149
3337
|
signal,
|
|
2150
|
-
env
|
|
3338
|
+
env,
|
|
3339
|
+
engine
|
|
2151
3340
|
}).catch((error) => {
|
|
2152
|
-
|
|
2153
|
-
if (
|
|
2154
|
-
|
|
2155
|
-
|
|
3341
|
+
const sweep = () => sweepLabelDetached(`${LABEL.session}=${nonce}`, void 0, engine);
|
|
3342
|
+
if (signal?.aborted) sweep();
|
|
3343
|
+
if (/unable to find user|no matching entries in passwd/i.test(error.message)) {
|
|
3344
|
+
sweep();
|
|
3345
|
+
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 }).`);
|
|
2156
3346
|
}
|
|
2157
3347
|
throw error;
|
|
2158
3348
|
});
|
|
@@ -2160,33 +3350,57 @@ async function createDockerSession(options) {
|
|
|
2160
3350
|
let removed;
|
|
2161
3351
|
const remove = () => {
|
|
2162
3352
|
gateway?.close();
|
|
2163
|
-
removed ??= removeContainerDetached(id);
|
|
3353
|
+
removed ??= removeContainerDetached(id, engine);
|
|
2164
3354
|
return removed;
|
|
2165
3355
|
};
|
|
2166
3356
|
if (signal?.aborted) {
|
|
2167
3357
|
await remove();
|
|
2168
3358
|
throw signal.reason;
|
|
2169
3359
|
}
|
|
3360
|
+
if (!spec.createsWorkdir) {
|
|
3361
|
+
const made = await docker([
|
|
3362
|
+
"exec",
|
|
3363
|
+
"--user",
|
|
3364
|
+
"0:0",
|
|
3365
|
+
id,
|
|
3366
|
+
"mkdir",
|
|
3367
|
+
"-p",
|
|
3368
|
+
SANDBOX_ROOT
|
|
3369
|
+
], {
|
|
3370
|
+
engine,
|
|
3371
|
+
signal
|
|
3372
|
+
}).catch((error) => ({
|
|
3373
|
+
exitCode: 1,
|
|
3374
|
+
stderr: error.message
|
|
3375
|
+
}));
|
|
3376
|
+
if (made.exitCode !== 0) {
|
|
3377
|
+
await remove();
|
|
3378
|
+
if (signal?.aborted) throw signal.reason;
|
|
3379
|
+
throw new Error(`Couldn't create ${SANDBOX_ROOT} in the container: ${made.stderr.trim()}`);
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
2170
3382
|
const egressOptions = options.egress === true ? {} : options.egress || void 0;
|
|
2171
3383
|
let egress;
|
|
2172
3384
|
let detached = [];
|
|
2173
3385
|
const attach = async (policy) => {
|
|
2174
3386
|
if (isRestricted(policy) && detached.length === 0) {
|
|
2175
|
-
detached = await networksOf(id);
|
|
3387
|
+
detached = await networksOf(id, engine);
|
|
3388
|
+
const mode = detached.length === 0 ? await networkMode(id, engine) : "none";
|
|
3389
|
+
if (detached.length === 0 && mode !== "none") throw new Error(`the container's network is ${mode}, which ${spec.name} can't detach it from, so a restricted policy would still have a route out`);
|
|
2176
3390
|
for (const n of detached) await dockerOk([
|
|
2177
3391
|
"network",
|
|
2178
3392
|
"disconnect",
|
|
2179
3393
|
"--force",
|
|
2180
3394
|
n,
|
|
2181
3395
|
id
|
|
2182
|
-
]);
|
|
3396
|
+
], { engine });
|
|
2183
3397
|
} else if (!isRestricted(policy) && detached.length > 0) {
|
|
2184
3398
|
for (const n of detached) await dockerOk([
|
|
2185
3399
|
"network",
|
|
2186
3400
|
"connect",
|
|
2187
3401
|
n,
|
|
2188
3402
|
id
|
|
2189
|
-
]);
|
|
3403
|
+
], { engine });
|
|
2190
3404
|
detached = [];
|
|
2191
3405
|
}
|
|
2192
3406
|
};
|
|
@@ -2197,7 +3411,7 @@ async function createDockerSession(options) {
|
|
|
2197
3411
|
gateway = await startGateway(id, {
|
|
2198
3412
|
...rest,
|
|
2199
3413
|
policy
|
|
2200
|
-
}, signal);
|
|
3414
|
+
}, signal, engine);
|
|
2201
3415
|
egress = { active: true };
|
|
2202
3416
|
} catch (error) {
|
|
2203
3417
|
if (signal?.aborted || isRestricted(policy)) {
|
|
@@ -2240,14 +3454,15 @@ async function createDockerSession(options) {
|
|
|
2240
3454
|
"sh",
|
|
2241
3455
|
"-c",
|
|
2242
3456
|
`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`
|
|
2243
|
-
]).then(() => {}, () => {});
|
|
3457
|
+
], { engine }).then(() => {}, () => {});
|
|
2244
3458
|
const run = async (o) => {
|
|
2245
3459
|
const env = envFor(o.env);
|
|
2246
3460
|
const marker = nextMarker();
|
|
2247
3461
|
try {
|
|
2248
3462
|
const r = await docker(execArgs(o.command, marker, o.workingDirectory, env), {
|
|
2249
3463
|
env,
|
|
2250
|
-
signal: o.abortSignal
|
|
3464
|
+
signal: o.abortSignal,
|
|
3465
|
+
engine
|
|
2251
3466
|
});
|
|
2252
3467
|
return {
|
|
2253
3468
|
exitCode: r.exitCode,
|
|
@@ -2266,7 +3481,10 @@ async function createDockerSession(options) {
|
|
|
2266
3481
|
"cat",
|
|
2267
3482
|
"--",
|
|
2268
3483
|
posix.resolve(SANDBOX_ROOT, o.path)
|
|
2269
|
-
], {
|
|
3484
|
+
], {
|
|
3485
|
+
signal: o.abortSignal,
|
|
3486
|
+
engine
|
|
3487
|
+
});
|
|
2270
3488
|
return r.exitCode === 0 ? new Uint8Array(r.stdout) : null;
|
|
2271
3489
|
};
|
|
2272
3490
|
const writeBinaryFile = async (o) => {
|
|
@@ -2280,7 +3498,8 @@ async function createDockerSession(options) {
|
|
|
2280
3498
|
`mkdir -p ${shellQuote(posix.dirname(path))} && cat > ${shellQuote(path)}`
|
|
2281
3499
|
], {
|
|
2282
3500
|
input: Buffer.from(o.content),
|
|
2283
|
-
signal: o.abortSignal
|
|
3501
|
+
signal: o.abortSignal,
|
|
3502
|
+
engine
|
|
2284
3503
|
});
|
|
2285
3504
|
};
|
|
2286
3505
|
const brokering = gateway ? {
|
|
@@ -2303,7 +3522,7 @@ async function createDockerSession(options) {
|
|
|
2303
3522
|
} : {};
|
|
2304
3523
|
const session = {
|
|
2305
3524
|
id,
|
|
2306
|
-
description:
|
|
3525
|
+
description: `${spec.name} container ${id.slice(0, 12)} (${options.image})`,
|
|
2307
3526
|
defaultWorkingDirectory: SANDBOX_ROOT,
|
|
2308
3527
|
ports: [BRIDGE_PORT],
|
|
2309
3528
|
get egress() {
|
|
@@ -2315,29 +3534,31 @@ async function createDockerSession(options) {
|
|
|
2315
3534
|
},
|
|
2316
3535
|
...brokering,
|
|
2317
3536
|
...substituting,
|
|
3537
|
+
oomKills: () => oomKills(id, engine),
|
|
2318
3538
|
run,
|
|
2319
3539
|
async spawn(o) {
|
|
2320
3540
|
const env = envFor(o.env);
|
|
2321
3541
|
const marker = nextMarker();
|
|
2322
3542
|
const args = execArgs(o.command, marker, o.workingDirectory, env);
|
|
2323
3543
|
const startedAt = Date.now();
|
|
2324
|
-
const child = spawn(
|
|
3544
|
+
const child = spawn(spec.bin, args, {
|
|
2325
3545
|
stdio: [
|
|
2326
3546
|
"ignore",
|
|
2327
3547
|
"pipe",
|
|
2328
3548
|
"pipe"
|
|
2329
3549
|
],
|
|
2330
3550
|
detached: true,
|
|
2331
|
-
env:
|
|
3551
|
+
env: {
|
|
2332
3552
|
...process.env,
|
|
3553
|
+
...engineEnv(engine),
|
|
2333
3554
|
...env
|
|
2334
|
-
}
|
|
3555
|
+
}
|
|
2335
3556
|
});
|
|
2336
3557
|
const exited = new Promise((resolve) => child.on("close", (code) => {
|
|
2337
3558
|
traceDocker(args, startedAt, {
|
|
2338
3559
|
exitCode: code,
|
|
2339
3560
|
stdout: "<streamed>"
|
|
2340
|
-
});
|
|
3561
|
+
}, spec.bin);
|
|
2341
3562
|
resolve({ exitCode: code ?? 1 });
|
|
2342
3563
|
}));
|
|
2343
3564
|
const kill = async () => {
|
|
@@ -2386,10 +3607,10 @@ async function createDockerSession(options) {
|
|
|
2386
3607
|
"port",
|
|
2387
3608
|
id,
|
|
2388
3609
|
`${port}/tcp`
|
|
2389
|
-
]);
|
|
3610
|
+
], { engine });
|
|
2390
3611
|
const hostPort = mapped.stdout.toString("utf8").split("\n")[0]?.split(":").at(-1);
|
|
2391
3612
|
if (mapped.exitCode !== 0 || !hostPort) {
|
|
2392
|
-
const reason = mapped.stderr.trim() ||
|
|
3613
|
+
const reason = mapped.stderr.trim() || `${spec.name} reports no host port for it`;
|
|
2393
3614
|
throw new Error(`Port ${port} of ${id.slice(0, 12)} isn't reachable: ${reason}`);
|
|
2394
3615
|
}
|
|
2395
3616
|
return { url: `${scheme}://127.0.0.1:${hostPort}` };
|
|
@@ -2420,7 +3641,7 @@ async function createDockerSession(options) {
|
|
|
2420
3641
|
/** The session as an AI SDK harness sandbox provider. */
|
|
2421
3642
|
const dockerSandboxProvider = (options) => ({
|
|
2422
3643
|
specificationVersion: "harness-sandbox-v1",
|
|
2423
|
-
providerId: "reeval-docker",
|
|
3644
|
+
providerId: options.engine === "podman" ? "reeval-podman" : "reeval-docker",
|
|
2424
3645
|
async createSession(o) {
|
|
2425
3646
|
const session = await createDockerSession({
|
|
2426
3647
|
...options,
|
|
@@ -2433,7 +3654,7 @@ const dockerSandboxProvider = (options) => ({
|
|
|
2433
3654
|
//#endregion
|
|
2434
3655
|
//#region ../sandbox-docker/src/build-image.ts
|
|
2435
3656
|
const PNPM_VERSION = "10.33.3";
|
|
2436
|
-
/** npm before 11.6 (node:22 ships 10.9) can crash on circular peer sets (npm/cli#8261)
|
|
3657
|
+
/** npm before 11.6 (node:22 ships 10.9) can crash on circular peer sets (npm/cli#8261). */
|
|
2437
3658
|
const NPM_VERSION = "11.20.0";
|
|
2438
3659
|
const sha256 = (text) => createHash("sha256").update(text).digest("hex");
|
|
2439
3660
|
const slugify = (text, max) => text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max).replace(/-+$/, "");
|
|
@@ -2453,22 +3674,22 @@ const BOOTSTRAPS = {
|
|
|
2453
3674
|
name: "Cursor",
|
|
2454
3675
|
cli: "@cursor/sdk",
|
|
2455
3676
|
version: `node -p "'@cursor/sdk ' + require(process.env.HOME + '/.ai-sdk-harness/.harness-bootstrap/cursor-sdk/node_modules/@cursor/sdk/package.json').version"`,
|
|
2456
|
-
adapter: async () => (await import("./src-
|
|
3677
|
+
adapter: async () => (await import("./src-DTbrY1hi.js")).createCursorSdk()
|
|
2457
3678
|
},
|
|
2458
3679
|
"claude-code": {
|
|
2459
3680
|
name: "Claude Code",
|
|
2460
3681
|
cli: "claude",
|
|
2461
|
-
adapter: async () => (await import("./dist-
|
|
3682
|
+
adapter: async () => (await import("./dist-B8hJImYt.js")).createClaudeCode()
|
|
2462
3683
|
},
|
|
2463
3684
|
antigravity: {
|
|
2464
3685
|
name: "Antigravity",
|
|
2465
3686
|
cli: "agy",
|
|
2466
|
-
adapter: async () => (await import("./src-
|
|
3687
|
+
adapter: async () => (await import("./src-DNnG4e4R.js")).createAntigravity()
|
|
2467
3688
|
},
|
|
2468
3689
|
opencode: {
|
|
2469
3690
|
name: "OpenCode",
|
|
2470
3691
|
cli: "opencode",
|
|
2471
|
-
adapter: async () => (await import("./src-
|
|
3692
|
+
adapter: async () => (await import("./src-ofUaYbuY.js")).createOpenCode({
|
|
2472
3693
|
auth: { GOOGLE_GENERATIVE_AI_API_KEY: "unused" },
|
|
2473
3694
|
model: "google/unused"
|
|
2474
3695
|
})
|
|
@@ -2476,7 +3697,7 @@ const BOOTSTRAPS = {
|
|
|
2476
3697
|
goose: {
|
|
2477
3698
|
name: "Goose",
|
|
2478
3699
|
cli: "goose",
|
|
2479
|
-
adapter: async () => (await import("./src-
|
|
3700
|
+
adapter: async () => (await import("./src-DdyJEh2v.js")).createGoose({
|
|
2480
3701
|
auth: { GOOGLE_API_KEY: "unused" },
|
|
2481
3702
|
model: "unused"
|
|
2482
3703
|
})
|
|
@@ -2484,12 +3705,28 @@ const BOOTSTRAPS = {
|
|
|
2484
3705
|
pi: {
|
|
2485
3706
|
name: "pi",
|
|
2486
3707
|
cli: "pi",
|
|
2487
|
-
adapter: async () => (await import("./src-
|
|
3708
|
+
adapter: async () => (await import("./src-jzagbuqH.js")).createPi({ auth: { GEMINI_API_KEY: "unused" } })
|
|
2488
3709
|
},
|
|
2489
3710
|
codex: {
|
|
2490
3711
|
name: "Codex",
|
|
2491
3712
|
cli: "codex",
|
|
2492
|
-
adapter: async () => (await import("./src-
|
|
3713
|
+
adapter: async () => (await import("./src-Dj9PAroc.js")).createCodex({ auth: { OPENAI_API_KEY: "unused" } })
|
|
3714
|
+
},
|
|
3715
|
+
fx: {
|
|
3716
|
+
name: "fx",
|
|
3717
|
+
cli: "fx",
|
|
3718
|
+
adapter: async () => (await import("./src-C_lPtowT.js")).createFx({
|
|
3719
|
+
auth: { AI_GATEWAY_API_KEY: "unused" },
|
|
3720
|
+
model: "unused/unused"
|
|
3721
|
+
})
|
|
3722
|
+
},
|
|
3723
|
+
"grok-build": {
|
|
3724
|
+
name: "Grok Build",
|
|
3725
|
+
cli: "grok",
|
|
3726
|
+
adapter: async () => (await import("./src-Du8H85Qq.js")).createGrokBuild({
|
|
3727
|
+
auth: { XAI_API_KEY: "unused" },
|
|
3728
|
+
model: "unused"
|
|
3729
|
+
})
|
|
2493
3730
|
}
|
|
2494
3731
|
};
|
|
2495
3732
|
/** Keeps the image's `PATH` in login shells, which Debian's `/etc/profile` resets. */
|
|
@@ -2521,10 +3758,10 @@ WORKDIR ${SANDBOX_ROOT}/${WORKSPACE_DIR}
|
|
|
2521
3758
|
`;
|
|
2522
3759
|
const tail = (text) => text.trim().split("\n").slice(-15).join("\n");
|
|
2523
3760
|
const clipLine = (text, max = 300) => text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
2524
|
-
/** Why a `docker build` failed, in a line: the failing step's exit, or BuildKit's own error. */
|
|
2525
|
-
function buildReason(error) {
|
|
2526
|
-
if (!error) return
|
|
2527
|
-
const exit = /^process ".*" did not complete successfully: exit code: (\d+)$/s.exec(error);
|
|
3761
|
+
/** Why a `docker build` failed, in a line: the failing step's exit, or BuildKit's (or buildah's) own error. */
|
|
3762
|
+
function buildReason(error, engine) {
|
|
3763
|
+
if (!error) return `${engineSpec(engine).bin} build failed`;
|
|
3764
|
+
const exit = /^process ".*" did not complete successfully: exit code: (\d+)$/s.exec(error) ?? /^while running runtime: exit status (\d+)$/.exec(error);
|
|
2528
3765
|
return exit ? `a RUN step exited ${exit[1]}` : clipLine(error.replace(/\s+/g, " "));
|
|
2529
3766
|
}
|
|
2530
3767
|
/**
|
|
@@ -2536,12 +3773,13 @@ function buildError(output, what, reason, keep = () => true) {
|
|
|
2536
3773
|
const text = [parsed.error ?? "", ...parsed.output].join("\n");
|
|
2537
3774
|
const cause = failureCause(text, parsed.exitCode);
|
|
2538
3775
|
parsed.output = parsed.output.filter(keep);
|
|
2539
|
-
const why = reason?.(parsed) ?? buildReason(parsed.error);
|
|
3776
|
+
const why = reason?.(parsed) ?? buildReason(parsed.error, what.engine);
|
|
2540
3777
|
return new ImageBuildError(failureMessage({
|
|
2541
3778
|
headline: `${what.headline} failed: ${why}`,
|
|
2542
3779
|
cause,
|
|
2543
3780
|
output: parsed.output,
|
|
2544
|
-
log: what.log ?? null
|
|
3781
|
+
log: what.log ?? null,
|
|
3782
|
+
...what.engine ? { engine: what.engine } : {}
|
|
2545
3783
|
}), {
|
|
2546
3784
|
stage: what.stage,
|
|
2547
3785
|
ref: what.ref,
|
|
@@ -2556,12 +3794,12 @@ const BUILT_BY = ["--label", `${LABEL.managed}=image`];
|
|
|
2556
3794
|
* Removes the images reeval built that nothing tags any more (a rebuild's old image) and the
|
|
2557
3795
|
* intermediate `-base` tags of `from` builds. Returns how many it removed.
|
|
2558
3796
|
*/
|
|
2559
|
-
async function pruneImages() {
|
|
3797
|
+
async function pruneImages(engine) {
|
|
2560
3798
|
const list = async (args) => (await docker([
|
|
2561
3799
|
"image",
|
|
2562
3800
|
"ls",
|
|
2563
3801
|
...args
|
|
2564
|
-
])).stdout.toString("utf8").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3802
|
+
], { engine })).stdout.toString("utf8").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2565
3803
|
const dangling = [...await list([
|
|
2566
3804
|
"-q",
|
|
2567
3805
|
"--filter",
|
|
@@ -2585,46 +3823,93 @@ async function pruneImages() {
|
|
|
2585
3823
|
"image",
|
|
2586
3824
|
"rm",
|
|
2587
3825
|
ref
|
|
2588
|
-
]).catch(() => null))?.exitCode === 0) removed++;
|
|
3826
|
+
], { engine }).catch(() => null))?.exitCode === 0) removed++;
|
|
2589
3827
|
return removed;
|
|
2590
3828
|
}
|
|
3829
|
+
/** Docker's names for Node's architectures, as image indexes list platforms. */
|
|
3830
|
+
const OCI_ARCH = {
|
|
3831
|
+
x64: "amd64",
|
|
3832
|
+
arm64: "arm64",
|
|
3833
|
+
arm: "arm",
|
|
3834
|
+
ppc64: "ppc64le"
|
|
3835
|
+
};
|
|
3836
|
+
/**
|
|
3837
|
+
* A registry digest for `from` without Docker's buildx: skopeo's, which is the same digest
|
|
3838
|
+
* buildx gives, or else this platform's manifest from `podman manifest inspect`.
|
|
3839
|
+
*/
|
|
3840
|
+
async function podmanRemoteDigest(from, signal) {
|
|
3841
|
+
const skopeo = await run("skopeo", [
|
|
3842
|
+
"inspect",
|
|
3843
|
+
"--format",
|
|
3844
|
+
"{{.Digest}}",
|
|
3845
|
+
`docker://${from}`
|
|
3846
|
+
], { ...signal ? { signal } : {} }).catch(() => null);
|
|
3847
|
+
const digest = skopeo?.exitCode === 0 ? skopeo.stdout.trim() : "";
|
|
3848
|
+
if (digest.startsWith("sha256:")) return { digest };
|
|
3849
|
+
const r = await docker([
|
|
3850
|
+
"manifest",
|
|
3851
|
+
"inspect",
|
|
3852
|
+
from
|
|
3853
|
+
], {
|
|
3854
|
+
signal,
|
|
3855
|
+
engine: "podman"
|
|
3856
|
+
}).catch(() => null);
|
|
3857
|
+
if (r?.exitCode === 0) {
|
|
3858
|
+
const manifest = JSON.parse(r.stdout.toString("utf8"));
|
|
3859
|
+
const arch = OCI_ARCH[process.arch] ?? process.arch;
|
|
3860
|
+
const own = manifest.manifests?.find((m) => m.platform?.os === "linux" && m.platform.architecture === arch)?.digest;
|
|
3861
|
+
if (own?.startsWith("sha256:")) return { digest: own };
|
|
3862
|
+
}
|
|
3863
|
+
return { error: (r?.stderr.trim() || skopeo?.stderr.trim() || "").replace(/^(?:Error|time="[^"]*" level=fatal msg=): ?/, "").replace(/\s+/g, " ") || `${from} doesn't resolve` };
|
|
3864
|
+
}
|
|
2591
3865
|
/**
|
|
2592
3866
|
* What a `from` names now, without pulling: its registry digest, or for an image that's only
|
|
2593
3867
|
* local, its id (`local`). `error` when neither resolves, e.g. a typo or no network.
|
|
2594
3868
|
*/
|
|
2595
|
-
async function resolveFrom(from, signal) {
|
|
3869
|
+
async function resolveFrom(from, signal, engine) {
|
|
2596
3870
|
const pinned = /@(sha256:[0-9a-f]{64})$/.exec(from)?.[1];
|
|
2597
3871
|
if (pinned) return {
|
|
2598
3872
|
digest: pinned,
|
|
2599
3873
|
local: false
|
|
2600
3874
|
};
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
"
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
3875
|
+
let error = null;
|
|
3876
|
+
if (engine === "podman") {
|
|
3877
|
+
const remote = await podmanRemoteDigest(from, signal);
|
|
3878
|
+
if ("digest" in remote) return {
|
|
3879
|
+
digest: remote.digest,
|
|
3880
|
+
local: false
|
|
3881
|
+
};
|
|
3882
|
+
error = remote.error;
|
|
3883
|
+
} else {
|
|
3884
|
+
const remote = await docker([
|
|
3885
|
+
"buildx",
|
|
3886
|
+
"imagetools",
|
|
3887
|
+
"inspect",
|
|
3888
|
+
"--format",
|
|
3889
|
+
"{{json .Manifest.Digest}}",
|
|
3890
|
+
from
|
|
3891
|
+
], { signal }).catch(() => null);
|
|
3892
|
+
const digest = remote?.exitCode === 0 ? JSON.parse(remote.stdout.toString("utf8").trim()) : null;
|
|
3893
|
+
if (typeof digest === "string" && digest.startsWith("sha256:")) return {
|
|
3894
|
+
digest,
|
|
3895
|
+
local: false
|
|
3896
|
+
};
|
|
3897
|
+
error = remote?.stderr.trim().replace(/^ERROR: /, "").replace(/\s+/g, " ") || null;
|
|
3898
|
+
}
|
|
3899
|
+
const local = await imageInfo(from, engine).catch(() => null);
|
|
2615
3900
|
if (local) return {
|
|
2616
3901
|
digest: local.id,
|
|
2617
3902
|
local: true
|
|
2618
3903
|
};
|
|
2619
|
-
return { error:
|
|
3904
|
+
return { error: error || `${from} doesn't resolve` };
|
|
2620
3905
|
}
|
|
2621
3906
|
/** Removes a tag or an image reeval replaced, unless something still uses it. */
|
|
2622
|
-
const removeQuietly = async (ref) => {
|
|
3907
|
+
const removeQuietly = async (ref, engine) => {
|
|
2623
3908
|
await docker([
|
|
2624
3909
|
"image",
|
|
2625
3910
|
"rm",
|
|
2626
3911
|
ref
|
|
2627
|
-
]).catch(() => {});
|
|
3912
|
+
], { engine }).catch(() => {});
|
|
2628
3913
|
};
|
|
2629
3914
|
/** The hash of reeval's base image build context, which its images carry as a label. */
|
|
2630
3915
|
function contextHash() {
|
|
@@ -2644,14 +3929,14 @@ function contextHash() {
|
|
|
2644
3929
|
* intermediate `-base` tag of a `from` build, are removed once the new one is committed.
|
|
2645
3930
|
*/
|
|
2646
3931
|
async function buildImage(log, options) {
|
|
2647
|
-
const { harness, from, signal } = options;
|
|
2648
|
-
const resolved = from ? await resolveFrom(from, signal) : null;
|
|
3932
|
+
const { harness, from, signal, engine } = options;
|
|
3933
|
+
const resolved = from ? await resolveFrom(from, signal, engine) : null;
|
|
2649
3934
|
const digest = resolved && "digest" in resolved ? resolved.digest : null;
|
|
2650
3935
|
const pinned = from && digest && resolved && "local" in resolved && !resolved.local && !from.includes("@") ? `${from}@${digest}` : from;
|
|
2651
3936
|
const bootstrap = BOOTSTRAPS[harness];
|
|
2652
3937
|
const base = from ? `${harnessImageFor(harness, from)}-base` : BASE_IMAGE;
|
|
2653
3938
|
const target = from ? harnessImageFor(harness, from) : imageFor(harness);
|
|
2654
|
-
const previous = [(await imageInfo(target))?.id, from ? null : (await imageInfo(base))?.id];
|
|
3939
|
+
const previous = [(await imageInfo(target, engine))?.id, from ? null : (await imageInfo(base, engine))?.id];
|
|
2655
3940
|
log(`Building ${base}${from ? ` from ${from}` : ""}`);
|
|
2656
3941
|
const build = from ? await docker([
|
|
2657
3942
|
"build",
|
|
@@ -2664,29 +3949,39 @@ async function buildImage(log, options) {
|
|
|
2664
3949
|
"-"
|
|
2665
3950
|
], {
|
|
2666
3951
|
input: toolchainDockerfile(pinned ?? from),
|
|
2667
|
-
signal
|
|
3952
|
+
signal,
|
|
3953
|
+
engine
|
|
2668
3954
|
}) : await docker([
|
|
2669
3955
|
"build",
|
|
2670
3956
|
"--progress",
|
|
2671
3957
|
"plain",
|
|
2672
3958
|
...BUILT_BY,
|
|
3959
|
+
"--build-arg",
|
|
3960
|
+
`NPM_VERSION=${NPM_VERSION}`,
|
|
3961
|
+
"--build-arg",
|
|
3962
|
+
`PNPM_VERSION=${PNPM_VERSION}`,
|
|
2673
3963
|
"--tag",
|
|
2674
3964
|
base,
|
|
2675
3965
|
DOCKER_CONTEXT
|
|
2676
|
-
], {
|
|
3966
|
+
], {
|
|
3967
|
+
signal,
|
|
3968
|
+
engine
|
|
3969
|
+
});
|
|
2677
3970
|
const output = `${build.stdout.toString("utf8")}${build.stderr}`;
|
|
2678
3971
|
options.output?.write(output);
|
|
2679
3972
|
if (build.exitCode !== 0) throw buildError(output, {
|
|
2680
3973
|
stage: from ? "from" : "harness",
|
|
2681
3974
|
ref: target,
|
|
2682
3975
|
headline: from ? `Building the toolchain layer FROM ${from}` : "Building reeval's base image",
|
|
2683
|
-
log: options.output?.path ?? null
|
|
3976
|
+
log: options.output?.path ?? null,
|
|
3977
|
+
engine
|
|
2684
3978
|
}, (parsed) => parsed.output.find((l) => l.startsWith("reeval needs a Debian")) ?? null);
|
|
2685
3979
|
log(`Installing the ${bootstrap.name} harness bootstrap`);
|
|
2686
3980
|
const session = await createDockerSession({
|
|
2687
3981
|
image: base,
|
|
2688
3982
|
lifetimeMs: 18e5,
|
|
2689
|
-
...signal ? { abortSignal: signal } : {}
|
|
3983
|
+
...signal ? { abortSignal: signal } : {},
|
|
3984
|
+
...engine ? { engine } : {}
|
|
2690
3985
|
});
|
|
2691
3986
|
let agentVersion;
|
|
2692
3987
|
try {
|
|
@@ -2706,7 +4001,7 @@ async function buildImage(log, options) {
|
|
|
2706
4001
|
"CMD [\"bash\"]",
|
|
2707
4002
|
session.id,
|
|
2708
4003
|
target
|
|
2709
|
-
]);
|
|
4004
|
+
], { engine });
|
|
2710
4005
|
} catch (error) {
|
|
2711
4006
|
if (signal?.aborted || error instanceof ImageBuildError) throw error;
|
|
2712
4007
|
const [first = "", ...rest] = error.message.split("\n");
|
|
@@ -2716,7 +4011,8 @@ async function buildImage(log, options) {
|
|
|
2716
4011
|
headline: `Baking the ${bootstrap.name} CLI onto ${from ?? "reeval-sandbox-base:latest"} failed: ${clipLine(first)}`,
|
|
2717
4012
|
cause,
|
|
2718
4013
|
output: outputTail(rest.join("\n")),
|
|
2719
|
-
log: options.output?.path ?? null
|
|
4014
|
+
log: options.output?.path ?? null,
|
|
4015
|
+
...engine ? { engine } : {}
|
|
2720
4016
|
}), {
|
|
2721
4017
|
stage: "harness",
|
|
2722
4018
|
ref: target,
|
|
@@ -2727,9 +4023,9 @@ async function buildImage(log, options) {
|
|
|
2727
4023
|
} finally {
|
|
2728
4024
|
await session.destroy();
|
|
2729
4025
|
}
|
|
2730
|
-
if (from) await removeQuietly(base);
|
|
2731
|
-
const now = (await imageInfo(target))?.id;
|
|
2732
|
-
for (const id of previous) if (id && id !== now) await removeQuietly(id);
|
|
4026
|
+
if (from) await removeQuietly(base, engine);
|
|
4027
|
+
const now = (await imageInfo(target, engine))?.id;
|
|
4028
|
+
for (const id of previous) if (id && id !== now) await removeQuietly(id, engine);
|
|
2733
4029
|
return {
|
|
2734
4030
|
image: target,
|
|
2735
4031
|
agentVersion
|
|
@@ -2772,15 +4068,18 @@ function installScript(steps) {
|
|
|
2772
4068
|
}
|
|
2773
4069
|
/** Builds the install layer if it's missing. `output` gets the build's output, for a log file. */
|
|
2774
4070
|
async function buildInstallLayer(parent, steps, options = {}) {
|
|
2775
|
-
const { signal } = options;
|
|
4071
|
+
const { signal, engine } = options;
|
|
2776
4072
|
const tag = installLayerTag(parent, steps);
|
|
2777
4073
|
if ((await docker([
|
|
2778
4074
|
"image",
|
|
2779
4075
|
"inspect",
|
|
2780
4076
|
"--format",
|
|
2781
|
-
"{{.
|
|
4077
|
+
"{{.Created}}",
|
|
2782
4078
|
tag
|
|
2783
|
-
], {
|
|
4079
|
+
], {
|
|
4080
|
+
signal,
|
|
4081
|
+
engine
|
|
4082
|
+
})).exitCode === 0) return tag;
|
|
2784
4083
|
const script = installScript(steps);
|
|
2785
4084
|
const encoded = Buffer.from(script).toString("base64");
|
|
2786
4085
|
const home = `$(getent passwd ${SANDBOX_USER} | cut -d: -f6)`;
|
|
@@ -2803,7 +4102,8 @@ async function buildInstallLayer(parent, steps, options = {}) {
|
|
|
2803
4102
|
"-"
|
|
2804
4103
|
], {
|
|
2805
4104
|
input: dockerfile,
|
|
2806
|
-
signal
|
|
4105
|
+
signal,
|
|
4106
|
+
engine
|
|
2807
4107
|
});
|
|
2808
4108
|
const output = `${build.stdout.toString("utf8")}${build.stderr}`;
|
|
2809
4109
|
options.output?.write(`${script}\n${dockerfile}\n\n${output}`);
|
|
@@ -2813,7 +4113,8 @@ async function buildInstallLayer(parent, steps, options = {}) {
|
|
|
2813
4113
|
stage: "install",
|
|
2814
4114
|
ref: tag,
|
|
2815
4115
|
headline: `Building the install layer (${`${steps.length} command${steps.length === 1 ? "" : "s"}`})`,
|
|
2816
|
-
log: options.output?.path ?? null
|
|
4116
|
+
log: options.output?.path ?? null,
|
|
4117
|
+
engine
|
|
2817
4118
|
}, () => failed ? `install ${failed[1]} exited ${failed[2]}: ${clipLine(failed[3] ?? "")}` : null, (line) => !line.startsWith("reeval: install "));
|
|
2818
4119
|
}
|
|
2819
4120
|
return tag;
|
|
@@ -2873,16 +4174,16 @@ function contextFiles(context, dockerfile) {
|
|
|
2873
4174
|
return files.sort();
|
|
2874
4175
|
}
|
|
2875
4176
|
/** Whether the Dockerfile starts from reeval's sandbox image, which then has to exist first. */
|
|
2876
|
-
const
|
|
4177
|
+
const usesReevalBase = (dockerfile) => new RegExp(`\\$\\{?${REEVAL_BASE_ARG}\\b`).test(readFileSync(dockerfile, "utf8"));
|
|
2877
4178
|
/**
|
|
2878
4179
|
* The tag of a `docker({ dockerfile })` build for a harness: a hash of the Dockerfile, every file
|
|
2879
4180
|
* of its build context (its directory, less `.dockerignore`) and, for a Dockerfile `FROM
|
|
2880
4181
|
* ${REEVAL_BASE}`, the id of the harness's image it names, so an edit to any of them, or a rebuilt reeval
|
|
2881
|
-
* image, builds anew. `
|
|
4182
|
+
* image, builds anew. `reevalBase` is that id (null when it isn't built).
|
|
2882
4183
|
*/
|
|
2883
|
-
function dockerfileTag(dockerfile, harness,
|
|
4184
|
+
function dockerfileTag(dockerfile, harness, reevalBase = null) {
|
|
2884
4185
|
const context = dirname(dockerfile);
|
|
2885
|
-
const base =
|
|
4186
|
+
const base = usesReevalBase(dockerfile) ? `${imageFor(harness)}@${reevalBase ?? "unbuilt"}` : "";
|
|
2886
4187
|
const hash = createHash("sha256").update(`${REEVAL_BASE_ARG}=${base}\0`);
|
|
2887
4188
|
for (const path of contextFiles(context, dockerfile)) {
|
|
2888
4189
|
const full = join(context, path);
|
|
@@ -2899,14 +4200,14 @@ function dockerfileTag(dockerfile, harness, esBase = null) {
|
|
|
2899
4200
|
}
|
|
2900
4201
|
/**
|
|
2901
4202
|
* Builds the user's Dockerfile with plain `docker build` (so Docker's layer cache applies), from
|
|
2902
|
-
* its directory, with `REEVAL_BASE` set to the harness's sandbox image (whose id is `
|
|
4203
|
+
* its directory, with `REEVAL_BASE` set to the harness's sandbox image (whose id is `reevalBase`).
|
|
2903
4204
|
* Rebuilds even when the tag exists with `force`, as `reeval image build` does, and then removes the
|
|
2904
4205
|
* image the tag pointed at before. `name` is the path failures name.
|
|
2905
4206
|
*/
|
|
2906
4207
|
async function buildDockerfile(dockerfile, options) {
|
|
2907
|
-
const { harness, signal } = options;
|
|
2908
|
-
const { tag } = dockerfileTag(dockerfile, harness, options.
|
|
2909
|
-
const previous = (await imageInfo(tag))?.id;
|
|
4208
|
+
const { harness, signal, engine } = options;
|
|
4209
|
+
const { tag } = dockerfileTag(dockerfile, harness, options.reevalBase ?? null);
|
|
4210
|
+
const previous = (await imageInfo(tag, engine))?.id;
|
|
2910
4211
|
if (previous && !options.force) return tag;
|
|
2911
4212
|
const build = await docker([
|
|
2912
4213
|
"build",
|
|
@@ -2921,20 +4222,24 @@ async function buildDockerfile(dockerfile, options) {
|
|
|
2921
4222
|
"--tag",
|
|
2922
4223
|
tag,
|
|
2923
4224
|
dirname(dockerfile)
|
|
2924
|
-
], {
|
|
4225
|
+
], {
|
|
4226
|
+
signal,
|
|
4227
|
+
engine
|
|
4228
|
+
});
|
|
2925
4229
|
const output = `${build.stdout.toString("utf8")}${build.stderr}`;
|
|
2926
4230
|
options.output?.write(output);
|
|
2927
4231
|
if (build.exitCode !== 0) throw buildError(output, {
|
|
2928
4232
|
stage: "dockerfile",
|
|
2929
4233
|
ref: tag,
|
|
2930
4234
|
headline: `Building ${options.name ?? dockerfile}`,
|
|
2931
|
-
log: options.output?.path ?? null
|
|
4235
|
+
log: options.output?.path ?? null,
|
|
4236
|
+
engine
|
|
2932
4237
|
});
|
|
2933
|
-
const now = (await imageInfo(tag))?.id;
|
|
2934
|
-
if (previous && previous !== now) await removeQuietly(previous);
|
|
4238
|
+
const now = (await imageInfo(tag, engine))?.id;
|
|
4239
|
+
if (previous && previous !== now) await removeQuietly(previous, engine);
|
|
2935
4240
|
return tag;
|
|
2936
4241
|
}
|
|
2937
4242
|
//#endregion
|
|
2938
|
-
export {
|
|
4243
|
+
export { removeContainerDetached as $, hostAllowed as A, WORKSPACE_DIR as B, applyTransformations as C, HARNESS_HOSTS as D, DOCKER_EGRESS as E, HARNESS_PACKAGES as F, docker as G, harnessLabel as H, LABEL as I, dockerOk as J, dockerAvailable as K, SANDBOX_HOME as L, BASE_IMAGE as M, BRIDGE_PORT as N, PACKAGE_HOSTS as O, DEFAULT_IMAGE as P, isRegistryDigest as Q, SANDBOX_ROOT as R, placeholder as S, onEndpoint as T, imageFor as U, adapterVersion as V, isBakedHarness as W, imageInfo as X, dockerState as Y, imageTags as Z, ROOT_CAPABILITIES as _, buildInstallLayer as a, isTransient as at, CA_PATH as b, dockerfileTag as c, ENGINES as ct, installLayerTag as d, normalizeImageId as dt, onDockerCommand as et, installScript as f, DEFAULT_LIMITS as g, usesReevalBase as h, buildImage as i, failureCause as it, BAKED_HARNESSES as j, defaultAllowedHosts as k, dockerignoreMatcher as l, dockerSeccompFile as lt, resolveFrom as m, REEVAL_BASE_ARG as n, RETRY_DELAYS as nt, contextFiles as o, parseBuildOutput as ot, pruneImages as p, dockerCapacity as q, buildDockerfile as r, causeLine as rt, contextHash as s, retryTransient as st, DOCKER_CONTEXT as t, ImageBuildError as tt, harnessImageFor as u, engineSpec as ut, createDockerSession as v, matches as w, PROXY_PORT as x, dockerSandboxProvider as y, SANDBOX_USER as z };
|
|
2939
4244
|
|
|
2940
|
-
//# sourceMappingURL=src-
|
|
4245
|
+
//# sourceMappingURL=src-B7treWa8.js.map
|