chainlesschain 0.164.0 → 0.165.1
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/package.json +4 -4
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-CYxFDrcm.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-CPBtdiU2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-Dayq7Dp7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-6U7bPb2s.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-D2Iqg_3D.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-CdoOB7WV.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-DrnAVB8w.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-CesMi5cy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-DaX9ptax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-OrwOPxDS.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-DiHbcysC.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-CbgCkaBJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-Du5hgrh4.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-DsPOBW-p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-BwHIFkNC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BvrDQSgd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-gwAE6arx.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DW-0l4uF.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-j-NsLesB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-CibVEBOI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-W4z9vSfe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-DAcrkKxG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-6GXah-89.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-BXBLlcNm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-BoJJBkG3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-okjsl46v.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Rvs7i6nn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-CHSUxXXS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-V3BlfxsB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-Nn4LNxhA.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-GmB6DOr6.js → MarkdownRenderer-SVAgNnkt.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-DEmjFcg-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-DlopXipI.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-BG3ynX60.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-DeRzWOfT.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-BoPbOv1C.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-un-rTHp7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-Ds0oveEB.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-D09JI-yY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-DTRhiKJn.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BAnnNCim.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-Yr-1fSYi.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-S9EAolpj.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-CqcLKqJ8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-Dw0F9EcU.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BDRwUevN.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-D3_c3nKB.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-CxdMV98x.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-CgjNQFLI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-BHghIIht.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-De6pqaKM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DDAjkUkT.js → Projects-CP8vPvzs.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CUnHbNf-.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-DA2f3Wts.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C1FeHDqp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-B5guaUMN.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-eTlRhgvK.js} +1 -1
- package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-D7AqSGY0.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-JiuLm4Ja.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-CwruahW3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-CdhqaR0b.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CU0ZIlcT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-CtduPfAb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-CROXm6AY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-DiZiABFD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-D6UL3dA5.js → SpeechSettings-B5pLcXUw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-DBX6J1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-BLyRJd3-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-BKa5qzmk.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-DnD3a4M6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-DEitQeTb.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-CjHggTDq.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-CsGT7sfl.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-yr4rFDrW.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-CLvu1Sjp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-B0m7yUP8.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-C0VvP4lc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-kllBAG1M.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-C54UQNLx.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-B7y8qSvU.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DRzTh8CJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-BSMwSnCx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTg602H5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-B6EGsdvp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-DDKqJ8RD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-323Hhxxt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-BICn3Qh1.js} +1 -1
- package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BMuGd2--.js} +1 -1
- package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-BRqt4fO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BVTAl5N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-BsR7Y2NG.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-Bx044qDj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-C1GFfuqN.js} +1 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
- package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-C66NpWy5.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-CDTvsX8t.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-CGNklbP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-CHfLYwcW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-CNxg61YP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CQlHFN_g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-CUEU48o1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-CWGax2QW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-C_JzIXUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-Cf23wVMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C484EJiS.js → index-CmAInUkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-CmAubjDi.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-Co7oM1qr.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CwPUZbMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-D1naNj4U.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-D5CsBShd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DBT3YeQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-DLdrHhML.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-Dc77VR3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-Dfv2lKu3.js} +1 -1
- package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-DiW0sDkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-DpTHIRfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DqFXGzzC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-XFe-1twF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-p2HYn89m.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-pqfux8vZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-rjzk9z1Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-th2ihDc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtobShro.js → index-u2YVld9W.js} +1 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-C3Djh9fa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-BzqFmbSe.js} +1 -1
- package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-DJo_FFaW.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-_tlTiBvk.js} +1 -1
- package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-CB2ZyxUg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DZwIFD4u.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-DxZY0dgk.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-CzzGt7GL.js} +1 -1
- package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-BaIK52yz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-3-FHu9_M.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-BkRT68fK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-DVVUhV8p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-BuBLFz0c.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-kpm6LtXU.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/cowork.js +283 -61
- package/src/data/changelog.json +15 -2
- package/src/gateways/ws/action-protocol.js +293 -19
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +542 -35
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +462 -38
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +750 -50
- package/src/harness/remote-session-registry.js +795 -27
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-protocol.js +154 -10
- package/src/lib/background-agent-supervisor.js +840 -48
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +64 -0
- package/src/lib/cowork-workflow.js +853 -61
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/dynamic-workflow-facade.js +1105 -17
- package/src/lib/mcp-stdio-package-materialization.js +1 -1
- package/src/lib/remote-approval-bridge.js +745 -108
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-coordinator.js +604 -40
- package/src/lib/remote-membership-host-store.js +59 -10
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/with-file-lock.js +73 -38
- package/src/repl/agent-repl.js +70 -29
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1099 -152
- package/src/runtime/headless-runner.js +22 -5
- package/src/runtime/headless-stream.js +19 -3
- package/src/workers/background-agent-keeper.js +249 -58
- package/src/workers/background-agent-worker.js +7 -0
- package/src/workers/background-turn-bootstrap.js +29 -2
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
|
@@ -18,7 +18,7 @@ import fs from "fs";
|
|
|
18
18
|
import path from "path";
|
|
19
19
|
import broker from "../lib/process-execution-broker/index.js";
|
|
20
20
|
import os from "os";
|
|
21
|
-
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
22
22
|
import { isProxy } from "node:util/types";
|
|
23
23
|
import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
|
|
24
24
|
import sharedShellPolicy from "./coding-agent-shell-policy.cjs";
|
|
@@ -28,6 +28,12 @@ import sharedHookRunner from "../lib/hook-runner.js";
|
|
|
28
28
|
import sharedHookEvents from "../lib/settings-hook-events.js";
|
|
29
29
|
import { mergeProviderOptions } from "../lib/provider-options.js";
|
|
30
30
|
import { applyCredentialProxy } from "../lib/credential-proxy.js";
|
|
31
|
+
import {
|
|
32
|
+
commitShellApprovalSideEffects,
|
|
33
|
+
createShellExecutionDescriptor,
|
|
34
|
+
evaluateShellCommandWithApproval,
|
|
35
|
+
snapshotShellExecutionArgs,
|
|
36
|
+
} from "../lib/shell-approval.js";
|
|
31
37
|
import { describeBackgroundCommand } from "../lib/terminal-context.js";
|
|
32
38
|
import { buildTelemetryAttributes } from "../lib/telemetry-ids.js";
|
|
33
39
|
import {
|
|
@@ -230,6 +236,36 @@ function collectWorkspacePluginBinSandboxPolicy(
|
|
|
230
236
|
);
|
|
231
237
|
}
|
|
232
238
|
|
|
239
|
+
function projectPluginBinInvocationAuthority(invocation) {
|
|
240
|
+
if (!invocation) return null;
|
|
241
|
+
const identity = invocation.executableIdentity || {};
|
|
242
|
+
return {
|
|
243
|
+
command: invocation.command || null,
|
|
244
|
+
runtime: invocation.runtime || null,
|
|
245
|
+
shell: invocation.shell === true,
|
|
246
|
+
pluginId: invocation.pluginId || null,
|
|
247
|
+
pluginVersion: invocation.pluginVersion || null,
|
|
248
|
+
pluginSource: invocation.pluginSource || null,
|
|
249
|
+
scope: invocation.scope || null,
|
|
250
|
+
binName: invocation.binName || null,
|
|
251
|
+
binPath: invocation.binPath || null,
|
|
252
|
+
pluginRoot: invocation.pluginRoot || null,
|
|
253
|
+
args: Array.isArray(invocation.args) ? [...invocation.args] : [],
|
|
254
|
+
requiredBoundaries: [
|
|
255
|
+
...(invocation.sandboxPolicy?.requiredBoundaries || []),
|
|
256
|
+
].sort(),
|
|
257
|
+
executableIdentity: {
|
|
258
|
+
realPath: identity.realPath || null,
|
|
259
|
+
sha256: identity.sha256 || null,
|
|
260
|
+
bytes: identity.bytes ?? null,
|
|
261
|
+
dev: identity.dev ?? null,
|
|
262
|
+
ino: identity.ino ?? null,
|
|
263
|
+
mtimeMs: identity.mtimeMs ?? null,
|
|
264
|
+
mode: identity.mode ?? null,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
233
269
|
function rethrowRunCodeSandboxFailure(error) {
|
|
234
270
|
if (
|
|
235
271
|
error?.sandboxFailClosed === true ||
|
|
@@ -2223,6 +2259,308 @@ function attachDiffReviewAudit(result, audit) {
|
|
|
2223
2259
|
return result;
|
|
2224
2260
|
}
|
|
2225
2261
|
|
|
2262
|
+
const RUN_CODE_ARGUMENT_KEYS = new Set([
|
|
2263
|
+
"language",
|
|
2264
|
+
"code",
|
|
2265
|
+
"timeout",
|
|
2266
|
+
"persist",
|
|
2267
|
+
]);
|
|
2268
|
+
|
|
2269
|
+
function snapshotRunCodeExecutionArgs(args) {
|
|
2270
|
+
if (
|
|
2271
|
+
!args ||
|
|
2272
|
+
typeof args !== "object" ||
|
|
2273
|
+
Array.isArray(args) ||
|
|
2274
|
+
isProxy(args)
|
|
2275
|
+
) {
|
|
2276
|
+
throw new TypeError("run_code arguments must be a plain data object");
|
|
2277
|
+
}
|
|
2278
|
+
const descriptors = Object.getOwnPropertyDescriptors(args);
|
|
2279
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
2280
|
+
if (typeof key !== "string" || !RUN_CODE_ARGUMENT_KEYS.has(key)) {
|
|
2281
|
+
throw new TypeError(`run_code contains unsupported field ${String(key)}`);
|
|
2282
|
+
}
|
|
2283
|
+
if (!Object.hasOwn(descriptors[key], "value")) {
|
|
2284
|
+
throw new TypeError(`run_code field ${key} must be a data property`);
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
const language = descriptors.language?.value;
|
|
2288
|
+
const code = descriptors.code?.value;
|
|
2289
|
+
if (typeof language !== "string" || !language) {
|
|
2290
|
+
throw new TypeError("run_code language must be a non-empty string");
|
|
2291
|
+
}
|
|
2292
|
+
if (typeof code !== "string") {
|
|
2293
|
+
throw new TypeError("run_code code must be a string");
|
|
2294
|
+
}
|
|
2295
|
+
const snapshot = { language, code };
|
|
2296
|
+
if (descriptors.timeout) {
|
|
2297
|
+
const timeout = descriptors.timeout.value;
|
|
2298
|
+
if (typeof timeout !== "number" || !Number.isFinite(timeout)) {
|
|
2299
|
+
throw new TypeError("run_code timeout must be a finite number");
|
|
2300
|
+
}
|
|
2301
|
+
snapshot.timeout = timeout;
|
|
2302
|
+
}
|
|
2303
|
+
if (descriptors.persist) {
|
|
2304
|
+
const persist = descriptors.persist.value;
|
|
2305
|
+
if (typeof persist !== "boolean") {
|
|
2306
|
+
throw new TypeError("run_code persist must be a boolean");
|
|
2307
|
+
}
|
|
2308
|
+
snapshot.persist = persist;
|
|
2309
|
+
}
|
|
2310
|
+
return Object.freeze(snapshot);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
function runCodeApprovalDescriptor(args, cwd) {
|
|
2314
|
+
return Object.freeze({
|
|
2315
|
+
language: args.language,
|
|
2316
|
+
codeSha256: createHash("sha256").update(args.code, "utf8").digest("hex"),
|
|
2317
|
+
codeBytes: Buffer.byteLength(args.code, "utf8"),
|
|
2318
|
+
timeout: Math.min(Math.max(args.timeout || 60, 1), 300),
|
|
2319
|
+
persist: args.persist === true,
|
|
2320
|
+
cwd,
|
|
2321
|
+
});
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
function canonicalPolicyAuthorityData(value, state = null, depth = 0) {
|
|
2325
|
+
const budget = state || { nodes: 0, seen: new WeakSet() };
|
|
2326
|
+
budget.nodes += 1;
|
|
2327
|
+
if (budget.nodes > 4096 || depth > 12) {
|
|
2328
|
+
throw new TypeError("policy authority exceeds canonical bounds");
|
|
2329
|
+
}
|
|
2330
|
+
if (value === null) return "null";
|
|
2331
|
+
if (value === undefined) return '"$undefined"';
|
|
2332
|
+
if (typeof value === "string" || typeof value === "boolean") {
|
|
2333
|
+
return JSON.stringify(value);
|
|
2334
|
+
}
|
|
2335
|
+
if (typeof value === "number") {
|
|
2336
|
+
if (!Number.isFinite(value)) {
|
|
2337
|
+
throw new TypeError("policy authority contains a non-finite number");
|
|
2338
|
+
}
|
|
2339
|
+
return JSON.stringify(value);
|
|
2340
|
+
}
|
|
2341
|
+
if (typeof value !== "object" || isProxy(value)) {
|
|
2342
|
+
throw new TypeError("policy authority must contain data objects only");
|
|
2343
|
+
}
|
|
2344
|
+
if (budget.seen.has(value)) {
|
|
2345
|
+
throw new TypeError("policy authority must not contain cycles");
|
|
2346
|
+
}
|
|
2347
|
+
budget.seen.add(value);
|
|
2348
|
+
try {
|
|
2349
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
2350
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
2351
|
+
if (keys.some((key) => typeof key !== "string") || keys.length > 512) {
|
|
2352
|
+
throw new TypeError("policy authority contains invalid keys");
|
|
2353
|
+
}
|
|
2354
|
+
if (Array.isArray(value)) {
|
|
2355
|
+
const length = descriptors.length?.value;
|
|
2356
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > 512) {
|
|
2357
|
+
throw new TypeError("policy authority array exceeds bounds");
|
|
2358
|
+
}
|
|
2359
|
+
const entries = [];
|
|
2360
|
+
for (let index = 0; index < length; index += 1) {
|
|
2361
|
+
const descriptor = descriptors[String(index)];
|
|
2362
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value")) {
|
|
2363
|
+
throw new TypeError("policy authority arrays must be dense data");
|
|
2364
|
+
}
|
|
2365
|
+
entries.push(
|
|
2366
|
+
canonicalPolicyAuthorityData(descriptor.value, budget, depth + 1),
|
|
2367
|
+
);
|
|
2368
|
+
}
|
|
2369
|
+
const allowedKeys = new Set([
|
|
2370
|
+
"length",
|
|
2371
|
+
...Array.from({ length }, (_, index) => String(index)),
|
|
2372
|
+
]);
|
|
2373
|
+
if (keys.some((key) => !allowedKeys.has(key))) {
|
|
2374
|
+
throw new TypeError("policy authority arrays contain extra fields");
|
|
2375
|
+
}
|
|
2376
|
+
return `[${entries.join(",")}]`;
|
|
2377
|
+
}
|
|
2378
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2379
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
2380
|
+
throw new TypeError("policy authority must contain plain objects");
|
|
2381
|
+
}
|
|
2382
|
+
return `{${keys
|
|
2383
|
+
.sort()
|
|
2384
|
+
.map((key) => {
|
|
2385
|
+
const descriptor = descriptors[key];
|
|
2386
|
+
if (!Object.hasOwn(descriptor, "value")) {
|
|
2387
|
+
throw new TypeError("policy authority accessors are not allowed");
|
|
2388
|
+
}
|
|
2389
|
+
return `${JSON.stringify(key)}:${canonicalPolicyAuthorityData(
|
|
2390
|
+
descriptor.value,
|
|
2391
|
+
budget,
|
|
2392
|
+
depth + 1,
|
|
2393
|
+
)}`;
|
|
2394
|
+
})
|
|
2395
|
+
.join(",")}}`;
|
|
2396
|
+
} finally {
|
|
2397
|
+
budget.seen.delete(value);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
function digestPolicyAuthority(projection) {
|
|
2402
|
+
return createHash("sha256")
|
|
2403
|
+
.update("chainlesschain.shell-policy-authority.v1\0", "utf8")
|
|
2404
|
+
.update(canonicalPolicyAuthorityData(projection), "utf8")
|
|
2405
|
+
.digest("hex");
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
function validatePermissionRuleset(rules) {
|
|
2409
|
+
if (!rules) return null;
|
|
2410
|
+
if (typeof rules !== "object" || Array.isArray(rules)) {
|
|
2411
|
+
throw new TypeError(
|
|
2412
|
+
"permission authority provider returned an invalid ruleset",
|
|
2413
|
+
);
|
|
2414
|
+
}
|
|
2415
|
+
const normalized = {};
|
|
2416
|
+
for (const key of ["allow", "ask", "deny"]) {
|
|
2417
|
+
const value = rules[key];
|
|
2418
|
+
if (value !== undefined && !Array.isArray(value)) {
|
|
2419
|
+
throw new TypeError(
|
|
2420
|
+
"permission authority provider returned an invalid ruleset",
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2423
|
+
normalized[key] = value ? [...value] : [];
|
|
2424
|
+
}
|
|
2425
|
+
return normalized;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
function projectPermissionAuthority({ authority, rules, tool, args, cwd }) {
|
|
2429
|
+
const verdict = rules
|
|
2430
|
+
? evaluatePermissionRules({ tool, args, cwd, rules })
|
|
2431
|
+
: { decision: null, rule: null };
|
|
2432
|
+
const source = verdict.rule
|
|
2433
|
+
? authority?.sources?.[`${verdict.decision}:${verdict.rule}`] || null
|
|
2434
|
+
: null;
|
|
2435
|
+
const scopedRule = source?.startsWith("scoped:")
|
|
2436
|
+
? authority?.scoped?.rules?.find(
|
|
2437
|
+
(record) =>
|
|
2438
|
+
record.status === "active" && `scoped:${record.id}` === source,
|
|
2439
|
+
) || null
|
|
2440
|
+
: null;
|
|
2441
|
+
return {
|
|
2442
|
+
rules: rules || null,
|
|
2443
|
+
sources: authority?.sources || null,
|
|
2444
|
+
scoped: authority?.scoped || null,
|
|
2445
|
+
version: authority?.version || authority?.policyVersion || null,
|
|
2446
|
+
revision: authority?.revision ?? null,
|
|
2447
|
+
headHash: authority?.headHash || null,
|
|
2448
|
+
verdict: {
|
|
2449
|
+
decision: verdict.decision || null,
|
|
2450
|
+
rule: verdict.rule || null,
|
|
2451
|
+
source,
|
|
2452
|
+
scopedRule: scopedRule
|
|
2453
|
+
? {
|
|
2454
|
+
id: scopedRule.id || null,
|
|
2455
|
+
revision: scopedRule.revision ?? null,
|
|
2456
|
+
expiresAt: scopedRule.expiresAt ?? null,
|
|
2457
|
+
status: scopedRule.status || null,
|
|
2458
|
+
}
|
|
2459
|
+
: null,
|
|
2460
|
+
},
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
function selectedHostToolPolicy(hostAuthority, tool) {
|
|
2465
|
+
const policies = hostAuthority?.tools || hostAuthority?.toolPolicies || null;
|
|
2466
|
+
return policies && typeof policies === "object"
|
|
2467
|
+
? policies[tool] || null
|
|
2468
|
+
: null;
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
function snapshotPlanToolAuthority(planManager, tool, args) {
|
|
2472
|
+
const active = planManager?.isActive?.() === true;
|
|
2473
|
+
return {
|
|
2474
|
+
active,
|
|
2475
|
+
executionLockActive: planManager?.executionLock != null,
|
|
2476
|
+
toolAllowed:
|
|
2477
|
+
!active || (tool === "git" && isReadOnlyGitCommand(args?.command))
|
|
2478
|
+
? true
|
|
2479
|
+
: planManager?.isToolAllowed?.(tool) === true,
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
function snapshotEffectiveToolSetAuthority(effectiveAllowedToolNames, tool) {
|
|
2484
|
+
const enforced = Array.isArray(effectiveAllowedToolNames);
|
|
2485
|
+
return {
|
|
2486
|
+
enforced,
|
|
2487
|
+
allowed: !enforced || effectiveAllowedToolNames.includes(tool),
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
|
|
2491
|
+
function evaluateSelectedToolAdmissionAuthority({
|
|
2492
|
+
admission,
|
|
2493
|
+
tool,
|
|
2494
|
+
runtimeDescriptor,
|
|
2495
|
+
localToolDescriptor,
|
|
2496
|
+
hostManagedToolPolicy,
|
|
2497
|
+
}) {
|
|
2498
|
+
if (admission?.enforce !== true) {
|
|
2499
|
+
return {
|
|
2500
|
+
override: {},
|
|
2501
|
+
decision: null,
|
|
2502
|
+
projection: { enforce: false },
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
const override =
|
|
2506
|
+
admission.tools && typeof admission.tools === "object"
|
|
2507
|
+
? admission.tools[tool] || {}
|
|
2508
|
+
: {};
|
|
2509
|
+
const tier =
|
|
2510
|
+
override.tier ||
|
|
2511
|
+
runtimeDescriptor?.tier ||
|
|
2512
|
+
getCodingAgentToolPolicy(tool)?.tier ||
|
|
2513
|
+
(localToolDescriptor ? "extension" : "mvp");
|
|
2514
|
+
const inputs = {
|
|
2515
|
+
capabilityGranted:
|
|
2516
|
+
override.capabilityGranted ?? admission.capabilityGranted,
|
|
2517
|
+
policyAllowed:
|
|
2518
|
+
override.policyAllowed ??
|
|
2519
|
+
(hostManagedToolPolicy
|
|
2520
|
+
? (hostManagedToolPolicy?.tools ||
|
|
2521
|
+
hostManagedToolPolicy?.toolPolicies ||
|
|
2522
|
+
{})[tool]?.allowed !== false
|
|
2523
|
+
: admission.policyAllowed),
|
|
2524
|
+
permissionGranted:
|
|
2525
|
+
override.permissionGranted ?? admission.permissionGranted,
|
|
2526
|
+
budgetOk: override.budgetOk ?? admission.budgetOk,
|
|
2527
|
+
uiSupported: override.uiSupported ?? admission.uiSupported,
|
|
2528
|
+
};
|
|
2529
|
+
const decision = admitTool({ tool, tier, ...inputs });
|
|
2530
|
+
return {
|
|
2531
|
+
override,
|
|
2532
|
+
decision,
|
|
2533
|
+
projection: {
|
|
2534
|
+
enforce: true,
|
|
2535
|
+
tier: decision.tier,
|
|
2536
|
+
capabilityGranted: inputs.capabilityGranted === true,
|
|
2537
|
+
policyAllowed: inputs.policyAllowed === true,
|
|
2538
|
+
permissionGranted: inputs.permissionGranted === true,
|
|
2539
|
+
budgetOk: inputs.budgetOk === true,
|
|
2540
|
+
uiSupported: inputs.uiSupported === true,
|
|
2541
|
+
admitted: decision.admitted === true,
|
|
2542
|
+
unmet: [...decision.unmet],
|
|
2543
|
+
},
|
|
2544
|
+
};
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
function snapshotApprovalPolicyAuthority(approvalGate, sessionId) {
|
|
2548
|
+
if (!approvalGate) return null;
|
|
2549
|
+
const snapshot = approvalGate.getAuthorizationPolicySnapshot?.(sessionId);
|
|
2550
|
+
if (snapshot && typeof snapshot.then === "function") {
|
|
2551
|
+
throw new TypeError("approval policy snapshot must be synchronous");
|
|
2552
|
+
}
|
|
2553
|
+
if (snapshot) return snapshot;
|
|
2554
|
+
return {
|
|
2555
|
+
schema: "chainlesschain.approval-policy-authority/v1",
|
|
2556
|
+
kind: "legacy-gate",
|
|
2557
|
+
sessionId: sessionId ? String(sessionId) : null,
|
|
2558
|
+
policy: approvalGate.getSessionPolicy?.(sessionId) || null,
|
|
2559
|
+
hasAuthorizationConsumer:
|
|
2560
|
+
approvalGate.hasAuthorizationConsumer?.() === true,
|
|
2561
|
+
};
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2226
2564
|
/**
|
|
2227
2565
|
* Execute a single tool call with plan-mode filtering and hook pipeline.
|
|
2228
2566
|
*
|
|
@@ -2235,9 +2573,99 @@ function attachDiffReviewAudit(result, audit) {
|
|
|
2235
2573
|
* @returns {Promise<object>} tool result
|
|
2236
2574
|
*/
|
|
2237
2575
|
export async function executeTool(name, args, context = {}) {
|
|
2576
|
+
const liveExecutionContext = context;
|
|
2577
|
+
// run_shell can cross several asynchronous permission/hook boundaries before
|
|
2578
|
+
// reaching executeToolInner. Capture its declared tuple and working directory
|
|
2579
|
+
// before any of them so an in-process caller cannot swap the approved command
|
|
2580
|
+
// or reinterpret a relative cwd after process.chdir().
|
|
2581
|
+
let shellAuthorityCwd = null;
|
|
2582
|
+
let shellAuthorityWorkspaceCwd = null;
|
|
2583
|
+
let runCodeAuthorityCwd = null;
|
|
2584
|
+
if (name === "run_shell") {
|
|
2585
|
+
try {
|
|
2586
|
+
const entryProcessCwd = process.cwd();
|
|
2587
|
+
const declared = snapshotShellExecutionArgs(args);
|
|
2588
|
+
const contextCwd =
|
|
2589
|
+
context.cwd === undefined || context.cwd === null
|
|
2590
|
+
? entryProcessCwd
|
|
2591
|
+
: context.cwd;
|
|
2592
|
+
if (typeof contextCwd !== "string" || !contextCwd) {
|
|
2593
|
+
throw new TypeError("run_shell context cwd must be a non-empty string");
|
|
2594
|
+
}
|
|
2595
|
+
if (
|
|
2596
|
+
declared.cwd !== undefined &&
|
|
2597
|
+
(typeof declared.cwd !== "string" || !declared.cwd)
|
|
2598
|
+
) {
|
|
2599
|
+
throw new TypeError("run_shell cwd must be a non-empty string");
|
|
2600
|
+
}
|
|
2601
|
+
shellAuthorityWorkspaceCwd = fs.realpathSync.native(
|
|
2602
|
+
path.resolve(entryProcessCwd, contextCwd),
|
|
2603
|
+
);
|
|
2604
|
+
if (!fs.statSync(shellAuthorityWorkspaceCwd).isDirectory()) {
|
|
2605
|
+
throw new Error("run_shell context cwd is not a directory");
|
|
2606
|
+
}
|
|
2607
|
+
const requestedCwd = path.resolve(
|
|
2608
|
+
shellAuthorityWorkspaceCwd,
|
|
2609
|
+
declared.cwd || ".",
|
|
2610
|
+
);
|
|
2611
|
+
shellAuthorityCwd = fs.realpathSync.native(requestedCwd);
|
|
2612
|
+
if (!fs.statSync(shellAuthorityCwd).isDirectory()) {
|
|
2613
|
+
throw new Error("run_shell cwd is not a directory");
|
|
2614
|
+
}
|
|
2615
|
+
args = snapshotShellExecutionArgs({
|
|
2616
|
+
...declared,
|
|
2617
|
+
cwd: shellAuthorityCwd,
|
|
2618
|
+
});
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
return {
|
|
2621
|
+
error: `[Shell Dispatch] ${error?.message || "invalid execution arguments"}`,
|
|
2622
|
+
policy: {
|
|
2623
|
+
decision: "blocked",
|
|
2624
|
+
via: "shell-execution-snapshot",
|
|
2625
|
+
code: "CC_SHELL_EXECUTION_SNAPSHOT_INVALID",
|
|
2626
|
+
},
|
|
2627
|
+
};
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
if (name === "run_code") {
|
|
2631
|
+
try {
|
|
2632
|
+
const entryProcessCwd = process.cwd();
|
|
2633
|
+
args = snapshotRunCodeExecutionArgs(args);
|
|
2634
|
+
const contextCwd =
|
|
2635
|
+
context.cwd === undefined || context.cwd === null
|
|
2636
|
+
? entryProcessCwd
|
|
2637
|
+
: context.cwd;
|
|
2638
|
+
if (typeof contextCwd !== "string" || !contextCwd) {
|
|
2639
|
+
throw new TypeError("run_code context cwd must be a non-empty string");
|
|
2640
|
+
}
|
|
2641
|
+
runCodeAuthorityCwd = fs.realpathSync.native(
|
|
2642
|
+
path.resolve(entryProcessCwd, contextCwd),
|
|
2643
|
+
);
|
|
2644
|
+
if (!fs.statSync(runCodeAuthorityCwd).isDirectory()) {
|
|
2645
|
+
throw new Error("run_code cwd is not a directory");
|
|
2646
|
+
}
|
|
2647
|
+
} catch (error) {
|
|
2648
|
+
return {
|
|
2649
|
+
error: `[Run Code Dispatch] ${error?.message || "invalid execution arguments"}`,
|
|
2650
|
+
policy: {
|
|
2651
|
+
decision: "blocked",
|
|
2652
|
+
via: "run-code-execution-snapshot",
|
|
2653
|
+
code: "CC_RUN_CODE_EXECUTION_SNAPSHOT_INVALID",
|
|
2654
|
+
},
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2238
2658
|
const hookDb = context.hookDb || null;
|
|
2239
2659
|
const skillLoader = context.skillLoader || _defaultSkillLoader;
|
|
2240
|
-
|
|
2660
|
+
// Keep the authority-bearing workspace root distinct from the command's
|
|
2661
|
+
// requested cwd. A command may run in a nested directory, but Plugin policy
|
|
2662
|
+
// discovery and strong-sandbox issuance must remain anchored to the host
|
|
2663
|
+
// workspace that admitted the tool call.
|
|
2664
|
+
const cwd =
|
|
2665
|
+
shellAuthorityWorkspaceCwd ||
|
|
2666
|
+
runCodeAuthorityCwd ||
|
|
2667
|
+
context.cwd ||
|
|
2668
|
+
process.cwd();
|
|
2241
2669
|
const planManager = context.planManager || getPlanModeManager();
|
|
2242
2670
|
// The provider receives a filtered schema, but an untrusted/buggy model can
|
|
2243
2671
|
// still emit an arbitrary tool_call. Reuse the same pure preflight that the
|
|
@@ -2247,6 +2675,27 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2247
2675
|
cwd,
|
|
2248
2676
|
});
|
|
2249
2677
|
if (authorityDenial) return authorityDenial;
|
|
2678
|
+
let admittedEffectiveToolSetDigest = null;
|
|
2679
|
+
if (name === "run_shell") {
|
|
2680
|
+
try {
|
|
2681
|
+
admittedEffectiveToolSetDigest = digestPolicyAuthority(
|
|
2682
|
+
snapshotEffectiveToolSetAuthority(
|
|
2683
|
+
context.effectiveAllowedToolNames,
|
|
2684
|
+
name,
|
|
2685
|
+
),
|
|
2686
|
+
);
|
|
2687
|
+
} catch (error) {
|
|
2688
|
+
return {
|
|
2689
|
+
error:
|
|
2690
|
+
"[Shell Dispatch] The effective tool capability ceiling could not be bound. No command side effect was started.",
|
|
2691
|
+
policy: {
|
|
2692
|
+
decision: "blocked",
|
|
2693
|
+
via: "shell-policy-authority",
|
|
2694
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
2695
|
+
},
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2250
2699
|
const localToolDescriptor =
|
|
2251
2700
|
context.externalToolDescriptors &&
|
|
2252
2701
|
typeof context.externalToolDescriptors === "object"
|
|
@@ -2259,41 +2708,41 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2259
2708
|
: null;
|
|
2260
2709
|
const runtimeDescriptor =
|
|
2261
2710
|
getRuntimeToolDescriptor(name) || localToolDescriptor;
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
: {};
|
|
2268
|
-
const tier =
|
|
2269
|
-
override.tier ||
|
|
2270
|
-
runtimeDescriptor?.tier ||
|
|
2271
|
-
getCodingAgentToolPolicy(name)?.tier ||
|
|
2272
|
-
(localToolDescriptor ? "extension" : "mvp");
|
|
2273
|
-
const decision = admitTool({
|
|
2711
|
+
let admittedToolAdmissionDigest = null;
|
|
2712
|
+
let toolAdmissionEvaluation;
|
|
2713
|
+
try {
|
|
2714
|
+
toolAdmissionEvaluation = evaluateSelectedToolAdmissionAuthority({
|
|
2715
|
+
admission: context.toolAdmission,
|
|
2274
2716
|
tool: name,
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
policyAllowed:
|
|
2279
|
-
override.policyAllowed ??
|
|
2280
|
-
(context.hostManagedToolPolicy
|
|
2281
|
-
? (context.hostManagedToolPolicy?.tools ||
|
|
2282
|
-
context.hostManagedToolPolicy?.toolPolicies ||
|
|
2283
|
-
{})[name]?.allowed !== false
|
|
2284
|
-
: admission.policyAllowed),
|
|
2285
|
-
permissionGranted:
|
|
2286
|
-
override.permissionGranted ?? admission.permissionGranted,
|
|
2287
|
-
budgetOk: override.budgetOk ?? admission.budgetOk,
|
|
2288
|
-
uiSupported: override.uiSupported ?? admission.uiSupported,
|
|
2717
|
+
runtimeDescriptor,
|
|
2718
|
+
localToolDescriptor,
|
|
2719
|
+
hostManagedToolPolicy: context.hostManagedToolPolicy,
|
|
2289
2720
|
});
|
|
2721
|
+
if (name === "run_shell") {
|
|
2722
|
+
admittedToolAdmissionDigest = digestPolicyAuthority(
|
|
2723
|
+
toolAdmissionEvaluation.projection,
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
} catch (error) {
|
|
2727
|
+
return {
|
|
2728
|
+
error:
|
|
2729
|
+
"[Tool Admission] The selected tool authority could not be bound. No tool side effect was started.",
|
|
2730
|
+
policy: {
|
|
2731
|
+
decision: "blocked",
|
|
2732
|
+
via: "tool-admission",
|
|
2733
|
+
code: error?.code || "CC_TOOL_ADMISSION_AUTHORITY_UNAVAILABLE",
|
|
2734
|
+
},
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
if (toolAdmissionEvaluation.decision) {
|
|
2738
|
+
const { override, decision } = toolAdmissionEvaluation;
|
|
2290
2739
|
const attribution = buildToolAttribution({
|
|
2291
2740
|
tool: name,
|
|
2292
2741
|
source:
|
|
2293
2742
|
override.source ||
|
|
2294
2743
|
runtimeDescriptor?.source ||
|
|
2295
2744
|
localToolDescriptor?.source ||
|
|
2296
|
-
|
|
2745
|
+
context.toolAdmission?.source ||
|
|
2297
2746
|
null,
|
|
2298
2747
|
version: override.version || runtimeDescriptor?.version || null,
|
|
2299
2748
|
scope: override.scope || localToolDescriptor?.scope || null,
|
|
@@ -2468,6 +2917,30 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2468
2917
|
: null,
|
|
2469
2918
|
scopedRule: matchedScopedRule || null,
|
|
2470
2919
|
};
|
|
2920
|
+
let admittedPermissionPolicyDigest = null;
|
|
2921
|
+
if (name === "run_shell") {
|
|
2922
|
+
try {
|
|
2923
|
+
admittedPermissionPolicyDigest = digestPolicyAuthority(
|
|
2924
|
+
projectPermissionAuthority({
|
|
2925
|
+
authority: permissionAuthority,
|
|
2926
|
+
rules: validatePermissionRuleset(effectivePermissionRules),
|
|
2927
|
+
tool: name,
|
|
2928
|
+
args,
|
|
2929
|
+
cwd,
|
|
2930
|
+
}),
|
|
2931
|
+
);
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
return {
|
|
2934
|
+
error:
|
|
2935
|
+
"[Shell Dispatch] The evaluated permission authority could not be bound. No command side effect was started.",
|
|
2936
|
+
policy: {
|
|
2937
|
+
decision: "blocked",
|
|
2938
|
+
via: "shell-policy-authority",
|
|
2939
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
2940
|
+
},
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2471
2944
|
|
|
2472
2945
|
// 1. settings deny
|
|
2473
2946
|
if (settingsVerdict.decision === "deny") {
|
|
@@ -2497,6 +2970,22 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2497
2970
|
toolPolicies && typeof toolPolicies === "object"
|
|
2498
2971
|
? toolPolicies[name]
|
|
2499
2972
|
: null;
|
|
2973
|
+
let admittedHostPolicyDigest = null;
|
|
2974
|
+
if (name === "run_shell") {
|
|
2975
|
+
try {
|
|
2976
|
+
admittedHostPolicyDigest = digestPolicyAuthority(hostToolPolicy);
|
|
2977
|
+
} catch (error) {
|
|
2978
|
+
return {
|
|
2979
|
+
error:
|
|
2980
|
+
"[Shell Dispatch] The evaluated host policy could not be bound. No command side effect was started.",
|
|
2981
|
+
policy: {
|
|
2982
|
+
decision: "blocked",
|
|
2983
|
+
via: "shell-policy-authority",
|
|
2984
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
2985
|
+
},
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2500
2989
|
const hostPolicyAllowsReadOnlyGit =
|
|
2501
2990
|
name === "git" &&
|
|
2502
2991
|
hostToolPolicy?.planModeBehavior === "readonly-conditional" &&
|
|
@@ -2530,6 +3019,24 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2530
3019
|
planManager.isActive() && !executionLockActive;
|
|
2531
3020
|
const externalToolBlockedDuringPlanning =
|
|
2532
3021
|
planReadOnlyFenceActive && !STATIC_AGENT_TOOL_NAMES.has(name);
|
|
3022
|
+
let admittedPlanPolicyDigest = null;
|
|
3023
|
+
if (name === "run_shell") {
|
|
3024
|
+
try {
|
|
3025
|
+
admittedPlanPolicyDigest = digestPolicyAuthority(
|
|
3026
|
+
snapshotPlanToolAuthority(planManager, name, args),
|
|
3027
|
+
);
|
|
3028
|
+
} catch (error) {
|
|
3029
|
+
return {
|
|
3030
|
+
error:
|
|
3031
|
+
"[Shell Dispatch] The evaluated plan authority could not be bound. No command side effect was started.",
|
|
3032
|
+
policy: {
|
|
3033
|
+
decision: "blocked",
|
|
3034
|
+
via: "shell-policy-authority",
|
|
3035
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
3036
|
+
},
|
|
3037
|
+
};
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
2533
3040
|
context = { ...context, planReadOnlyFenceActive };
|
|
2534
3041
|
if (
|
|
2535
3042
|
planManager.isActive() &&
|
|
@@ -2828,6 +3335,160 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2828
3335
|
if (instructionPreflight) return instructionPreflight;
|
|
2829
3336
|
}
|
|
2830
3337
|
|
|
3338
|
+
let shellDispatchPolicyAuthority = null;
|
|
3339
|
+
if (name === "run_shell") {
|
|
3340
|
+
try {
|
|
3341
|
+
const permissionRulesProvider = context.permissionRulesProvider || null;
|
|
3342
|
+
const approvalGate = context.approvalGate || null;
|
|
3343
|
+
const initialProjection = {
|
|
3344
|
+
permission: projectPermissionAuthority({
|
|
3345
|
+
authority: permissionAuthority,
|
|
3346
|
+
rules: validatePermissionRuleset(effectivePermissionRules),
|
|
3347
|
+
tool: name,
|
|
3348
|
+
args,
|
|
3349
|
+
cwd,
|
|
3350
|
+
}),
|
|
3351
|
+
host: selectedHostToolPolicy(
|
|
3352
|
+
context.hostManagedToolPolicy || null,
|
|
3353
|
+
name,
|
|
3354
|
+
),
|
|
3355
|
+
plan: snapshotPlanToolAuthority(planManager, name, args),
|
|
3356
|
+
effectiveToolSet: snapshotEffectiveToolSetAuthority(
|
|
3357
|
+
context.effectiveAllowedToolNames,
|
|
3358
|
+
name,
|
|
3359
|
+
),
|
|
3360
|
+
toolAdmission: evaluateSelectedToolAdmissionAuthority({
|
|
3361
|
+
admission: context.toolAdmission,
|
|
3362
|
+
tool: name,
|
|
3363
|
+
runtimeDescriptor,
|
|
3364
|
+
localToolDescriptor,
|
|
3365
|
+
hostManagedToolPolicy: context.hostManagedToolPolicy,
|
|
3366
|
+
}).projection,
|
|
3367
|
+
approval: snapshotApprovalPolicyAuthority(
|
|
3368
|
+
approvalGate,
|
|
3369
|
+
context.sessionId || null,
|
|
3370
|
+
),
|
|
3371
|
+
unattended: context.unattendedActionPolicy || null,
|
|
3372
|
+
shellPolicyOverrides: context.shellPolicyOverrides || null,
|
|
3373
|
+
classifyAllShell: context.classifyAllShell === true,
|
|
3374
|
+
};
|
|
3375
|
+
if (
|
|
3376
|
+
digestPolicyAuthority(initialProjection.permission) !==
|
|
3377
|
+
admittedPermissionPolicyDigest ||
|
|
3378
|
+
digestPolicyAuthority(initialProjection.host) !==
|
|
3379
|
+
admittedHostPolicyDigest ||
|
|
3380
|
+
digestPolicyAuthority(initialProjection.plan) !==
|
|
3381
|
+
admittedPlanPolicyDigest ||
|
|
3382
|
+
digestPolicyAuthority(initialProjection.effectiveToolSet) !==
|
|
3383
|
+
admittedEffectiveToolSetDigest ||
|
|
3384
|
+
digestPolicyAuthority(initialProjection.toolAdmission) !==
|
|
3385
|
+
admittedToolAdmissionDigest
|
|
3386
|
+
) {
|
|
3387
|
+
const error = new Error(
|
|
3388
|
+
"Shell policy authority changed while an earlier confirmation was pending",
|
|
3389
|
+
);
|
|
3390
|
+
error.code = "CC_SHELL_POLICY_AUTHORITY_CHANGED";
|
|
3391
|
+
throw error;
|
|
3392
|
+
}
|
|
3393
|
+
const initialDigest = digestPolicyAuthority(initialProjection);
|
|
3394
|
+
shellDispatchPolicyAuthority = Object.freeze({
|
|
3395
|
+
policyVersion: `cc-shell-policy-authority/v1:${initialDigest}`,
|
|
3396
|
+
async revalidate() {
|
|
3397
|
+
try {
|
|
3398
|
+
if (
|
|
3399
|
+
(liveExecutionContext.permissionRulesProvider || null) !==
|
|
3400
|
+
permissionRulesProvider ||
|
|
3401
|
+
(liveExecutionContext.approvalGate || null) !== approvalGate ||
|
|
3402
|
+
(liveExecutionContext.planManager || getPlanModeManager()) !==
|
|
3403
|
+
planManager
|
|
3404
|
+
) {
|
|
3405
|
+
const error = new Error("policy authority provider changed");
|
|
3406
|
+
error.code = "CC_SHELL_POLICY_AUTHORITY_CHANGED";
|
|
3407
|
+
throw error;
|
|
3408
|
+
}
|
|
3409
|
+
let currentPermissionAuthority = null;
|
|
3410
|
+
let currentPermissionRules =
|
|
3411
|
+
liveExecutionContext.permissionRules || null;
|
|
3412
|
+
if (permissionRulesProvider) {
|
|
3413
|
+
currentPermissionAuthority = await permissionRulesProvider({
|
|
3414
|
+
cwd,
|
|
3415
|
+
tool: name,
|
|
3416
|
+
args,
|
|
3417
|
+
});
|
|
3418
|
+
currentPermissionRules =
|
|
3419
|
+
currentPermissionAuthority?.rules ||
|
|
3420
|
+
currentPermissionAuthority ||
|
|
3421
|
+
null;
|
|
3422
|
+
}
|
|
3423
|
+
const currentProjection = {
|
|
3424
|
+
permission: projectPermissionAuthority({
|
|
3425
|
+
authority: currentPermissionAuthority,
|
|
3426
|
+
rules: validatePermissionRuleset(currentPermissionRules),
|
|
3427
|
+
tool: name,
|
|
3428
|
+
args,
|
|
3429
|
+
cwd,
|
|
3430
|
+
}),
|
|
3431
|
+
host: selectedHostToolPolicy(
|
|
3432
|
+
liveExecutionContext.hostManagedToolPolicy || null,
|
|
3433
|
+
name,
|
|
3434
|
+
),
|
|
3435
|
+
plan: snapshotPlanToolAuthority(planManager, name, args),
|
|
3436
|
+
effectiveToolSet: snapshotEffectiveToolSetAuthority(
|
|
3437
|
+
liveExecutionContext.effectiveAllowedToolNames,
|
|
3438
|
+
name,
|
|
3439
|
+
),
|
|
3440
|
+
toolAdmission: evaluateSelectedToolAdmissionAuthority({
|
|
3441
|
+
admission: liveExecutionContext.toolAdmission,
|
|
3442
|
+
tool: name,
|
|
3443
|
+
runtimeDescriptor,
|
|
3444
|
+
localToolDescriptor,
|
|
3445
|
+
hostManagedToolPolicy:
|
|
3446
|
+
liveExecutionContext.hostManagedToolPolicy,
|
|
3447
|
+
}).projection,
|
|
3448
|
+
approval: snapshotApprovalPolicyAuthority(
|
|
3449
|
+
approvalGate,
|
|
3450
|
+
context.sessionId || null,
|
|
3451
|
+
),
|
|
3452
|
+
unattended: liveExecutionContext.unattendedActionPolicy || null,
|
|
3453
|
+
shellPolicyOverrides:
|
|
3454
|
+
liveExecutionContext.shellPolicyOverrides || null,
|
|
3455
|
+
classifyAllShell: liveExecutionContext.classifyAllShell === true,
|
|
3456
|
+
};
|
|
3457
|
+
if (digestPolicyAuthority(currentProjection) !== initialDigest) {
|
|
3458
|
+
const error = new Error(
|
|
3459
|
+
"Shell permission authority changed after initial admission",
|
|
3460
|
+
);
|
|
3461
|
+
error.code = "CC_SHELL_POLICY_AUTHORITY_CHANGED";
|
|
3462
|
+
throw error;
|
|
3463
|
+
}
|
|
3464
|
+
return true;
|
|
3465
|
+
} catch (error) {
|
|
3466
|
+
if (error?.code === "CC_SHELL_POLICY_AUTHORITY_CHANGED") {
|
|
3467
|
+
throw error;
|
|
3468
|
+
}
|
|
3469
|
+
const wrapped = new Error(
|
|
3470
|
+
"Current shell permission authority could not be revalidated",
|
|
3471
|
+
{ cause: error },
|
|
3472
|
+
);
|
|
3473
|
+
wrapped.code =
|
|
3474
|
+
error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE";
|
|
3475
|
+
throw wrapped;
|
|
3476
|
+
}
|
|
3477
|
+
},
|
|
3478
|
+
});
|
|
3479
|
+
} catch (error) {
|
|
3480
|
+
return {
|
|
3481
|
+
error:
|
|
3482
|
+
"[Shell Dispatch] The current permission authority could not be bound. No command side effect was started.",
|
|
3483
|
+
policy: {
|
|
3484
|
+
decision: "blocked",
|
|
3485
|
+
via: "shell-policy-authority",
|
|
3486
|
+
code: error?.code || "CC_SHELL_POLICY_AUTHORITY_UNAVAILABLE",
|
|
3487
|
+
},
|
|
3488
|
+
};
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
|
|
2831
3492
|
// PreToolUse hooks. DB hooks (cc hook add) stay observe-only — a failure
|
|
2832
3493
|
// never blocks. settings.json hooks (context.settingsHooks) are decision-
|
|
2833
3494
|
// capable: a `block` (exit 2 / {decision:block}) stops the tool here, an
|
|
@@ -2951,12 +3612,14 @@ export async function executeTool(name, args, context = {}) {
|
|
|
2951
3612
|
subtreeInstructionScope:
|
|
2952
3613
|
context.subtreeInstructionScope || context.sessionId || "__legacy__",
|
|
2953
3614
|
shellPolicyOverrides: context.shellPolicyOverrides || null,
|
|
3615
|
+
classifyAllShell: context.classifyAllShell === true,
|
|
2954
3616
|
approvalGate: context.approvalGate || null,
|
|
2955
3617
|
shellConfirm: context.shellConfirm || null,
|
|
2956
3618
|
additionalDirectories: context.additionalDirectories || null,
|
|
2957
3619
|
sandbox: context.sandbox || null,
|
|
2958
3620
|
ruleAllowed,
|
|
2959
3621
|
settingsVerdict,
|
|
3622
|
+
shellDispatchPolicyAuthority,
|
|
2960
3623
|
subAgentDepth: context.subAgentDepth || 0,
|
|
2961
3624
|
subAgentBudget: context.subAgentBudget || null,
|
|
2962
3625
|
sessionBudget: context.sessionBudget || null,
|
|
@@ -4331,6 +4994,7 @@ async function executeToolInner(
|
|
|
4331
4994
|
sandbox,
|
|
4332
4995
|
ruleAllowed = false,
|
|
4333
4996
|
settingsVerdict = null,
|
|
4997
|
+
shellDispatchPolicyAuthority = null,
|
|
4334
4998
|
subAgentDepth = 0,
|
|
4335
4999
|
subAgentBudget = null,
|
|
4336
5000
|
sessionBudget = null,
|
|
@@ -4812,6 +5476,9 @@ async function executeToolInner(
|
|
|
4812
5476
|
}
|
|
4813
5477
|
|
|
4814
5478
|
case "run_shell": {
|
|
5479
|
+
// Bound the declared run_shell data once. Approval and dispatch never
|
|
5480
|
+
// observe later mutation of the caller-owned tool arguments.
|
|
5481
|
+
args = Object.isFrozen(args) ? args : snapshotShellExecutionArgs(args);
|
|
4815
5482
|
if (unattendedActionPolicy?.unattended === true) {
|
|
4816
5483
|
const unattendedVerdict = evaluateUnattendedShellAction(args.command, {
|
|
4817
5484
|
...unattendedActionPolicy,
|
|
@@ -4829,12 +5496,12 @@ async function executeToolInner(
|
|
|
4829
5496
|
}
|
|
4830
5497
|
}
|
|
4831
5498
|
|
|
4832
|
-
const shellPolicyOpts = {
|
|
5499
|
+
const shellPolicyOpts = Object.freeze({
|
|
4833
5500
|
...(shellPolicyOverrides
|
|
4834
|
-
? { overrideRuleIds: shellPolicyOverrides }
|
|
5501
|
+
? { overrideRuleIds: Object.freeze([...shellPolicyOverrides]) }
|
|
4835
5502
|
: {}),
|
|
4836
5503
|
...(classifyAllShell ? { classifyAllShell: true } : {}),
|
|
4837
|
-
};
|
|
5504
|
+
});
|
|
4838
5505
|
// Layer-by-layer explanation chain for a blocked command: which layers
|
|
4839
5506
|
// were consulted and what each said (settings rules → shell policy →
|
|
4840
5507
|
// approval gate). Attached to denial results so `/permissions denials`
|
|
@@ -4870,60 +5537,80 @@ async function executeToolInner(
|
|
|
4870
5537
|
return chain;
|
|
4871
5538
|
};
|
|
4872
5539
|
const override = getRuntimeToolDescriptorByCommand(args.command);
|
|
4873
|
-
let shellPolicy
|
|
5540
|
+
let shellPolicy = evaluateShellCommandPolicy(
|
|
5541
|
+
args.command,
|
|
5542
|
+
shellPolicyOpts,
|
|
5543
|
+
);
|
|
4874
5544
|
let approvalOutcome = null;
|
|
5545
|
+
let approvalAuthorization = null;
|
|
5546
|
+
let approvalAuthorizationContext = null;
|
|
5547
|
+
let approvalGateResult = null;
|
|
4875
5548
|
// A settings `allow` rule (ruleAllowed) pre-authorizes: skip the
|
|
4876
5549
|
// ApprovalGate tier confirm, but still run the hard shell-policy denylist
|
|
4877
5550
|
// below so an allow rule can never re-enable a blocked unsafe command.
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
5551
|
+
const requestShellApproval = async (operationArgs) => {
|
|
5552
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
5553
|
+
if (approvalGate && !ruleAllowed) {
|
|
5554
|
+
const approvalPolicyVersion = `${shellDispatchPolicyAuthority?.policyVersion || "cc-shell-policy-authority/v1:unobserved"}:shell:${shellPolicy.decision}:${shellPolicy.ruleId || "none"}`;
|
|
5555
|
+
const gated = await evaluateShellCommandWithApproval({
|
|
5556
|
+
command: args.command,
|
|
5557
|
+
args: operationArgs,
|
|
5558
|
+
sessionId,
|
|
5559
|
+
approvalGate,
|
|
5560
|
+
shellPolicyOptions: shellPolicyOpts,
|
|
5561
|
+
workspace: args.cwd || cwd,
|
|
5562
|
+
policyEnv: shellParentEnvironment,
|
|
5563
|
+
policyVersion: approvalPolicyVersion,
|
|
5564
|
+
deferSideEffects: true,
|
|
5565
|
+
});
|
|
5566
|
+
approvalGateResult = gated;
|
|
5567
|
+
shellPolicy = gated.shellPolicy;
|
|
5568
|
+
approvalOutcome = {
|
|
5569
|
+
decision: gated.decision,
|
|
5570
|
+
via: gated.via,
|
|
5571
|
+
riskLevel: gated.riskLevel,
|
|
5572
|
+
policy: gated.policy,
|
|
5573
|
+
};
|
|
5574
|
+
approvalAuthorization = gated.authorization;
|
|
5575
|
+
approvalAuthorizationContext = gated.authorizationContext;
|
|
5576
|
+
if (!gated.allowed) {
|
|
5577
|
+
// Make a policy denial ACTIONABLE for the model (Claude-Code 2.1.193
|
|
5578
|
+
// "denial reasons to transcripts"): tell it this won't change on
|
|
5579
|
+
// retry and to involve the user, so it stops re-issuing the same
|
|
5580
|
+
// blocked command (which otherwise just burns turns + tokens).
|
|
5581
|
+
const tierLabel =
|
|
5582
|
+
typeof gated.policy === "string" ? `"${gated.policy}" ` : "";
|
|
5583
|
+
return attachDescriptor(
|
|
5584
|
+
{
|
|
5585
|
+
error:
|
|
5586
|
+
gated.via === "shell-policy"
|
|
5587
|
+
? `[Shell Policy] ${gated.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`
|
|
5588
|
+
: `[ApprovalGate] command denied by the ${tierLabel}approval policy (via ${gated.via}). Retrying the same command will not help — it needs user approval. Tell the user (they can run it themselves, approve it, or relax the policy) and continue with other work.`,
|
|
5589
|
+
shellCommandPolicy: shellPolicy,
|
|
5590
|
+
approval: approvalOutcome,
|
|
5591
|
+
permissionChain: buildPermissionChain(gated),
|
|
5592
|
+
},
|
|
5593
|
+
override || runtimeDescriptor,
|
|
5594
|
+
);
|
|
5595
|
+
}
|
|
5596
|
+
} else {
|
|
5597
|
+
shellPolicy = evaluateShellCommandPolicy(
|
|
5598
|
+
args.command,
|
|
5599
|
+
shellPolicyOpts,
|
|
4924
5600
|
);
|
|
5601
|
+
if (!shellPolicy.allowed) {
|
|
5602
|
+
return attachDescriptor(
|
|
5603
|
+
{
|
|
5604
|
+
error: `[Shell Policy] ${shellPolicy.reason} This command is blocked by policy and will not run — do not retry it; find another approach.`,
|
|
5605
|
+
shellCommandPolicy: shellPolicy,
|
|
5606
|
+
permissionChain: buildPermissionChain(null),
|
|
5607
|
+
},
|
|
5608
|
+
override || runtimeDescriptor,
|
|
5609
|
+
);
|
|
5610
|
+
}
|
|
4925
5611
|
}
|
|
4926
|
-
|
|
5612
|
+
return null;
|
|
5613
|
+
};
|
|
4927
5614
|
|
|
4928
5615
|
// P1 #8 Windows/PowerShell first-class: per-call args.shell or the
|
|
4929
5616
|
// configured settings `shell.windowsDefault` may route this command
|
|
@@ -4932,7 +5619,7 @@ async function executeToolInner(
|
|
|
4932
5619
|
// the historical default shell byte-identical (useDefaultShell=true).
|
|
4933
5620
|
const { resolveShellInvocation } =
|
|
4934
5621
|
await import("../lib/shell-selector.cjs");
|
|
4935
|
-
|
|
5622
|
+
let shellInv = resolveShellInvocation({
|
|
4936
5623
|
command: args.command,
|
|
4937
5624
|
requested: args.shell,
|
|
4938
5625
|
cwd: args.cwd || cwd,
|
|
@@ -4949,12 +5636,16 @@ async function executeToolInner(
|
|
|
4949
5636
|
// Resolver failures for a matching strict alias are terminal — falling
|
|
4950
5637
|
// back to a shell would bypass target identity and manifest requirements.
|
|
4951
5638
|
let pluginBinInvocation = null;
|
|
4952
|
-
let
|
|
5639
|
+
let pluginBinRuntime = null;
|
|
4953
5640
|
let pluginBinSandboxPolicy = null;
|
|
4954
5641
|
let pluginBinSandboxExecutionContract = null;
|
|
5642
|
+
const backgroundPlatform =
|
|
5643
|
+
typeof _backgroundProcessDeps.platform === "function"
|
|
5644
|
+
? _backgroundProcessDeps.platform()
|
|
5645
|
+
: process.platform;
|
|
4955
5646
|
try {
|
|
4956
5647
|
const pluginBin = await import("../lib/plugin-runtime/bin.js");
|
|
4957
|
-
|
|
5648
|
+
pluginBinRuntime = pluginBin;
|
|
4958
5649
|
pluginBinSandboxPolicy = collectWorkspacePluginBinSandboxPolicy(
|
|
4959
5650
|
pluginBin,
|
|
4960
5651
|
cwd,
|
|
@@ -4978,8 +5669,8 @@ async function executeToolInner(
|
|
|
4978
5669
|
{ cwd },
|
|
4979
5670
|
);
|
|
4980
5671
|
if (
|
|
4981
|
-
|
|
4982
|
-
(
|
|
5672
|
+
backgroundPlatform === "linux" ||
|
|
5673
|
+
(backgroundPlatform === "win32" &&
|
|
4983
5674
|
pluginBinInvocation.runtime === "node")
|
|
4984
5675
|
) {
|
|
4985
5676
|
try {
|
|
@@ -5024,7 +5715,7 @@ async function executeToolInner(
|
|
|
5024
5715
|
const processOrigin = pluginBinInvocation
|
|
5025
5716
|
? "plugin:bin"
|
|
5026
5717
|
: "tool:run_shell";
|
|
5027
|
-
|
|
5718
|
+
let processProvenance = {
|
|
5028
5719
|
...(pluginBinInvocation
|
|
5029
5720
|
? {
|
|
5030
5721
|
pluginId: pluginBinInvocation.pluginId,
|
|
@@ -5058,21 +5749,262 @@ async function executeToolInner(
|
|
|
5058
5749
|
}
|
|
5059
5750
|
: {};
|
|
5060
5751
|
|
|
5752
|
+
// Snapshot process-derived execution inputs before confirmation. The
|
|
5753
|
+
// descriptor carries only an opaque, secret-free environment reference;
|
|
5754
|
+
// the actual child environment remains in this invocation-local closure.
|
|
5755
|
+
const shellParentEnvironment = Object.freeze({ ...process.env });
|
|
5756
|
+
const agentIdentityEnvironment = Object.freeze({
|
|
5757
|
+
CLAUDECODE: "1",
|
|
5758
|
+
...(sessionId
|
|
5759
|
+
? {
|
|
5760
|
+
CC_SESSION_ID: String(sessionId),
|
|
5761
|
+
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5762
|
+
}
|
|
5763
|
+
: {}),
|
|
5764
|
+
});
|
|
5765
|
+
const credentialProxyResult = applyCredentialProxy(
|
|
5766
|
+
{
|
|
5767
|
+
...shellParentEnvironment,
|
|
5768
|
+
...agentIdentityEnvironment,
|
|
5769
|
+
},
|
|
5770
|
+
{ env: shellParentEnvironment },
|
|
5771
|
+
);
|
|
5772
|
+
const shellChildEnvironment = Object.freeze({
|
|
5773
|
+
...credentialProxyResult.env,
|
|
5774
|
+
});
|
|
5775
|
+
const sandboxChildEnvironment = agentIdentityEnvironment;
|
|
5776
|
+
const shellExecutionDescriptor = createShellExecutionDescriptor({
|
|
5777
|
+
args,
|
|
5778
|
+
workspace: args.cwd || cwd,
|
|
5779
|
+
shellInvocation: shellInv,
|
|
5780
|
+
pluginInvocation: pluginBinInvocation,
|
|
5781
|
+
pluginSandboxPolicy: pluginBinSandboxPolicy,
|
|
5782
|
+
pluginSandboxExecutionContract: pluginBinSandboxExecutionContract,
|
|
5783
|
+
sandbox,
|
|
5784
|
+
environmentRef: {
|
|
5785
|
+
id: `run-shell-env:${randomUUID()}`,
|
|
5786
|
+
inheritance: sandbox ? "sandbox-minimal" : "host-snapshot",
|
|
5787
|
+
credentialProxy: credentialProxyResult.enabled === true,
|
|
5788
|
+
maskedNames: Object.freeze([...credentialProxyResult.masked]),
|
|
5789
|
+
},
|
|
5790
|
+
});
|
|
5791
|
+
// From here onward the descriptor itself is the tool-argument snapshot.
|
|
5792
|
+
args = shellExecutionDescriptor;
|
|
5793
|
+
shellInv = shellExecutionDescriptor.execution.shell;
|
|
5794
|
+
const shellSandbox = shellExecutionDescriptor.execution.sandbox;
|
|
5795
|
+
const executionPlugin = shellExecutionDescriptor.execution.plugin;
|
|
5796
|
+
const approvedPluginProjection = executionPlugin
|
|
5797
|
+
? canonicalPolicyAuthorityData(executionPlugin)
|
|
5798
|
+
: null;
|
|
5799
|
+
const approvedWorkspacePluginPolicy = canonicalPolicyAuthorityData(
|
|
5800
|
+
pluginBinSandboxPolicy,
|
|
5801
|
+
);
|
|
5802
|
+
const approvedPluginInvocationAuthority = pluginBinInvocation
|
|
5803
|
+
? canonicalPolicyAuthorityData(
|
|
5804
|
+
projectPluginBinInvocationAuthority(pluginBinInvocation),
|
|
5805
|
+
)
|
|
5806
|
+
: null;
|
|
5807
|
+
const refreshPluginBinExecution = () => {
|
|
5808
|
+
if (!executionPlugin) return;
|
|
5809
|
+
if (!pluginBinRuntime) {
|
|
5810
|
+
const error = new Error(
|
|
5811
|
+
"Plugin bin runtime is unavailable during launch revalidation",
|
|
5812
|
+
);
|
|
5813
|
+
error.code = "ERR_PLUGIN_BIN_DISCOVERY_FAILED";
|
|
5814
|
+
error.pluginBinFailClosed = true;
|
|
5815
|
+
throw error;
|
|
5816
|
+
}
|
|
5817
|
+
const freshInvocation = pluginBinRuntime.resolvePluginBinInvocation(
|
|
5818
|
+
shellExecutionDescriptor.command,
|
|
5819
|
+
{
|
|
5820
|
+
cwd,
|
|
5821
|
+
commandCwd: shellExecutionDescriptor.cwd || cwd,
|
|
5822
|
+
failClosed: true,
|
|
5823
|
+
},
|
|
5824
|
+
);
|
|
5825
|
+
if (!freshInvocation) {
|
|
5826
|
+
const error = new Error(
|
|
5827
|
+
"Approved plugin bin alias is no longer available",
|
|
5828
|
+
);
|
|
5829
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5830
|
+
error.pluginBinFailClosed = true;
|
|
5831
|
+
throw error;
|
|
5832
|
+
}
|
|
5833
|
+
if (
|
|
5834
|
+
canonicalPolicyAuthorityData(
|
|
5835
|
+
projectPluginBinInvocationAuthority(freshInvocation),
|
|
5836
|
+
) !== approvedPluginInvocationAuthority
|
|
5837
|
+
) {
|
|
5838
|
+
const error = new Error(
|
|
5839
|
+
"Plugin bin manifest, trust, alias, or sandbox authority changed after approval",
|
|
5840
|
+
);
|
|
5841
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5842
|
+
error.pluginBinFailClosed = true;
|
|
5843
|
+
throw error;
|
|
5844
|
+
}
|
|
5845
|
+
let freshPolicy = collectWorkspacePluginBinSandboxPolicy(
|
|
5846
|
+
pluginBinRuntime,
|
|
5847
|
+
cwd,
|
|
5848
|
+
shellExecutionDescriptor.cwd || cwd,
|
|
5849
|
+
);
|
|
5850
|
+
if (freshInvocation.sandboxPolicy) {
|
|
5851
|
+
freshPolicy = pluginBinRuntime.pinPluginBinSandboxPolicy(
|
|
5852
|
+
{
|
|
5853
|
+
requiredBoundaries: [
|
|
5854
|
+
...(freshPolicy?.requiredBoundaries || []),
|
|
5855
|
+
...(freshInvocation.sandboxPolicy.requiredBoundaries || []),
|
|
5856
|
+
],
|
|
5857
|
+
},
|
|
5858
|
+
{ cwd },
|
|
5859
|
+
);
|
|
5860
|
+
}
|
|
5861
|
+
let freshContract = null;
|
|
5862
|
+
if (
|
|
5863
|
+
freshInvocation.sandboxPolicy &&
|
|
5864
|
+
(backgroundPlatform === "linux" ||
|
|
5865
|
+
(backgroundPlatform === "win32" &&
|
|
5866
|
+
freshInvocation.runtime === "node"))
|
|
5867
|
+
) {
|
|
5868
|
+
freshContract = pluginBinRuntime.createPluginSandboxExecutionContract(
|
|
5869
|
+
freshInvocation,
|
|
5870
|
+
{ sync: shellExecutionDescriptor.run_in_background !== true },
|
|
5871
|
+
);
|
|
5872
|
+
}
|
|
5873
|
+
const freshProjection = createShellExecutionDescriptor({
|
|
5874
|
+
args: shellExecutionDescriptor,
|
|
5875
|
+
workspace: shellExecutionDescriptor.execution.workspace,
|
|
5876
|
+
shellInvocation: shellExecutionDescriptor.execution.shell,
|
|
5877
|
+
pluginInvocation: freshInvocation,
|
|
5878
|
+
pluginSandboxPolicy: freshPolicy,
|
|
5879
|
+
pluginSandboxExecutionContract: freshContract,
|
|
5880
|
+
sandbox: shellExecutionDescriptor.execution.sandbox,
|
|
5881
|
+
environmentRef: shellExecutionDescriptor.execution.environment,
|
|
5882
|
+
}).execution.plugin;
|
|
5883
|
+
if (
|
|
5884
|
+
canonicalPolicyAuthorityData(freshProjection) !==
|
|
5885
|
+
approvedPluginProjection
|
|
5886
|
+
) {
|
|
5887
|
+
const error = new Error(
|
|
5888
|
+
"Plugin bin manifest, trust, alias, or sandbox authority changed after approval",
|
|
5889
|
+
);
|
|
5890
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5891
|
+
error.pluginBinFailClosed = true;
|
|
5892
|
+
throw error;
|
|
5893
|
+
}
|
|
5894
|
+
pluginBinInvocation = freshInvocation;
|
|
5895
|
+
pluginBinSandboxPolicy = freshPolicy;
|
|
5896
|
+
pluginBinSandboxExecutionContract = freshContract;
|
|
5897
|
+
processProvenance = {
|
|
5898
|
+
pluginId: freshInvocation.pluginId,
|
|
5899
|
+
pluginVersion: freshInvocation.pluginVersion,
|
|
5900
|
+
pluginSource: freshInvocation.pluginSource,
|
|
5901
|
+
pluginExecutableIdentity: freshInvocation.executableIdentity,
|
|
5902
|
+
...(freshPolicy ? { sandboxPolicy: freshPolicy } : {}),
|
|
5903
|
+
...(freshContract ? { sandboxExecutionContract: freshContract } : {}),
|
|
5904
|
+
};
|
|
5905
|
+
};
|
|
5906
|
+
const refreshPluginExecutionAuthority = () => {
|
|
5907
|
+
if (executionPlugin) {
|
|
5908
|
+
refreshPluginBinExecution();
|
|
5909
|
+
return;
|
|
5910
|
+
}
|
|
5911
|
+
if (!pluginBinRuntime) return;
|
|
5912
|
+
const freshPolicy = collectWorkspacePluginBinSandboxPolicy(
|
|
5913
|
+
pluginBinRuntime,
|
|
5914
|
+
cwd,
|
|
5915
|
+
shellExecutionDescriptor.cwd || cwd,
|
|
5916
|
+
);
|
|
5917
|
+
if (
|
|
5918
|
+
canonicalPolicyAuthorityData(freshPolicy) !==
|
|
5919
|
+
approvedWorkspacePluginPolicy
|
|
5920
|
+
) {
|
|
5921
|
+
const error = new Error(
|
|
5922
|
+
"Workspace plugin sandbox authority changed after approval",
|
|
5923
|
+
);
|
|
5924
|
+
error.code = "ERR_PLUGIN_BIN_LAUNCH_AUTHORITY_CHANGED";
|
|
5925
|
+
error.pluginBinFailClosed = true;
|
|
5926
|
+
throw error;
|
|
5927
|
+
}
|
|
5928
|
+
pluginBinSandboxPolicy = freshPolicy;
|
|
5929
|
+
processProvenance = freshPolicy ? { sandboxPolicy: freshPolicy } : {};
|
|
5930
|
+
};
|
|
5931
|
+
|
|
5932
|
+
const approvalDenial = await requestShellApproval(
|
|
5933
|
+
shellExecutionDescriptor,
|
|
5934
|
+
);
|
|
5935
|
+
if (approvalDenial) return approvalDenial;
|
|
5936
|
+
|
|
5937
|
+
let shellDispatchAdmitted = false;
|
|
5938
|
+
const admitShellDispatch = async () => {
|
|
5939
|
+
if (shellDispatchAdmitted) {
|
|
5940
|
+
throw new Error("Shell dispatch authorization was already consumed");
|
|
5941
|
+
}
|
|
5942
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
5943
|
+
refreshPluginExecutionAuthority();
|
|
5944
|
+
const revalidated = evaluateShellCommandPolicy(
|
|
5945
|
+
shellExecutionDescriptor.command,
|
|
5946
|
+
shellPolicyOpts,
|
|
5947
|
+
);
|
|
5948
|
+
if (
|
|
5949
|
+
!revalidated.allowed ||
|
|
5950
|
+
revalidated.decision !== shellPolicy.decision ||
|
|
5951
|
+
(revalidated.ruleId || null) !== (shellPolicy.ruleId || null)
|
|
5952
|
+
) {
|
|
5953
|
+
const error = new Error(
|
|
5954
|
+
"Shell policy changed after approval; command dispatch was denied",
|
|
5955
|
+
);
|
|
5956
|
+
error.code = "ERR_SHELL_POLICY_REVALIDATION_FAILED";
|
|
5957
|
+
throw error;
|
|
5958
|
+
}
|
|
5959
|
+
if (approvalAuthorization) {
|
|
5960
|
+
if (typeof approvalGate?.consumeAuthorization !== "function") {
|
|
5961
|
+
throw new Error(
|
|
5962
|
+
"Remote shell approval cannot be consumed by this ApprovalGate",
|
|
5963
|
+
);
|
|
5964
|
+
}
|
|
5965
|
+
await approvalGate.consumeAuthorization(
|
|
5966
|
+
approvalAuthorization,
|
|
5967
|
+
approvalAuthorizationContext,
|
|
5968
|
+
);
|
|
5969
|
+
approvalAuthorization = null;
|
|
5970
|
+
}
|
|
5971
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
5972
|
+
refreshPluginExecutionAuthority();
|
|
5973
|
+
const finalPolicy = evaluateShellCommandPolicy(
|
|
5974
|
+
shellExecutionDescriptor.command,
|
|
5975
|
+
shellPolicyOpts,
|
|
5976
|
+
);
|
|
5977
|
+
if (
|
|
5978
|
+
!finalPolicy.allowed ||
|
|
5979
|
+
finalPolicy.decision !== shellPolicy.decision ||
|
|
5980
|
+
(finalPolicy.ruleId || null) !== (shellPolicy.ruleId || null)
|
|
5981
|
+
) {
|
|
5982
|
+
const error = new Error(
|
|
5983
|
+
"Shell policy changed while authorization was consumed; command dispatch was denied",
|
|
5984
|
+
);
|
|
5985
|
+
error.code = "ERR_SHELL_POLICY_REVALIDATION_FAILED";
|
|
5986
|
+
throw error;
|
|
5987
|
+
}
|
|
5988
|
+
// Auditing is intentionally committed only after durable authorization
|
|
5989
|
+
// consumption and immediately before the first external side effect.
|
|
5990
|
+
commitShellApprovalSideEffects(approvalGateResult);
|
|
5991
|
+
if (pluginBinInvocation) {
|
|
5992
|
+
pluginBinResult.plugin_bin.launch_identity_reattested = true;
|
|
5993
|
+
}
|
|
5994
|
+
shellDispatchAdmitted = true;
|
|
5995
|
+
};
|
|
5996
|
+
|
|
5061
5997
|
// Background: spawn, register, return a task_id immediately. The agent
|
|
5062
5998
|
// polls output + completion via check_shell. No timeout — that's the whole
|
|
5063
5999
|
// point of backgrounding (builds, test suites, dev servers).
|
|
5064
6000
|
if (args.run_in_background === true) {
|
|
5065
|
-
if (
|
|
6001
|
+
if (shellSandbox) {
|
|
5066
6002
|
return attachDescriptor({
|
|
5067
6003
|
error:
|
|
5068
6004
|
"[Sandbox] Background shell tasks are not supported in the ephemeral sandbox. Run in the foreground or explicitly disable --sandbox.",
|
|
5069
6005
|
policy: { decision: "deny", via: "sandbox" },
|
|
5070
6006
|
});
|
|
5071
6007
|
}
|
|
5072
|
-
const backgroundPlatform =
|
|
5073
|
-
typeof _backgroundProcessDeps.platform === "function"
|
|
5074
|
-
? _backgroundProcessDeps.platform()
|
|
5075
|
-
: process.platform;
|
|
5076
6008
|
const pinnedBackgroundBoundaries = [
|
|
5077
6009
|
...(pluginBinSandboxPolicy?.requiredBoundaries || []),
|
|
5078
6010
|
];
|
|
@@ -5124,16 +6056,7 @@ async function executeToolInner(
|
|
|
5124
6056
|
cwd: workingDirectory,
|
|
5125
6057
|
shell: false,
|
|
5126
6058
|
windowsHide: true,
|
|
5127
|
-
env:
|
|
5128
|
-
...process.env,
|
|
5129
|
-
CLAUDECODE: "1",
|
|
5130
|
-
...(sessionId
|
|
5131
|
-
? {
|
|
5132
|
-
CC_SESSION_ID: String(sessionId),
|
|
5133
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5134
|
-
}
|
|
5135
|
-
: {}),
|
|
5136
|
-
}).env,
|
|
6059
|
+
env: shellChildEnvironment,
|
|
5137
6060
|
detached: false,
|
|
5138
6061
|
origin: "tool:run_shell",
|
|
5139
6062
|
policy: "allow",
|
|
@@ -5188,6 +6111,9 @@ async function executeToolInner(
|
|
|
5188
6111
|
},
|
|
5189
6112
|
};
|
|
5190
6113
|
}
|
|
6114
|
+
// Admission precedes task cleanup, sequence allocation, listener
|
|
6115
|
+
// registration, proxy startup and process creation.
|
|
6116
|
+
await admitShellDispatch();
|
|
5191
6117
|
// Free memory from idle background tasks before adding another, so a
|
|
5192
6118
|
// long agent run can't accumulate forgotten dev servers (no-op unless
|
|
5193
6119
|
// the machine is actually under memory pressure).
|
|
@@ -5199,7 +6125,7 @@ async function executeToolInner(
|
|
|
5199
6125
|
cwd:
|
|
5200
6126
|
linuxGenericBackgroundLaunch?.options.cwd ||
|
|
5201
6127
|
(linuxDirectPluginStrongBackground
|
|
5202
|
-
?
|
|
6128
|
+
? executionPlugin.sandboxExecution.workingDirectory
|
|
5203
6129
|
: args.cwd || cwd),
|
|
5204
6130
|
status: "running",
|
|
5205
6131
|
exitCode: null,
|
|
@@ -5230,16 +6156,7 @@ async function executeToolInner(
|
|
|
5230
6156
|
// Credential proxy (opt-in, CC_CREDENTIAL_PROXY): keeps the agent's
|
|
5231
6157
|
// real long-lived secrets out of the spawned command's env — a
|
|
5232
6158
|
// no-op (same object) when disabled. See credential-proxy.js.
|
|
5233
|
-
env:
|
|
5234
|
-
...process.env,
|
|
5235
|
-
CLAUDECODE: "1",
|
|
5236
|
-
...(sessionId
|
|
5237
|
-
? {
|
|
5238
|
-
CC_SESSION_ID: String(sessionId),
|
|
5239
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5240
|
-
}
|
|
5241
|
-
: {}),
|
|
5242
|
-
}).env,
|
|
6159
|
+
env: shellChildEnvironment,
|
|
5243
6160
|
// POSIX: own process group so check_shell{kill}/teardown can signal
|
|
5244
6161
|
// the whole tree (shell + its grandchild command). No-op on Windows
|
|
5245
6162
|
// where the tree is killed via taskkill /T instead.
|
|
@@ -5266,18 +6183,16 @@ async function executeToolInner(
|
|
|
5266
6183
|
linuxGenericBackgroundLaunch.options,
|
|
5267
6184
|
);
|
|
5268
6185
|
} else if (pluginBinInvocation) {
|
|
5269
|
-
reattestPluginBinInvocation(pluginBinInvocation);
|
|
5270
|
-
pluginBinResult.plugin_bin.launch_identity_reattested = true;
|
|
5271
6186
|
child = broker.spawn(
|
|
5272
|
-
|
|
6187
|
+
executionPlugin.sandboxExecution?.kind ===
|
|
5273
6188
|
"strict-plugin-node-bin"
|
|
5274
|
-
?
|
|
5275
|
-
:
|
|
5276
|
-
|
|
6189
|
+
? executionPlugin.sandboxExecution.runtimePath
|
|
6190
|
+
: executionPlugin.command,
|
|
6191
|
+
executionPlugin.argv,
|
|
5277
6192
|
{
|
|
5278
6193
|
...brokerOpts,
|
|
5279
6194
|
cwd:
|
|
5280
|
-
|
|
6195
|
+
executionPlugin.sandboxExecution?.workingDirectory ||
|
|
5281
6196
|
brokerOpts.cwd,
|
|
5282
6197
|
shell: false,
|
|
5283
6198
|
},
|
|
@@ -5373,7 +6288,7 @@ async function executeToolInner(
|
|
|
5373
6288
|
);
|
|
5374
6289
|
}
|
|
5375
6290
|
|
|
5376
|
-
if (
|
|
6291
|
+
if (shellSandbox) {
|
|
5377
6292
|
if (pluginBinSandboxPolicy) {
|
|
5378
6293
|
return attachDescriptor(
|
|
5379
6294
|
{
|
|
@@ -5391,15 +6306,16 @@ async function executeToolInner(
|
|
|
5391
6306
|
override || runtimeDescriptor,
|
|
5392
6307
|
);
|
|
5393
6308
|
}
|
|
6309
|
+
await admitShellDispatch();
|
|
5394
6310
|
const { executeSandboxedShell, sandboxSummary } =
|
|
5395
6311
|
await import("../lib/agent-sandbox.js");
|
|
5396
6312
|
// Domain-restricted networking is ENFORCED by routing the sandboxed
|
|
5397
6313
|
// process's egress through a local filtering proxy (see
|
|
5398
6314
|
// sandbox-egress-proxy.js). Start it only when the policy actually
|
|
5399
6315
|
// restricts domains and network is on; tear it down after the command.
|
|
5400
|
-
const sboxPolicy =
|
|
6316
|
+
const sboxPolicy = shellSandbox.policy || {};
|
|
5401
6317
|
const needsEgress =
|
|
5402
|
-
|
|
6318
|
+
shellSandbox.network === true &&
|
|
5403
6319
|
(sboxPolicy.allowedDomains?.length || 0) +
|
|
5404
6320
|
(sboxPolicy.deniedDomains?.length || 0) >
|
|
5405
6321
|
0;
|
|
@@ -5428,20 +6344,25 @@ async function executeToolInner(
|
|
|
5428
6344
|
}
|
|
5429
6345
|
let result;
|
|
5430
6346
|
try {
|
|
5431
|
-
|
|
6347
|
+
// Proxy startup and dynamic imports are asynchronous. Re-read the
|
|
6348
|
+
// live authority once more after they settle and immediately before
|
|
6349
|
+
// the synchronous sandbox dispatch.
|
|
6350
|
+
await shellDispatchPolicyAuthority?.revalidate?.();
|
|
6351
|
+
refreshPluginExecutionAuthority();
|
|
6352
|
+
if (pluginBinSandboxPolicy) {
|
|
6353
|
+
const error = new Error(
|
|
6354
|
+
"A plugin sandbox authority appeared while the legacy shell sandbox was starting",
|
|
6355
|
+
);
|
|
6356
|
+
error.code = "ERR_PLUGIN_BIN_SANDBOX_ROUTE_CONFLICT";
|
|
6357
|
+
error.pluginBinFailClosed = true;
|
|
6358
|
+
throw error;
|
|
6359
|
+
}
|
|
6360
|
+
result = executeSandboxedShell(args.command, shellSandbox, {
|
|
5432
6361
|
cwd: args.cwd || cwd,
|
|
5433
6362
|
timeout: _resolveShellTimeout(args.timeout),
|
|
5434
6363
|
maxBuffer: 1024 * 1024,
|
|
5435
6364
|
egressProxy,
|
|
5436
|
-
env:
|
|
5437
|
-
CLAUDECODE: "1",
|
|
5438
|
-
...(sessionId
|
|
5439
|
-
? {
|
|
5440
|
-
CC_SESSION_ID: String(sessionId),
|
|
5441
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5442
|
-
}
|
|
5443
|
-
: {}),
|
|
5444
|
-
},
|
|
6365
|
+
env: sandboxChildEnvironment,
|
|
5445
6366
|
});
|
|
5446
6367
|
} finally {
|
|
5447
6368
|
if (proxyHandle) {
|
|
@@ -5453,7 +6374,7 @@ async function executeToolInner(
|
|
|
5453
6374
|
}
|
|
5454
6375
|
}
|
|
5455
6376
|
const common = {
|
|
5456
|
-
sandbox: sandboxSummary(
|
|
6377
|
+
sandbox: sandboxSummary(shellSandbox),
|
|
5457
6378
|
shellCommandPolicy: shellPolicy,
|
|
5458
6379
|
approval: approvalOutcome,
|
|
5459
6380
|
policyTrace: ["shell-policy", "approval", "sandbox"],
|
|
@@ -5492,18 +6413,10 @@ async function executeToolInner(
|
|
|
5492
6413
|
// Credential proxy (opt-in, CC_CREDENTIAL_PROXY): keeps the agent's
|
|
5493
6414
|
// real long-lived secrets out of the shell's env — a no-op (same
|
|
5494
6415
|
// object) when disabled. See credential-proxy.js.
|
|
5495
|
-
env:
|
|
5496
|
-
...process.env,
|
|
5497
|
-
CLAUDECODE: "1",
|
|
5498
|
-
...(sessionId
|
|
5499
|
-
? {
|
|
5500
|
-
CC_SESSION_ID: String(sessionId),
|
|
5501
|
-
CLAUDE_CODE_SESSION_ID: String(sessionId),
|
|
5502
|
-
}
|
|
5503
|
-
: {}),
|
|
5504
|
-
}).env,
|
|
6416
|
+
env: shellChildEnvironment,
|
|
5505
6417
|
};
|
|
5506
6418
|
let output;
|
|
6419
|
+
await admitShellDispatch();
|
|
5507
6420
|
const brokerExecOpts = {
|
|
5508
6421
|
...fgExecOpts,
|
|
5509
6422
|
origin: processOrigin,
|
|
@@ -5512,17 +6425,15 @@ async function executeToolInner(
|
|
|
5512
6425
|
...processProvenance,
|
|
5513
6426
|
};
|
|
5514
6427
|
if (pluginBinInvocation) {
|
|
5515
|
-
reattestPluginBinInvocation(pluginBinInvocation);
|
|
5516
|
-
pluginBinResult.plugin_bin.launch_identity_reattested = true;
|
|
5517
6428
|
const res = broker.spawnSync(
|
|
5518
|
-
|
|
5519
|
-
?
|
|
5520
|
-
:
|
|
5521
|
-
|
|
6429
|
+
executionPlugin.sandboxExecution?.kind === "strict-plugin-node-bin"
|
|
6430
|
+
? executionPlugin.sandboxExecution.runtimePath
|
|
6431
|
+
: executionPlugin.command,
|
|
6432
|
+
executionPlugin.argv,
|
|
5522
6433
|
{
|
|
5523
6434
|
...brokerExecOpts,
|
|
5524
6435
|
cwd:
|
|
5525
|
-
|
|
6436
|
+
executionPlugin.sandboxExecution?.workingDirectory ||
|
|
5526
6437
|
brokerExecOpts.cwd,
|
|
5527
6438
|
shell: false,
|
|
5528
6439
|
windowsHide: true,
|
|
@@ -5601,6 +6512,15 @@ async function executeToolInner(
|
|
|
5601
6512
|
: {}),
|
|
5602
6513
|
stderr: (err.stderr || "").substring(0, 2000),
|
|
5603
6514
|
exitCode: err.status,
|
|
6515
|
+
...(typeof err.code === "string" && err.code
|
|
6516
|
+
? {
|
|
6517
|
+
policy: {
|
|
6518
|
+
decision: "deny",
|
|
6519
|
+
via: "dispatch-revalidation",
|
|
6520
|
+
code: err.code,
|
|
6521
|
+
},
|
|
6522
|
+
}
|
|
6523
|
+
: {}),
|
|
5604
6524
|
...shellMeta,
|
|
5605
6525
|
...pluginBinResult,
|
|
5606
6526
|
shellCommandPolicy: shellPolicy,
|
|
@@ -5724,8 +6644,18 @@ async function executeToolInner(
|
|
|
5724
6644
|
approvalGate &&
|
|
5725
6645
|
typeof approvalGate.hasConfirmer === "function" &&
|
|
5726
6646
|
!approvalGate.hasConfirmer();
|
|
6647
|
+
// A durable remote-approval consumer is an explicit fail-closed mode.
|
|
6648
|
+
// run_code does not yet carry an exact lease tuple, so it must still
|
|
6649
|
+
// consult the remote confirmer in headless sessions; that confirmer
|
|
6650
|
+
// returns lease-unavailable and no code is persisted or launched.
|
|
6651
|
+
const durableAuthorizationGate =
|
|
6652
|
+
approvalGate &&
|
|
6653
|
+
typeof approvalGate.hasAuthorizationConsumer === "function" &&
|
|
6654
|
+
approvalGate.hasAuthorizationConsumer() === true;
|
|
5727
6655
|
if (
|
|
5728
|
-
(interactiveApproval ||
|
|
6656
|
+
(interactiveApproval ||
|
|
6657
|
+
policyGateNoConfirmer ||
|
|
6658
|
+
durableAuthorizationGate) &&
|
|
5729
6659
|
approvalGate &&
|
|
5730
6660
|
!ruleAllowed &&
|
|
5731
6661
|
typeof approvalGate.decide === "function"
|
|
@@ -5736,7 +6666,7 @@ async function executeToolInner(
|
|
|
5736
6666
|
sessionId,
|
|
5737
6667
|
riskLevel: APPROVAL_RISK.HIGH,
|
|
5738
6668
|
tool: "run_code",
|
|
5739
|
-
args:
|
|
6669
|
+
args: runCodeApprovalDescriptor(args, cwd),
|
|
5740
6670
|
});
|
|
5741
6671
|
if (gate.decision !== APPROVAL_DECISION.ALLOW) {
|
|
5742
6672
|
// Actionable denial (matches run_shell, Claude-Code 2.1.193): tell the
|
|
@@ -5753,6 +6683,23 @@ async function executeToolInner(
|
|
|
5753
6683
|
},
|
|
5754
6684
|
});
|
|
5755
6685
|
}
|
|
6686
|
+
if (gate.authorization) {
|
|
6687
|
+
return attachDescriptor({
|
|
6688
|
+
error:
|
|
6689
|
+
"[ApprovalGate] run_code received a durable authorization, but this tool does not yet implement an exact one-shot consume tuple. No code was launched.",
|
|
6690
|
+
approval: {
|
|
6691
|
+
decision: APPROVAL_DECISION.DENY,
|
|
6692
|
+
via: "authorization-consume-unsupported",
|
|
6693
|
+
riskLevel: "high",
|
|
6694
|
+
policy: gate.policy,
|
|
6695
|
+
},
|
|
6696
|
+
policy: {
|
|
6697
|
+
decision: "deny",
|
|
6698
|
+
via: "authorization-consume-unsupported",
|
|
6699
|
+
code: "CC_RUN_CODE_AUTHORIZATION_CONSUME_UNSUPPORTED",
|
|
6700
|
+
},
|
|
6701
|
+
});
|
|
6702
|
+
}
|
|
5756
6703
|
}
|
|
5757
6704
|
return attachDescriptor(await _executeRunCode(args, cwd));
|
|
5758
6705
|
}
|