chainlesschain 0.162.187 → 0.162.189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CETaO3s8.js → AIOps-DbZft8w0.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CAe7E9TW.js → ActionButton-DoOZpwfN.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BGr2mzwO.js → Analytics-B0J0KLTW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BEoY-NcT.js → AppLayout-D9eIR0qq.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-DUZivCgt.js → Artifacts-BnZXEVeo.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-C3yBs68k.js → Audit-X1ce4Bjn.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-C9KwiY3O.js → BackgroundAgents-BvWR-y5v.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BoSWD_C8.js → Backup-BublSGEd.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Dp09GkxL.js → BaseInput-CR_77Hso.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-5t9d-Ja0.js → Chat-DixW52tq.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DmVku4GH.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-8QJFlkqI.js → Checkbox-CCv1C87K.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BJVFQ3lL.js → Codegen-B03Cc3dd.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-6hdLD0oB.js → Col-CA06-6pU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B8opo0uY.js → Community-Bj881J52.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Dv8HudiN.js → Compact-UZLTPoaL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DhxuCS5N.js → Compliance-kGwzRb_0.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Dggy6VIL.js → Cowork-KundoYM8.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-DSmDqJTT.js → Cron-BYZyoqDe.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-7bZq0j5f.js → Crosschain-BhrrppBX.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BWY4kh-H.js → DID-CdC6EguZ.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-bOzPZxlu.js → Dashboard-CcQx3J4y.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BTFJHqQu.js → Dropdown-BpijtEzQ.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BC3MOdtT.js → EmailListRenderer-DfZjeZTe.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CFHAdTTY.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-DZ_4aF_n.js → Federation-Okdi3pJm.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BZje6UTj.js → FormItemContext-mH-HebKW.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-D7QfCvjN.js → GenericCardRenderer-xivV69x7.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DuG7qANG.js → Git-1rh_YBwg.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BDmYAgTh.js → Governance-eAXkNxdY.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-DSXAfc1Q.js → Inference-CS6dMVTN.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DusleniP.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CpImmA8p.js → Logs-KchHtiHt.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BZ6LJBkk.js → MarkdownRenderer-TabqHW9B.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-DXS8CXkE.js → Marketplace-CsmGcX5X.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-D151UfUI.js → McpTools-BaPFnnyz.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-DKDxnoA1.js → Memory-2m-sUUf7.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BFZ95iKM.js → MobileBridge-D5nVZBRu.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CDAR-grF.js → MobileProjects-Bo8MLZvT.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-OGJ95xJs.js → Mtc-3GDvamGa.js} +6 -6
  42. package/src/assets/web-panel/assets/{MtcAudit-BmGTo1XC.js → MtcAudit-D2zGuZCH.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-XumJRqOa.js → Multisig-BBVQ4t6t.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DHhhRZed.js → NLProgramming-BIOCyOGp.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-B9rGxNNr.js → Notes-DVwBGpzc.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DClLbMYV.js → NotificationSettings-BDnHfIfX.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-I8OohT7N.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-M6WzaYVa.js → Organization-D3SRKaa7.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Cb3B5KtM.js → Overflow-C2B8ahyn.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BGk57kya.js → P2P-2iwQzG65.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-CXcKyPqK.js → PdhVaultBrowser-CoPiWqMN.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-hsfJ-Y0e.js → Permissions-yV5XNXo5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BSBii4js.js → PersonalDataHub-uCDJhMUF.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-BNw52rey.js → Pipeline-hN7ucMOA.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DUpfue4E.js → Privacy-CgU0ptN4.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C68NsP3h.js → ProjectInit-CfYPiT6j.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BJ2It9v4.js → ProjectSettings-B0A2C4nw.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BE5C0ajn.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-EnU4VWwS.js → Providers-CstQPf8W.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BRt5-B_I.js → QrScannerModal-Co3z_ynk.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-Jd5L9UL2.js → QuickAsk-DMTS6pQ6.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BNDhoVIu.js → Recommend-ETw4odUa.js} +1 -1
  63. package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
  64. package/src/assets/web-panel/assets/{Reputation-D1wQPUNv.js → Reputation-BTVqQgKq.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DnjRegul.js → Row-CBKKwwtO.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C_T4I-nF.js → RssFeed-BHUQ438I.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-C4lrxlLh.js → Search-B4NqeUsF.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-ByYT-VzP.js → Security-BRjoYG5_.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-BHpyEqB5.js → Services-CxQzu3p3.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C0I23vG-.js → Skeleton-BKzwwmWO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DGZXz22K.js → Skills-DrsFcgth.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DL_urP9A.js → Sla-D21tmXCH.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-6x-hUNQ_.js → SpeechSettings-CjyjLfp8.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-Crk5TCXq.js → SyncSettings-Cu45-tnx.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-rZGq5PZs.js → Tasks-oMcILYOj.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-DT5el8Q8.js → Templates-Bh-Zcr5R.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-mlgeyMK8.js → Tenant-68ns63aI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-C8HEA-lM.js → Terminal-BLJkFykE.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DvlqBayB.js → TimelineRenderer-xcrJLq-O.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C7lDcNmT.js → Tokens-C6PpFJtf.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-CM8nCnIJ.js → Trigger-BaKA7tdM.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BQDLUQ9x.js → Trust-BzhA57Jc.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CVqM0OFE.js → UkeySign-4_U3JkIR.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BvAcQpzT.js → VideoEditing-162AANxX.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-zFVFaAZ2.js → Wallet-B1viuql_.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Cc522ums.js → WebAuthn-ZVWazhCy.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Bg_wxpDo.js → WorkflowEditor-tzdyUk-1.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-jg0QwC8K.js → chat-DmrHNuwq.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-ALzYz1gO.js → colors-DAJ_M-PK.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DXHYUoqi.js → compact-item-uIzmYwLs.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BsGsBM5m.js → createContext-D_ZmuLwb.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-BccMLzxA.js → hasIn-CDt_shTL.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Cg9cOVe9.js → index-BAvSmJXW.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BTTeAiOP.js → index-BDF6EQBs.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-C3k3ZwkQ.js → index-BHvlm0In.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CQ1ORdp-.js → index-BRa4kJ_S.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BcJzphrP.js → index-BTCXf1t-.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-rIy96PeM.js → index-Bbxjfstq.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CSFESurl.js → index-Bx4d6c1k.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cmg_Npx5.js → index-ByzGvpR9.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CahDve8M.js → index-C16fYKFD.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-2VstcImq.js → index-C5WcdJH2.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-C3Rf8wIM.js → index-CG0lY4h3.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-uzykuL_R.js → index-CKyd472S.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CBVIFX-x.js → index-CaelyMgF.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-TeCof1s3.js → index-CbD_WW_6.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BfF0OGKU.js → index-CcZ9xJI9.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-oWltafBn.js → index-CeUpKjvi.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CWLWBgfe.js → index-Cvak6Oe2.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C0AbXsDy.js → index-CzXqvMuP.js} +1 -1
  112. package/src/assets/web-panel/assets/index-D-Swwqld.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BfV8RDlg.js → index-D4n1lGTB.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Dxqiu9-F.js → index-D9OE8B3d.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BdCmJ_bG.js → index-DBFmcaCQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DgjQ-kdT.js → index-DdUMcwRB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BBbbr1YB.js → index-DeGBoLQA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dmk7dD-T.js → index-DfSSy9uy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-JS2LXdd0.js → index-DqDSF2RW.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D9oV2iRR.js → index-DvFhhe6r.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BvsII10i.js → index-DwfvPe-j.js} +1 -1
  122. package/src/assets/web-panel/assets/index-FMZAMKy8.js +1 -0
  123. package/src/assets/web-panel/assets/{index-CrZHUsOZ.js → index-FyplHuNt.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CLzPzN75.js → index-GcKOHD_c.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-koT8Q86Z.js → index-OzTxFIJP.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-08diKcTm.js → index-QsgfSZrV.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CJg6Wsjc.js → index-Z8P4BexE.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-srqW5_-X.js → index-clSX4PEc.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-mIQUoUx1.js → index-ifxmu8SY.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cz_s25m3.js → index-rS3gl_68.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-UQXedWQz.js → index-rXdwUeE3.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-rcwgNMc0.js → index-saf3VNeP.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-xafkqrW3.js → initDefaultProps-pmfaD-aK.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BrtyQXLk.js → motion-CaQ2CpYF.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-BunUyg59.js → move-CkKTqNbI.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BPwjBeuE.js → mtc-parser-Dd4xsgRI.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BpyqZlNO.js → omit-BFuA5YEs.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DDrV0XjE.js → pickAttrs-DDfxOAJc.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-1A18Pcl2.js → placementArrow-Be595JCp.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-B8Fnlmso.js → responsiveObserve-7byljUM3.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-x-Fwb2Zm.js → slide-CTWfOTaY.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-aAG5yJl_.js → statusUtils-CNZOuIxM.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BRQSTMRL.js → styleChecker-DwOcBV78.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-D5X_KozY.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DN_YIjP-.js → useFs-CzyQjH9r.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BizxAvjw.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-B5oAxN93.js → vnode-DVfZiS5N.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DJhvyzKv.js → zoom-BYme7fbY.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agent.js +22 -0
  151. package/src/commands/auto-mode.js +47 -0
  152. package/src/commands/checkpoint-managed.js +1059 -0
  153. package/src/commands/checkpoint.js +3 -0
  154. package/src/commands/team-distributed.js +3324 -0
  155. package/src/commands/team.js +1130 -74
  156. package/src/data/auto-mode-safety-eval-v1.json +3294 -0
  157. package/src/data/changelog.json +26 -0
  158. package/src/lib/agent-core.js +1 -0
  159. package/src/lib/agent-team/task-lease.js +271 -3
  160. package/src/lib/agent-team/team-adjudication.js +1501 -0
  161. package/src/lib/agent-team/team-budget.js +4 -1
  162. package/src/lib/agent-team/team-control-store.js +582 -0
  163. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  164. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  165. package/src/lib/agent-team/team-runner.js +319 -22
  166. package/src/lib/agent-team/team-worktree.js +1403 -95
  167. package/src/lib/auto-mode-safety-classifier.js +2034 -0
  168. package/src/lib/auto-mode-safety-eval.js +1022 -0
  169. package/src/lib/managed-checkpoint-render.js +60 -0
  170. package/src/lib/managed-tool-checkpoint.js +194 -0
  171. package/src/lib/process-execution-broker/index.js +438 -5
  172. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  173. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  174. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  175. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  176. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  177. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  178. package/src/lib/process-execution-broker/workspace-transaction.js +3556 -0
  179. package/src/lib/secure-file-identity.js +433 -0
  180. package/src/lib/turn-binding.js +160 -2
  181. package/src/lib/with-file-lock.js +140 -23
  182. package/src/repl/agent-repl.js +19 -1
  183. package/src/runtime/agent-core.js +366 -122
  184. package/src/runtime/headless-runner.js +12 -0
  185. package/src/runtime/headless-stream.js +3 -0
  186. package/src/runtime/policies/agent-policy.js +14 -0
  187. package/src/assets/web-panel/assets/Projects-D2FHLUa-.js +0 -1
  188. package/src/assets/web-panel/assets/RemoteSession-BII41Mel.js +0 -5
  189. package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +0 -1
  190. package/src/assets/web-panel/assets/index-8zbMbbuf.js +0 -1
  191. package/src/assets/web-panel/assets/index-C97f71oi.js +0 -1
@@ -0,0 +1,2034 @@
1
+ /**
2
+ * Auto-mode safety classifier (P2-15).
3
+ *
4
+ * This is a deterministic, offline classifier for evaluating dangerous tool
5
+ * intents. It never executes the supplied command and never reads a referenced
6
+ * file. The output contains stable reason codes, but deliberately omits raw
7
+ * arguments so benchmark reports cannot echo credentials from a custom corpus.
8
+ *
9
+ * The classifier is an additional signal, not an authority. Existing managed
10
+ * denies, shell hard-denies, credential guards and OS sandbox decisions remain
11
+ * authoritative. `evaluateAutoModeSafety` reports those layers separately.
12
+ */
13
+
14
+ import path from "node:path";
15
+ import { createRequire } from "node:module";
16
+ import { classifyRemoteExecCommand } from "./install-command-policy.js";
17
+ import {
18
+ commandReadsCredentials,
19
+ credentialFileReason,
20
+ isSecretEnvName,
21
+ } from "./credential-guard.js";
22
+ import { containsSecret } from "./secret-scan.js";
23
+
24
+ const require_ = createRequire(import.meta.url);
25
+ const shellPolicy = require_("../runtime/coding-agent-shell-policy.cjs");
26
+ const { evaluateShellCommandPolicy } = shellPolicy;
27
+
28
+ export const AUTO_MODE_SAFETY_CLASSIFICATION_SCHEMA =
29
+ "chainlesschain.auto-mode-safety-classification/v1";
30
+ export const AUTO_MODE_SAFETY_VERDICT_SCHEMA =
31
+ "chainlesschain.auto-mode-safety-verdict/v1";
32
+ export const AUTO_MODE_SAFETY_CLASSIFIER_VERSION = "1.0.0";
33
+
34
+ export const SAFETY_CATEGORY = Object.freeze({
35
+ WORKSPACE_SCOPE_ESCAPE: "workspace.scope_escape",
36
+ SECRET_EGRESS: "secret.egress",
37
+ PRODUCTION_DEPLOYMENT: "deployment.production",
38
+ GIT_FORCE_PUSH: "git.force_push",
39
+ UNREVIEWED_MERGE: "merge.unreviewed",
40
+ THIRD_PARTY_AGENT_UNISOLATED: "agent.third_party_unisolated",
41
+ FILESYSTEM_DESTRUCTIVE: "filesystem.destructive",
42
+ INFRASTRUCTURE_DESTRUCTIVE: "infrastructure.destructive",
43
+ REMOTE_CODE_EXECUTION: "code.remote_exec",
44
+ SHELL_ENCODED_EXECUTION: "shell.encoded_execution",
45
+ PUBLICATION: "artifact.publication",
46
+ });
47
+
48
+ export const RELEASE_CRITICAL_SAFETY_CATEGORIES = Object.freeze([
49
+ SAFETY_CATEGORY.WORKSPACE_SCOPE_ESCAPE,
50
+ SAFETY_CATEGORY.SECRET_EGRESS,
51
+ SAFETY_CATEGORY.PRODUCTION_DEPLOYMENT,
52
+ SAFETY_CATEGORY.GIT_FORCE_PUSH,
53
+ SAFETY_CATEGORY.UNREVIEWED_MERGE,
54
+ SAFETY_CATEGORY.THIRD_PARTY_AGENT_UNISOLATED,
55
+ ]);
56
+
57
+ const RISK_RANK = Object.freeze({ low: 0, medium: 1, high: 2 });
58
+ const SEVERITY_RANK = Object.freeze({
59
+ none: 0,
60
+ medium: 1,
61
+ high: 2,
62
+ critical: 3,
63
+ });
64
+
65
+ const MUTATING_PATH_TOOLS = new Set([
66
+ "apply_patch",
67
+ "delete_file",
68
+ "edit_file",
69
+ "edit_file_hashed",
70
+ "move_file",
71
+ "notebook_edit",
72
+ "write_file",
73
+ ]);
74
+
75
+ const PATH_ARG_KEYS = new Set([
76
+ "destination",
77
+ "destinations",
78
+ "destinationpath",
79
+ "file",
80
+ "files",
81
+ "filepath",
82
+ "from",
83
+ "frompath",
84
+ "notebookpath",
85
+ "output",
86
+ "outputpath",
87
+ "path",
88
+ "paths",
89
+ "target",
90
+ "targets",
91
+ "targetpath",
92
+ "to",
93
+ "topath",
94
+ ]);
95
+
96
+ const COMMAND_WRAPPERS = new Set([
97
+ "bash",
98
+ "cmd",
99
+ "dash",
100
+ "fish",
101
+ "ksh",
102
+ "powershell",
103
+ "pwsh",
104
+ "sh",
105
+ "zsh",
106
+ ]);
107
+
108
+ const EXEC_PREFIXES = new Set([
109
+ "command",
110
+ "doas",
111
+ "env",
112
+ "exec",
113
+ "nice",
114
+ "nohup",
115
+ "pkexec",
116
+ "sudo",
117
+ ]);
118
+
119
+ const PREFIX_OPTIONS_WITH_VALUES = Object.freeze({
120
+ doas: new Set(["-a", "-c", "-u"]),
121
+ env: new Set(["-c", "-s", "-u", "--chdir", "--split-string", "--unset"]),
122
+ nice: new Set(["-n", "--adjustment"]),
123
+ pkexec: new Set(["--user"]),
124
+ sudo: new Set([
125
+ "-c",
126
+ "-d",
127
+ "-g",
128
+ "-h",
129
+ "-p",
130
+ "-r",
131
+ "-t",
132
+ "-u",
133
+ "--chdir",
134
+ "--chroot",
135
+ "--close-from",
136
+ "--command-timeout",
137
+ "--group",
138
+ "--host",
139
+ "--other-user",
140
+ "--prompt",
141
+ "--role",
142
+ "--type",
143
+ "--user",
144
+ ]),
145
+ });
146
+
147
+ const DESTRUCTIVE_COMMANDS = new Set([
148
+ "del",
149
+ "diskpart",
150
+ "erase",
151
+ "format",
152
+ "format-volume",
153
+ "mkfs",
154
+ "rd",
155
+ "remove-item",
156
+ "ri",
157
+ "rm",
158
+ "rmdir",
159
+ "shred",
160
+ "wipefs",
161
+ ]);
162
+
163
+ const AGENT_BINARIES = new Set([
164
+ "aider",
165
+ "claude",
166
+ "codex",
167
+ "gemini",
168
+ "opencode",
169
+ ]);
170
+
171
+ function normalizeRisk(value, fallback = "medium") {
172
+ const risk = typeof value === "string" ? value.toLowerCase() : "";
173
+ return Object.hasOwn(RISK_RANK, risk) ? risk : fallback;
174
+ }
175
+
176
+ function maxRisk(a, b) {
177
+ const left = normalizeRisk(a);
178
+ const right = normalizeRisk(b);
179
+ return RISK_RANK[right] > RISK_RANK[left] ? right : left;
180
+ }
181
+
182
+ function maxSeverity(a, b) {
183
+ const left = Object.hasOwn(SEVERITY_RANK, a) ? a : "none";
184
+ const right = Object.hasOwn(SEVERITY_RANK, b) ? b : "none";
185
+ return SEVERITY_RANK[right] > SEVERITY_RANK[left] ? right : left;
186
+ }
187
+
188
+ function stableSerialize(value, seen = new WeakSet()) {
189
+ if (value == null || typeof value !== "object") {
190
+ if (typeof value === "bigint") return JSON.stringify(String(value));
191
+ return JSON.stringify(value);
192
+ }
193
+ if (seen.has(value)) return '"[Circular]"';
194
+ seen.add(value);
195
+ if (Array.isArray(value)) {
196
+ const out = `[${value.map((item) => stableSerialize(item, seen)).join(",")}]`;
197
+ seen.delete(value);
198
+ return out;
199
+ }
200
+ const out = `{${Object.keys(value)
201
+ .sort()
202
+ .map((key) => `${JSON.stringify(key)}:${stableSerialize(value[key], seen)}`)
203
+ .join(",")}}`;
204
+ seen.delete(value);
205
+ return out;
206
+ }
207
+
208
+ function executableBase(token) {
209
+ let normalized = String(token || "")
210
+ .replace(/^["']|["']$/g, "")
211
+ .replace(/[`^]/g, "");
212
+ normalized =
213
+ /^(?:[A-Za-z]:\\|\\\\|\.{1,2}\\)/.test(normalized) ||
214
+ /\\[^\\]+\.(?:cmd|exe)$/i.test(normalized)
215
+ ? normalized.replace(/\\/g, "/")
216
+ : normalized.replace(/\\(.)/g, "$1");
217
+ const base = normalized.split("/").pop() || "";
218
+ return base.toLowerCase().replace(/\.(?:cmd|com|exe)$/i, "");
219
+ }
220
+
221
+ function unwrapQuoted(value) {
222
+ const text = String(value || "").trim();
223
+ if (text.length < 2) return text;
224
+ const first = text[0];
225
+ const last = text[text.length - 1];
226
+ return (first === "'" || first === '"') && first === last
227
+ ? text.slice(1, -1)
228
+ : text;
229
+ }
230
+
231
+ function tokenizeSafetyCommandWithSpans(command) {
232
+ const raw = String(command || "");
233
+ const tokens = [];
234
+ let current = "";
235
+ let start = -1;
236
+ let quote = "";
237
+
238
+ const push = (end) => {
239
+ if (start < 0) return;
240
+ tokens.push({ value: current, start, end });
241
+ current = "";
242
+ start = -1;
243
+ };
244
+
245
+ for (let i = 0; i < raw.length; i += 1) {
246
+ const ch = raw[i];
247
+ if (!quote && /\s/.test(ch)) {
248
+ push(i);
249
+ continue;
250
+ }
251
+ if (start < 0) start = i;
252
+ if (quote) {
253
+ if (ch === quote) {
254
+ quote = "";
255
+ continue;
256
+ }
257
+ if (
258
+ ch === "\\" &&
259
+ quote === '"' &&
260
+ i + 1 < raw.length &&
261
+ ['"', "\\", "$", "`"].includes(raw[i + 1])
262
+ ) {
263
+ current += raw[i + 1];
264
+ i += 1;
265
+ continue;
266
+ }
267
+ current += ch;
268
+ continue;
269
+ }
270
+ if (ch === "'" || ch === '"') {
271
+ quote = ch;
272
+ continue;
273
+ }
274
+ if (
275
+ ch === "\\" &&
276
+ i + 1 < raw.length &&
277
+ (/\s/.test(raw[i + 1]) || ["'", '"'].includes(raw[i + 1]))
278
+ ) {
279
+ current += raw[i + 1];
280
+ i += 1;
281
+ continue;
282
+ }
283
+ current += ch;
284
+ }
285
+ push(raw.length);
286
+ return tokens;
287
+ }
288
+
289
+ function tokenizeSafetyCommand(command) {
290
+ return tokenizeSafetyCommandWithSpans(command).map((token) => token.value);
291
+ }
292
+
293
+ function isAssignmentToken(token) {
294
+ return /^[A-Za-z_]\w*=/.test(String(token || ""));
295
+ }
296
+
297
+ function optionConsumesNext(prefix, token) {
298
+ const raw = String(token || "");
299
+ const lower = raw.toLowerCase();
300
+ const options = PREFIX_OPTIONS_WITH_VALUES[prefix];
301
+ if (!options || raw.includes("=")) return false;
302
+ return options.has(lower);
303
+ }
304
+
305
+ function skipPrefixOptions(tokens, start, prefix) {
306
+ let i = start;
307
+ while (i < tokens.length) {
308
+ const token = String(tokens[i] || "");
309
+ if (prefix === "env" && isAssignmentToken(token)) {
310
+ i += 1;
311
+ continue;
312
+ }
313
+ if (token === "--") return i + 1;
314
+ if (!token.startsWith("-") || token === "-") break;
315
+ const consumesNext = optionConsumesNext(prefix, token);
316
+ i += consumesNext ? 2 : 1;
317
+ }
318
+ return i;
319
+ }
320
+
321
+ function executableTokenIndex(tokens) {
322
+ let i = 0;
323
+ while (i < tokens.length) {
324
+ if (isAssignmentToken(tokens[i])) {
325
+ i += 1;
326
+ continue;
327
+ }
328
+ const prefix = executableBase(tokens[i]);
329
+ if (!EXEC_PREFIXES.has(prefix)) break;
330
+ i = skipPrefixOptions(tokens, i + 1, prefix);
331
+ }
332
+ return i;
333
+ }
334
+
335
+ function splitSafetyCommandSegments(command) {
336
+ const raw = String(command || "");
337
+ const out = [];
338
+ let start = 0;
339
+ let quote = "";
340
+ let escaping = false;
341
+
342
+ const push = (end) => {
343
+ const segment = raw
344
+ .slice(start, end)
345
+ .trim()
346
+ .replace(/^[({]\s*/, "")
347
+ .trim();
348
+ if (segment) out.push(segment);
349
+ };
350
+
351
+ for (let i = 0; i < raw.length; i += 1) {
352
+ const ch = raw[i];
353
+ if (escaping) {
354
+ escaping = false;
355
+ continue;
356
+ }
357
+ if (ch === "\\" && quote !== "'") {
358
+ escaping = true;
359
+ continue;
360
+ }
361
+ if (quote) {
362
+ if (ch === quote) quote = "";
363
+ continue;
364
+ }
365
+ if (ch === "'" || ch === '"') {
366
+ quote = ch;
367
+ continue;
368
+ }
369
+ if (ch === "\r" || ch === "\n" || "|;&".includes(ch)) {
370
+ push(i);
371
+ if (
372
+ i + 1 < raw.length &&
373
+ ((ch === "|" && raw[i + 1] === "|") ||
374
+ (ch === "&" && raw[i + 1] === "&"))
375
+ ) {
376
+ i += 1;
377
+ }
378
+ start = i + 1;
379
+ }
380
+ }
381
+ push(raw.length);
382
+ return out;
383
+ }
384
+
385
+ function shellCommandFlag(wrapper, token) {
386
+ const lower = String(token || "").toLowerCase();
387
+ if (wrapper === "cmd") return lower === "/c";
388
+ if (["powershell", "pwsh"].includes(wrapper)) {
389
+ return ["-c", "-command", "-commandwithargs"].includes(lower);
390
+ }
391
+ return /^-[a-z]*c[a-z]*$/i.test(lower);
392
+ }
393
+
394
+ function shellCommandFlagIndex(tokens, wrapperIndex, wrapper) {
395
+ for (let index = wrapperIndex + 1; index < tokens.length; index += 1) {
396
+ const token = String(tokens[index] || "");
397
+ if (shellCommandFlag(wrapper, token)) return index;
398
+ if (wrapper === "cmd") {
399
+ if (/^\/[a-z]$/i.test(token)) continue;
400
+ return -1;
401
+ }
402
+ if (token === "--") continue;
403
+ if (token.startsWith("-")) continue;
404
+ return -1;
405
+ }
406
+ return -1;
407
+ }
408
+
409
+ function unwrapShellCommand(segment) {
410
+ const raw = String(segment || "");
411
+ const spans = tokenizeSafetyCommandWithSpans(raw);
412
+ const tokens = spans.map((token) => token.value);
413
+ const wrapperIndex = executableTokenIndex(tokens);
414
+ const wrapper = executableBase(tokens[wrapperIndex]);
415
+ if (!COMMAND_WRAPPERS.has(wrapper)) return null;
416
+ const flagIndex = shellCommandFlagIndex(tokens, wrapperIndex, wrapper);
417
+ if (flagIndex < 0 || flagIndex + 1 >= spans.length) return null;
418
+ let commandStart = flagIndex + 1;
419
+ if (tokens[commandStart] === "--" && commandStart + 1 < spans.length) {
420
+ commandStart += 1;
421
+ }
422
+ return unwrapQuoted(raw.slice(spans[commandStart].start).trim());
423
+ }
424
+
425
+ function collectCommandSegments(command, depth = 0) {
426
+ const raw = String(command || "");
427
+ if (!raw.trim()) return [];
428
+ let segments = splitSafetyCommandSegments(raw);
429
+ if (!segments.length) segments = [raw];
430
+ const out = [];
431
+ for (const segment of segments) {
432
+ const text = String(segment || "").trim();
433
+ if (!text) continue;
434
+ out.push(text);
435
+ if (depth >= 3) continue;
436
+ const inner = unwrapShellCommand(text);
437
+ if (inner && inner !== text) {
438
+ out.push(...collectCommandSegments(inner, depth + 1));
439
+ }
440
+ }
441
+ return [...new Set(out)];
442
+ }
443
+
444
+ function executableTokens(segment) {
445
+ const tokens = tokenizeSafetyCommand(String(segment || ""));
446
+ return tokens.slice(executableTokenIndex(tokens)).map((token) => {
447
+ let canonical = String(token || "").replace(/[`^](.)/g, "$1");
448
+ canonical = canonical.replace(/^\\([+-])/, "$1");
449
+ if (/^[+-]/.test(canonical)) {
450
+ canonical = canonical.replace(/\\(.)/g, "$1");
451
+ }
452
+ return canonical;
453
+ });
454
+ }
455
+
456
+ function canonicalShellKeyword(token) {
457
+ return String(token || "")
458
+ .replace(/[`^](.)/g, "$1")
459
+ .replace(/\\(.)/g, "$1");
460
+ }
461
+
462
+ function inputCommand(input) {
463
+ const command = input?.args?.command;
464
+ if (typeof command === "string") return command;
465
+ const argv = input?.args?.argv;
466
+ return Array.isArray(argv)
467
+ ? argv
468
+ .map((value) => {
469
+ const token = String(value);
470
+ if (token && !/[\s|;&"'\\]/.test(token)) return token;
471
+ return `"${token.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
472
+ })
473
+ .join(" ")
474
+ : "";
475
+ }
476
+
477
+ function workspaceRoots(context = {}) {
478
+ const roots = [];
479
+ if (Array.isArray(context.workspaceRoots)) {
480
+ roots.push(
481
+ ...context.workspaceRoots.filter((root) => typeof root === "string"),
482
+ );
483
+ }
484
+ if (typeof context.workspaceRoot === "string") {
485
+ roots.push(context.workspaceRoot);
486
+ }
487
+ return [...new Set(roots.filter((root) => root.trim()))];
488
+ }
489
+
490
+ function pathApiFor(values, platform) {
491
+ const platformKey = String(platform || "").toLowerCase();
492
+ if (["win32", "windows"].includes(platformKey)) return path.win32;
493
+ if (
494
+ [
495
+ "aix",
496
+ "android",
497
+ "darwin",
498
+ "freebsd",
499
+ "linux",
500
+ "macos",
501
+ "openbsd",
502
+ "sunos",
503
+ ].includes(platformKey)
504
+ ) {
505
+ return path.posix;
506
+ }
507
+ return values.some(
508
+ (value) =>
509
+ /^[A-Za-z]:[\\/]/.test(String(value || "")) ||
510
+ /^\\\\/.test(String(value || "")),
511
+ )
512
+ ? path.win32
513
+ : path.posix;
514
+ }
515
+
516
+ function hasUnresolvedPathExpansion(target) {
517
+ const raw = String(target || "");
518
+ return (
519
+ /(?:^|[\\/])\$(?:env:)?[A-Za-z_][A-Za-z0-9_]*/i.test(raw) ||
520
+ /(?:^|[\\/])\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(raw) ||
521
+ /(?:^|[\\/])%[A-Za-z_][A-Za-z0-9_]*%/.test(raw)
522
+ );
523
+ }
524
+
525
+ function isPathWithinAnyRoot(target, roots, context = {}) {
526
+ if (!roots.length) return true;
527
+ const values = [target, ...roots, context.cwd];
528
+ const api = pathApiFor(values, context.platform);
529
+ const cwd =
530
+ typeof context.cwd === "string" && context.cwd ? context.cwd : roots[0];
531
+ const rawTarget = String(target || "").trim();
532
+ if (!rawTarget) return true;
533
+ if (
534
+ /^~(?:[\\/]|$)/.test(rawTarget) ||
535
+ hasUnresolvedPathExpansion(rawTarget)
536
+ ) {
537
+ return false;
538
+ }
539
+ const targetAbs = api.resolve(cwd, rawTarget);
540
+ for (const root of roots) {
541
+ const rootAbs = api.resolve(cwd, String(root));
542
+ const relative = api.relative(rootAbs, targetAbs);
543
+ if (
544
+ relative === "" ||
545
+ (!relative.startsWith("..") && !api.isAbsolute(relative))
546
+ ) {
547
+ return true;
548
+ }
549
+ }
550
+ return false;
551
+ }
552
+
553
+ function collectPathArgs(args, depth = 0) {
554
+ if (!args || typeof args !== "object" || depth > 4) return [];
555
+ const out = [];
556
+ for (const [key, value] of Object.entries(args)) {
557
+ const normalizedKey = key.toLowerCase().replace(/[_-]/g, "");
558
+ if (PATH_ARG_KEYS.has(normalizedKey)) {
559
+ if (typeof value === "string") out.push(value);
560
+ if (Array.isArray(value)) {
561
+ out.push(...value.filter((item) => typeof item === "string"));
562
+ }
563
+ }
564
+ if (Array.isArray(value)) {
565
+ for (const item of value) {
566
+ if (item && typeof item === "object") {
567
+ out.push(...collectPathArgs(item, depth + 1));
568
+ }
569
+ }
570
+ continue;
571
+ }
572
+ if (
573
+ value &&
574
+ typeof value === "object" &&
575
+ !Array.isArray(value) &&
576
+ !["context", "metadata"].includes(normalizedKey)
577
+ ) {
578
+ out.push(...collectPathArgs(value, depth + 1));
579
+ }
580
+ }
581
+ return out;
582
+ }
583
+
584
+ function toolLeaf(tool) {
585
+ const parts = String(tool || "")
586
+ .toLowerCase()
587
+ .split(/__|[.:/]/)
588
+ .filter(Boolean);
589
+ return parts[parts.length - 1] || "";
590
+ }
591
+
592
+ function isMutatingPathTool(tool) {
593
+ return MUTATING_PATH_TOOLS.has(toolLeaf(tool));
594
+ }
595
+
596
+ function collectPatchTargets(args) {
597
+ const targets = [];
598
+ for (const patch of [args?.patch, args?.input]) {
599
+ if (typeof patch !== "string") continue;
600
+ const header =
601
+ /^\*{3} (?:Add File|Delete File|Move to|Update File):\s*(.+?)\s*$/gm;
602
+ let match;
603
+ while ((match = header.exec(patch))) {
604
+ targets.push(unwrapQuoted(match[1]));
605
+ }
606
+ }
607
+ return targets;
608
+ }
609
+
610
+ function redirectTargets(command) {
611
+ const raw = String(command || "");
612
+ const targets = [];
613
+ let quote = "";
614
+ let escaping = false;
615
+
616
+ for (let i = 0; i < raw.length; i += 1) {
617
+ const ch = raw[i];
618
+ if (escaping) {
619
+ escaping = false;
620
+ continue;
621
+ }
622
+ if (ch === "\\" && quote !== "'") {
623
+ escaping = true;
624
+ continue;
625
+ }
626
+ if (quote) {
627
+ if (ch === quote) quote = "";
628
+ continue;
629
+ }
630
+ if (ch === "'" || ch === '"') {
631
+ quote = ch;
632
+ continue;
633
+ }
634
+ if (ch !== ">") continue;
635
+
636
+ while (raw[i + 1] === ">") i += 1;
637
+ let cursor = i + 1;
638
+ while (/\s/.test(raw[cursor] || "")) cursor += 1;
639
+ const first = raw[cursor];
640
+ if (!first || first === "&" || first === "(") continue;
641
+ let target = "";
642
+ if (first === "'" || first === '"') {
643
+ const targetQuote = first;
644
+ cursor += 1;
645
+ while (cursor < raw.length && raw[cursor] !== targetQuote) {
646
+ target += raw[cursor];
647
+ cursor += 1;
648
+ }
649
+ } else {
650
+ while (cursor < raw.length && !/[\s;&|]/.test(raw[cursor])) {
651
+ target += raw[cursor];
652
+ cursor += 1;
653
+ }
654
+ }
655
+ if (target) targets.push(target);
656
+ }
657
+ return targets;
658
+ }
659
+
660
+ function optionValue(tokens, names) {
661
+ const normalizedNames = new Set(names.map((name) => name.toLowerCase()));
662
+ for (let i = 1; i < tokens.length; i += 1) {
663
+ const token = String(tokens[i] || "");
664
+ const lower = token.toLowerCase();
665
+ const equals = lower.indexOf("=");
666
+ const name = equals >= 0 ? lower.slice(0, equals) : lower;
667
+ if (normalizedNames.has(name)) {
668
+ if (equals >= 0) return token.slice(equals + 1);
669
+ return tokens[i + 1] || "";
670
+ }
671
+ for (const option of normalizedNames) {
672
+ if (
673
+ option.length === 2 &&
674
+ lower.startsWith(option) &&
675
+ lower.length > option.length
676
+ ) {
677
+ return token.slice(option.length).replace(/^=/, "");
678
+ }
679
+ }
680
+ }
681
+ return "";
682
+ }
683
+
684
+ function powershellPositionalArgs(tokens, optionsWithValues) {
685
+ const valued = new Set(
686
+ optionsWithValues.map((option) => option.toLowerCase()),
687
+ );
688
+ const positional = [];
689
+ for (let index = 1; index < tokens.length; index += 1) {
690
+ const raw = String(tokens[index] || "");
691
+ const lower = raw.toLowerCase();
692
+ const colon = lower.indexOf(":");
693
+ const name = colon >= 0 ? lower.slice(0, colon) : lower;
694
+ if (valued.has(name)) {
695
+ if (colon < 0) index += 1;
696
+ continue;
697
+ }
698
+ if (raw.startsWith("-")) continue;
699
+ positional.push(raw);
700
+ }
701
+ return positional;
702
+ }
703
+
704
+ function shellWriteTargets(command) {
705
+ const out = [];
706
+ const raw = String(command || "");
707
+ out.push(...redirectTargets(raw));
708
+
709
+ for (const segment of collectCommandSegments(raw)) {
710
+ const tokens = executableTokens(segment);
711
+ const first = executableBase(tokens[0]);
712
+ if (["cp", "copy", "install", "move", "mv", "tee"].includes(first)) {
713
+ const targetDirectory = ["cp", "install", "mv"].includes(first)
714
+ ? optionValue(tokens, ["-t", "--target-directory"])
715
+ : "";
716
+ if (targetDirectory) {
717
+ out.push(targetDirectory);
718
+ } else {
719
+ const candidates = tokens
720
+ .slice(1)
721
+ .filter((token) => !token.startsWith("-"));
722
+ if (candidates.length) out.push(candidates[candidates.length - 1]);
723
+ }
724
+ }
725
+ if (["add-content", "out-file", "set-content"].includes(first)) {
726
+ const target =
727
+ optionValue(tokens, ["-filePath", "-literalPath", "-path"]) ||
728
+ (tokens[1] && !String(tokens[1]).startsWith("-") ? tokens[1] : "");
729
+ if (target) out.push(target);
730
+ }
731
+ if (["copy-item", "move-item"].includes(first)) {
732
+ const positional = powershellPositionalArgs(tokens, [
733
+ "-credential",
734
+ "-destination",
735
+ "-erroraction",
736
+ "-errorvariable",
737
+ "-exclude",
738
+ "-filter",
739
+ "-include",
740
+ "-informationaction",
741
+ "-informationvariable",
742
+ "-literalpath",
743
+ "-outbuffer",
744
+ "-outvariable",
745
+ "-path",
746
+ "-pipelinevariable",
747
+ "-progressaction",
748
+ "-warningaction",
749
+ "-warningvariable",
750
+ ]);
751
+ const namedSource = optionValue(tokens, ["-literalPath", "-path"]);
752
+ const target =
753
+ optionValue(tokens, ["-destination"]) ||
754
+ (namedSource
755
+ ? positional[0] || ""
756
+ : positional.length >= 2
757
+ ? positional[1]
758
+ : "");
759
+ if (target) out.push(target);
760
+ }
761
+ if (first === "dd") {
762
+ for (const token of tokens.slice(1)) {
763
+ const match = String(token).match(/^of=(.+)$/i);
764
+ if (match) out.push(match[1]);
765
+ }
766
+ }
767
+ if (
768
+ first === "touch" ||
769
+ (first === "sed" &&
770
+ tokens
771
+ .slice(1)
772
+ .some((token) => /^-(?:i|-[i]n-place)(?:[.=].*)?$/i.test(token)))
773
+ ) {
774
+ const candidates = tokens
775
+ .slice(1)
776
+ .filter((token) => !String(token).startsWith("-"));
777
+ if (candidates.length) out.push(candidates[candidates.length - 1]);
778
+ }
779
+ }
780
+ return out;
781
+ }
782
+
783
+ function hasSensitivePath(command) {
784
+ const tokens = String(command || "")
785
+ .replace(/["'|;&(),]/g, " ")
786
+ .split(/\s+/)
787
+ .filter(Boolean);
788
+ return tokens.some((token) => credentialFileReason(token));
789
+ }
790
+
791
+ function referencesSecretEnvironment(command) {
792
+ const raw = String(command || "");
793
+ const patterns = [
794
+ /\$env:([A-Za-z_][A-Za-z0-9_]*)/gi,
795
+ /\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)(?:(?::?[-+?])[^}]*)?\}/gi,
796
+ /\$([A-Za-z_][A-Za-z0-9_]*)/g,
797
+ /%([A-Za-z_][A-Za-z0-9_]*)(?::[^%]*)?%/g,
798
+ /!([A-Za-z_][A-Za-z0-9_]*)!/g,
799
+ ];
800
+ for (const pattern of patterns) {
801
+ let match;
802
+ while ((match = pattern.exec(raw))) {
803
+ if (isSecretEnvName(match[1])) return true;
804
+ }
805
+ }
806
+ return false;
807
+ }
808
+
809
+ function tokenContainsSecretReference(token) {
810
+ const raw = String(token || "");
811
+ return referencesSecretEnvironment(raw) || containsSecret(raw);
812
+ }
813
+
814
+ function curlHasEgressSink(tokens) {
815
+ for (const token of tokens.slice(1)) {
816
+ const raw = String(token || "");
817
+ const lower = raw.toLowerCase();
818
+ if (
819
+ raw === "-F" ||
820
+ raw.startsWith("-F") ||
821
+ raw === "-H" ||
822
+ raw.startsWith("-H") ||
823
+ raw === "-T" ||
824
+ raw.startsWith("-T") ||
825
+ raw === "-u" ||
826
+ raw.startsWith("-u") ||
827
+ raw === "-d" ||
828
+ /^-d.+/.test(raw) ||
829
+ [
830
+ "--cookie",
831
+ "--data",
832
+ "--data-ascii",
833
+ "--data-binary",
834
+ "--data-raw",
835
+ "--data-urlencode",
836
+ "--form",
837
+ "--form-string",
838
+ "--header",
839
+ "--json",
840
+ "--oauth2-bearer",
841
+ "--proxy-header",
842
+ "--upload-file",
843
+ "--url-query",
844
+ "--user",
845
+ ].some((option) => lower === option || lower.startsWith(`${option}=`))
846
+ ) {
847
+ return true;
848
+ }
849
+ if (/^https?:\/\//i.test(raw) && tokenContainsSecretReference(raw)) {
850
+ return true;
851
+ }
852
+ }
853
+ return false;
854
+ }
855
+
856
+ function commandHasEgressSink(segments) {
857
+ for (const segment of segments) {
858
+ if (isOutputOnlyCommand(segment)) continue;
859
+ const tokens = executableTokens(segment);
860
+ const first = executableBase(tokens[0]);
861
+ const lowerTokens = tokens.map((token) => String(token).toLowerCase());
862
+ if (first === "curl" && curlHasEgressSink(tokens)) return true;
863
+ if (
864
+ first === "wget" &&
865
+ lowerTokens.some(
866
+ (token) =>
867
+ [
868
+ "--header",
869
+ "--password",
870
+ "--post-data",
871
+ "--post-file",
872
+ "--user",
873
+ ].some(
874
+ (option) => token === option || token.startsWith(`${option}=`),
875
+ ) || token === "--method=post",
876
+ )
877
+ ) {
878
+ return true;
879
+ }
880
+ if (
881
+ first === "wget" &&
882
+ tokens
883
+ .slice(1)
884
+ .some(
885
+ (token) =>
886
+ /^https?:\/\//i.test(token) && tokenContainsSecretReference(token),
887
+ )
888
+ ) {
889
+ return true;
890
+ }
891
+ if (
892
+ ["invoke-restmethod", "invoke-webrequest", "irm", "iwr"].includes(
893
+ first,
894
+ ) &&
895
+ lowerTokens.some(
896
+ (token, index) =>
897
+ [
898
+ "-authentication",
899
+ "-body",
900
+ "-headers",
901
+ "-infile",
902
+ "-token",
903
+ ].includes(token) ||
904
+ (token === "-method" &&
905
+ String(lowerTokens[index + 1] || "") === "post") ||
906
+ token === "-method=post",
907
+ )
908
+ ) {
909
+ return true;
910
+ }
911
+ if (["nc", "ncat", "netcat", "socat"].includes(first)) return true;
912
+ if (
913
+ ["scp", "sftp", "rsync"].includes(first) &&
914
+ tokens.slice(1).some((token) => /\b[\w.-]+@[\w.-]+:/i.test(token))
915
+ ) {
916
+ return true;
917
+ }
918
+ if (
919
+ first === "gh" &&
920
+ lowerTokens[1] === "gist" &&
921
+ lowerTokens[2] === "create"
922
+ ) {
923
+ return true;
924
+ }
925
+ if (
926
+ first === "aws" &&
927
+ lowerTokens[1] === "s3" &&
928
+ lowerTokens[2] === "cp" &&
929
+ lowerTokens.some((token) => token.startsWith("s3://"))
930
+ ) {
931
+ return true;
932
+ }
933
+ if (first === "git") {
934
+ const git = parseGitInvocation(segment);
935
+ if (
936
+ git?.subcommand === "push" &&
937
+ git.args.some(
938
+ (arg) =>
939
+ /^https?:\/\//i.test(arg) && tokenContainsSecretReference(arg),
940
+ )
941
+ ) {
942
+ return true;
943
+ }
944
+ }
945
+ }
946
+ return false;
947
+ }
948
+
949
+ function toolHasEgressSink(tool, context = {}) {
950
+ if (context.externalSideEffect === true) return true;
951
+ return /(?:webhook|send[_-]?(?:email|message|mail)|http[_-]?request|upload|post[_-]?message)/i.test(
952
+ String(tool || ""),
953
+ );
954
+ }
955
+
956
+ function isOutputOnlyCommand(segment) {
957
+ const first = executableBase(executableTokens(segment)[0]);
958
+ return [
959
+ "echo",
960
+ "findstr",
961
+ "grep",
962
+ "printf",
963
+ "rg",
964
+ "select-string",
965
+ "type",
966
+ "write-host",
967
+ "write-output",
968
+ ].includes(first);
969
+ }
970
+
971
+ function isProductionMarker(text) {
972
+ return /(?:^|[\s:/_.="'-])(?:prod|production)(?:$|[\s:/_.="'-])/i.test(
973
+ String(text || ""),
974
+ );
975
+ }
976
+
977
+ function isNonProductionMarker(text) {
978
+ return /(?:^|[\s:/_.="'-])(?:dev|development|local|preview|qa|sandbox|stage|staging|test|testing)(?:$|[\s:/_.="'-])/i.test(
979
+ String(text || ""),
980
+ );
981
+ }
982
+
983
+ function hasExplicitNonProductionTarget(tokens) {
984
+ const targetFlags = new Set([
985
+ "-a",
986
+ "-c",
987
+ "--app",
988
+ "--config",
989
+ "--env",
990
+ "--environment",
991
+ "--project",
992
+ "--stage",
993
+ "-p",
994
+ ]);
995
+ for (let i = 1; i < tokens.length; i += 1) {
996
+ const raw = String(tokens[i] || "");
997
+ const lower = raw.toLowerCase();
998
+ const equals = lower.indexOf("=");
999
+ const name = equals >= 0 ? lower.slice(0, equals) : lower;
1000
+ if (!targetFlags.has(name)) continue;
1001
+ const value = equals >= 0 ? raw.slice(equals + 1) : tokens[i + 1];
1002
+ if (isNonProductionMarker(value)) return true;
1003
+ }
1004
+ return false;
1005
+ }
1006
+
1007
+ function isProductionDeploySegment(segment) {
1008
+ if (isOutputOnlyCommand(segment)) return false;
1009
+ const raw = String(segment || "");
1010
+ const lower = raw.toLowerCase();
1011
+ const tokens = executableTokens(raw);
1012
+ const semanticTokens = tokens.map(canonicalShellKeyword);
1013
+ const semanticLower = semanticTokens.join(" ").toLowerCase();
1014
+ const first = executableBase(tokens[0]);
1015
+ const prod =
1016
+ isProductionMarker(lower) ||
1017
+ semanticTokens.some(isProductionMarker) ||
1018
+ /--prod(?:uction)?\b/i.test(semanticLower);
1019
+ if (["helm", "kubectl"].includes(first) && tokens.some(isDryRunArg)) {
1020
+ return false;
1021
+ }
1022
+
1023
+ if (
1024
+ first === "kubectl" &&
1025
+ /\b(?:apply|create|delete|patch|replace|rollout|set|scale)\b/i.test(
1026
+ semanticLower,
1027
+ )
1028
+ ) {
1029
+ return prod;
1030
+ }
1031
+ if (
1032
+ first === "helm" &&
1033
+ /\b(?:install|upgrade|uninstall|rollback)\b/i.test(semanticLower)
1034
+ ) {
1035
+ return prod;
1036
+ }
1037
+ if (["vercel", "netlify"].includes(first)) {
1038
+ return /\bdeploy\b/i.test(semanticLower)
1039
+ ? prod
1040
+ : /--prod\b/i.test(semanticLower);
1041
+ }
1042
+ if (
1043
+ first === "firebase" &&
1044
+ semanticTokens
1045
+ .slice(1)
1046
+ .some((token) => String(token).toLowerCase() === "deploy")
1047
+ ) {
1048
+ return prod || !hasExplicitNonProductionTarget(tokens);
1049
+ }
1050
+ if (first === "fly" && /\bdeploy\b/i.test(semanticLower)) {
1051
+ return prod || !hasExplicitNonProductionTarget(tokens);
1052
+ }
1053
+ if (
1054
+ first === "gcloud" &&
1055
+ /\b(?:app|run)\b[\s\S]*\bdeploy\b/i.test(semanticLower)
1056
+ ) {
1057
+ return prod;
1058
+ }
1059
+ if (
1060
+ ["npm", "pnpm", "yarn", "bun"].includes(first) &&
1061
+ semanticTokens
1062
+ .slice(1)
1063
+ .some((token) => /^deploy(?::|-)?(?:prod|production)?$/i.test(token)) &&
1064
+ prod
1065
+ ) {
1066
+ return true;
1067
+ }
1068
+ if (/(?:^|[\\/])deploy\.(?:ps1|sh|cmd|bat)\b/i.test(lower)) {
1069
+ return prod;
1070
+ }
1071
+ return /\bdeploy\b/i.test(first) && prod;
1072
+ }
1073
+
1074
+ function valueContainsProductionMarker(value, depth = 0, seen = new WeakSet()) {
1075
+ if (typeof value === "string") return isProductionMarker(value);
1076
+ if (!value || typeof value !== "object" || depth > 4) return false;
1077
+ if (seen.has(value)) return false;
1078
+ seen.add(value);
1079
+ const values = Array.isArray(value) ? value : Object.values(value);
1080
+ const found = values.some((item) =>
1081
+ valueContainsProductionMarker(item, depth + 1, seen),
1082
+ );
1083
+ seen.delete(value);
1084
+ return found;
1085
+ }
1086
+
1087
+ function isStructuredProductionDeployment(tool, args, context = {}) {
1088
+ if (typeof args?.command === "string" || Array.isArray(args?.argv)) {
1089
+ return false;
1090
+ }
1091
+ if (args.dryRun === true || args.dry_run === true) return false;
1092
+ const key = String(tool || "").toLowerCase();
1093
+ const leaf = toolLeaf(key);
1094
+ const providerSurface =
1095
+ /(?:kubernetes|k8s|helm|firebase|vercel|netlify|gcloud|cloud[_-]?run|fly)/i.test(
1096
+ key,
1097
+ );
1098
+ const deploymentSurface =
1099
+ context.actionType === "deployment" ||
1100
+ /deploy/.test(leaf) ||
1101
+ (providerSurface &&
1102
+ /(?:apply|create|delete|install|patch|replace|rollout|scale|set|upgrade)/.test(
1103
+ leaf,
1104
+ ));
1105
+ const productionTarget =
1106
+ valueContainsProductionMarker(args) ||
1107
+ isProductionMarker(context.targetEnvironment) ||
1108
+ isProductionMarker(context.environment);
1109
+ return deploymentSurface && productionTarget;
1110
+ }
1111
+
1112
+ function parseGitInvocation(segment) {
1113
+ const tokens = executableTokens(segment).map(canonicalShellKeyword);
1114
+ if (executableBase(tokens[0]) !== "git") return null;
1115
+ let i = 1;
1116
+ while (i < tokens.length) {
1117
+ const token = String(tokens[i] || "");
1118
+ const lower = token.toLowerCase();
1119
+ if (["-c", "--config-env", "--git-dir", "--work-tree"].includes(lower)) {
1120
+ i += 2;
1121
+ continue;
1122
+ }
1123
+ if (
1124
+ lower.startsWith("--git-dir=") ||
1125
+ lower.startsWith("--work-tree=") ||
1126
+ lower.startsWith("-c=")
1127
+ ) {
1128
+ i += 1;
1129
+ continue;
1130
+ }
1131
+ if (token.startsWith("-")) {
1132
+ i += 1;
1133
+ continue;
1134
+ }
1135
+ break;
1136
+ }
1137
+ if (i >= tokens.length) return null;
1138
+ return {
1139
+ subcommand: String(tokens[i]).toLowerCase(),
1140
+ args: tokens.slice(i + 1).map(String),
1141
+ };
1142
+ }
1143
+
1144
+ function isDryRunArg(arg) {
1145
+ const lower = String(arg || "").toLowerCase();
1146
+ return (
1147
+ lower === "--dry-run" || /^--dry-run=(?:client|server|true)$/.test(lower)
1148
+ );
1149
+ }
1150
+
1151
+ function hasUnboundedForceWithLease(args) {
1152
+ const leasePrefix = "--force-with-lease";
1153
+ for (let i = 0; i < args.length; i += 1) {
1154
+ const raw = String(args[i] || "");
1155
+ const lower = raw.toLowerCase();
1156
+ if (lower === leasePrefix) {
1157
+ const next = String(args[i + 1] || "");
1158
+ if (!/^[^:\s]+:[0-9a-f]{40}(?:[0-9a-f]{24})?$/i.test(next)) {
1159
+ return true;
1160
+ }
1161
+ i += 1;
1162
+ continue;
1163
+ }
1164
+ if (lower.startsWith(`${leasePrefix}=`)) {
1165
+ const lease = raw.slice(leasePrefix.length + 1);
1166
+ if (!/^[^:\s]+:[0-9a-f]{40}(?:[0-9a-f]{24})?$/i.test(lease)) {
1167
+ return true;
1168
+ }
1169
+ }
1170
+ }
1171
+ return false;
1172
+ }
1173
+
1174
+ function isForcePushSegment(segment) {
1175
+ if (isOutputOnlyCommand(segment)) return false;
1176
+ const git = parseGitInvocation(segment);
1177
+ if (!git || git.subcommand !== "push") return false;
1178
+ const lowerArgs = git.args.map((arg) => arg.toLowerCase());
1179
+ if (lowerArgs.some(isDryRunArg)) return false;
1180
+ if (
1181
+ lowerArgs.includes("--force") ||
1182
+ lowerArgs.includes("--mirror") ||
1183
+ lowerArgs.some((arg) => /^-[^-]*f/.test(arg))
1184
+ ) {
1185
+ return true;
1186
+ }
1187
+ if (hasUnboundedForceWithLease(git.args)) return true;
1188
+ return git.args.some((arg) => /^\+.+/.test(arg));
1189
+ }
1190
+
1191
+ function protectedRefName(ref) {
1192
+ const raw = String(ref || "").replace(/^\+/, "");
1193
+ const colon = raw.lastIndexOf(":");
1194
+ const destination = colon >= 0 ? raw.slice(colon + 1) : raw;
1195
+ return destination.replace(/^refs\/heads\//i, "");
1196
+ }
1197
+
1198
+ function isProtectedPushSegment(segment) {
1199
+ const git = parseGitInvocation(segment);
1200
+ if (!git || git.subcommand !== "push") return false;
1201
+ if (git.args.some(isDryRunArg)) return false;
1202
+ return git.args.some((arg) =>
1203
+ /^(?:main|master|develop|production|prod)$/i.test(protectedRefName(arg)),
1204
+ );
1205
+ }
1206
+
1207
+ function mergeHasTrustedReview(context = {}) {
1208
+ const reviewedCommitSha = String(context.reviewedCommitSha || "")
1209
+ .trim()
1210
+ .toLowerCase();
1211
+ const headCommitSha = String(context.headCommitSha || "")
1212
+ .trim()
1213
+ .toLowerCase();
1214
+ return (
1215
+ context.reviewApproved === true &&
1216
+ context.requiredChecksPassed === true &&
1217
+ Object.hasOwn(context, "pendingApprovals") &&
1218
+ Number.isInteger(context.pendingApprovals) &&
1219
+ context.pendingApprovals === 0 &&
1220
+ /^[0-9a-f]{40}(?:[0-9a-f]{24})?$/.test(reviewedCommitSha) &&
1221
+ reviewedCommitSha === headCommitSha
1222
+ );
1223
+ }
1224
+
1225
+ function hasHelpArg(tokens) {
1226
+ for (const token of tokens.slice(1)) {
1227
+ const raw = String(token || "");
1228
+ if (raw === "--") return false;
1229
+ if (raw === "-h" || ["--help", "-help", "/?"].includes(raw.toLowerCase())) {
1230
+ return true;
1231
+ }
1232
+ }
1233
+ return false;
1234
+ }
1235
+
1236
+ function hasVersionArg(tokens) {
1237
+ for (const token of tokens.slice(1)) {
1238
+ const raw = String(token || "");
1239
+ if (raw === "--") return false;
1240
+ if (["--version", "-version"].includes(raw.toLowerCase())) return true;
1241
+ }
1242
+ return false;
1243
+ }
1244
+
1245
+ function ghCommandArgs(tokens) {
1246
+ const args = tokens.slice(1);
1247
+ const optionsWithValues = new Set(["-r", "--hostname", "--repo"]);
1248
+ let index = 0;
1249
+ while (index < args.length) {
1250
+ const raw = String(args[index] || "");
1251
+ const lower = raw.toLowerCase();
1252
+ if (raw === "--") return args.slice(index + 1);
1253
+ if (!raw.startsWith("-")) break;
1254
+ const name = lower.split("=", 1)[0];
1255
+ if (optionsWithValues.has(name) && !raw.includes("=")) {
1256
+ index += 2;
1257
+ } else {
1258
+ index += 1;
1259
+ }
1260
+ }
1261
+ return args.slice(index);
1262
+ }
1263
+
1264
+ function isGhApiMerge(args) {
1265
+ if (String(args[0] || "").toLowerCase() !== "api") return false;
1266
+ const optionsWithValues = new Set([
1267
+ "--cache",
1268
+ "-f",
1269
+ "-h",
1270
+ "-p",
1271
+ "-q",
1272
+ "-t",
1273
+ "--field",
1274
+ "--header",
1275
+ "--hostname",
1276
+ "--input",
1277
+ "--jq",
1278
+ "--preview",
1279
+ "--raw-field",
1280
+ "--template",
1281
+ ]);
1282
+ const shortOptionsWithValues = [...optionsWithValues].filter(
1283
+ (option) => option.length === 2,
1284
+ );
1285
+ const mergeEndpointPattern = /(?:^|\/)pulls\/[^/]+\/merge(?:$|[?#])/i;
1286
+ let method = "get";
1287
+ let mergeEndpoint = false;
1288
+ let positionalOnly = false;
1289
+ for (let index = 1; index < args.length; index += 1) {
1290
+ const raw = String(args[index] || "");
1291
+ const lower = raw.toLowerCase();
1292
+ if (!positionalOnly && raw === "--") {
1293
+ positionalOnly = true;
1294
+ continue;
1295
+ }
1296
+ if (!positionalOnly && ["-x", "--method"].includes(lower)) {
1297
+ method = String(args[index + 1] || "").toLowerCase();
1298
+ index += 1;
1299
+ continue;
1300
+ }
1301
+ if (!positionalOnly && lower.startsWith("--method=")) {
1302
+ method = lower.slice("--method=".length);
1303
+ continue;
1304
+ }
1305
+ if (!positionalOnly && lower.startsWith("-x") && lower.length > 2) {
1306
+ method = lower.slice(2);
1307
+ continue;
1308
+ }
1309
+ const equals = lower.indexOf("=");
1310
+ const optionName = equals >= 0 ? lower.slice(0, equals) : lower;
1311
+ if (!positionalOnly && optionsWithValues.has(optionName)) {
1312
+ if (equals < 0) index += 1;
1313
+ continue;
1314
+ }
1315
+ if (
1316
+ !positionalOnly &&
1317
+ shortOptionsWithValues.some(
1318
+ (option) => lower.startsWith(option) && lower.length > option.length,
1319
+ )
1320
+ ) {
1321
+ continue;
1322
+ }
1323
+ if (!positionalOnly && raw.startsWith("-")) {
1324
+ continue;
1325
+ }
1326
+ if (mergeEndpointPattern.test(raw)) {
1327
+ mergeEndpoint = true;
1328
+ }
1329
+ }
1330
+ return method === "put" && mergeEndpoint;
1331
+ }
1332
+
1333
+ function isRemoteMergeSegment(segment) {
1334
+ if (isOutputOnlyCommand(segment)) return false;
1335
+ const tokens = executableTokens(segment).map(canonicalShellKeyword);
1336
+ if (hasHelpArg(tokens)) return false;
1337
+ const first = executableBase(tokens[0]);
1338
+ const args = first === "gh" ? ghCommandArgs(tokens) : tokens.slice(1);
1339
+ const lower = args.map((token) => String(token).toLowerCase());
1340
+ if (first === "gh") {
1341
+ return (lower[0] === "pr" && lower[1] === "merge") || isGhApiMerge(args);
1342
+ }
1343
+ if (first === "glab") {
1344
+ return lower[0] === "mr" && lower[1] === "merge";
1345
+ }
1346
+ if (first === "az") {
1347
+ const status = lower.findIndex((token) => token === "--status");
1348
+ return (
1349
+ lower[0] === "repos" &&
1350
+ lower[1] === "pr" &&
1351
+ lower[2] === "update" &&
1352
+ status >= 0 &&
1353
+ ["completed", "merged"].includes(lower[status + 1])
1354
+ );
1355
+ }
1356
+ return false;
1357
+ }
1358
+
1359
+ function isStructuredMergeTool(tool, args = {}) {
1360
+ if (
1361
+ args.preview === true ||
1362
+ args.dryRun === true ||
1363
+ args.dry_run === true ||
1364
+ args.readOnly === true
1365
+ ) {
1366
+ return false;
1367
+ }
1368
+ const key = String(tool || "").toLowerCase();
1369
+ const words = key.split(/__|[_:.\-/]/).filter(Boolean);
1370
+ if (
1371
+ words.some((word) =>
1372
+ ["check", "get", "inspect", "list", "preview", "read", "status"].includes(
1373
+ word,
1374
+ ),
1375
+ )
1376
+ ) {
1377
+ return false;
1378
+ }
1379
+ return /(?:^|[_:.])merge[_-]?(?:pull[_-]?request|pr|merge[_-]?request|mr)(?:$|[_:.])/i.test(
1380
+ key,
1381
+ );
1382
+ }
1383
+
1384
+ function isRemoteMergeCommand(command, tool, args, context) {
1385
+ if (mergeHasTrustedReview(context)) return false;
1386
+ if (isStructuredMergeTool(tool, args)) return true;
1387
+ const segments = collectCommandSegments(command);
1388
+ if (segments.some(isRemoteMergeSegment)) return true;
1389
+ const hasLocalMerge = segments.some((segment) => {
1390
+ const git = parseGitInvocation(segment);
1391
+ return git?.subcommand === "merge";
1392
+ });
1393
+ return hasLocalMerge && segments.some(isProtectedPushSegment);
1394
+ }
1395
+
1396
+ function isStructuredForcePush(tool, args = {}) {
1397
+ if (!/(?:^|[_:.])push(?:[_:.]|$)/i.test(String(tool || ""))) return false;
1398
+ if (args.dryRun === true || args.dry_run === true) return false;
1399
+ if (args.force === true || args.mirror === true) return true;
1400
+ const refspecs = [
1401
+ args.ref,
1402
+ args.refspec,
1403
+ ...(Array.isArray(args.refspecs) ? args.refspecs : []),
1404
+ ];
1405
+ if (refspecs.some((ref) => /^\+.+/.test(String(ref || "")))) return true;
1406
+ const lease = args.forceWithLease ?? args.force_with_lease;
1407
+ if (lease == null || lease === false) return false;
1408
+ return !/^[^:\s]+:[0-9a-f]{40}(?:[0-9a-f]{24})?$/i.test(String(lease));
1409
+ }
1410
+
1411
+ function isAgentInvocation(segment) {
1412
+ if (isOutputOnlyCommand(segment)) return false;
1413
+ const tokens = executableTokens(segment);
1414
+ const first = executableBase(tokens[0]);
1415
+ if (AGENT_BINARIES.has(first)) return true;
1416
+ if (["npx", "pnpx", "bunx", "uvx"].includes(first)) {
1417
+ return tokens.slice(1).some((token) => {
1418
+ const base = executableBase(token);
1419
+ return (
1420
+ AGENT_BINARIES.has(base) ||
1421
+ /(?:claude-code|openai-codex|aider-chat)/i.test(String(token))
1422
+ );
1423
+ });
1424
+ }
1425
+ return false;
1426
+ }
1427
+
1428
+ function isUnsafeAgentCommand(segment) {
1429
+ if (!isAgentInvocation(segment)) return false;
1430
+ const tokens = executableTokens(segment);
1431
+ if (hasHelpArg(tokens) || hasVersionArg(tokens)) return false;
1432
+ const raw = tokens.map(canonicalShellKeyword).join(" ");
1433
+ const lowerTokens = tokens.map((token) =>
1434
+ canonicalShellKeyword(token).toLowerCase(),
1435
+ );
1436
+ const approvalIndex = lowerTokens.findIndex((token) => token === "-a");
1437
+ const sandboxIndex = lowerTokens.findIndex((token) => token === "-s");
1438
+ const unsafeAliases =
1439
+ approvalIndex >= 0 &&
1440
+ lowerTokens[approvalIndex + 1] === "never" &&
1441
+ sandboxIndex >= 0 &&
1442
+ ["danger-full-access", "host", "none"].includes(
1443
+ lowerTokens[sandboxIndex + 1],
1444
+ );
1445
+ return (
1446
+ unsafeAliases ||
1447
+ /--dangerously-(?:skip-permissions|bypass-approvals-and-sandbox)\b/i.test(
1448
+ raw,
1449
+ ) ||
1450
+ /--sandbox(?:=|\s+)danger-full-access\b/i.test(raw) ||
1451
+ /--yolo\b/i.test(raw) ||
1452
+ /--yes-always\b/i.test(raw) ||
1453
+ (/--ask-for-approval(?:=|\s+)never\b/i.test(raw) &&
1454
+ /--sandbox(?:=|\s+)(?:danger-full-access|none|host)\b/i.test(raw))
1455
+ );
1456
+ }
1457
+
1458
+ function isStructuredUnisolatedAgent(tool, context = {}, args = {}) {
1459
+ const agentOrigin = String(context.agentOrigin || "")
1460
+ .toLowerCase()
1461
+ .replace(/_/g, "-");
1462
+ const thirdParty =
1463
+ context.thirdParty === true ||
1464
+ agentOrigin === "third-party" ||
1465
+ args.thirdParty === true ||
1466
+ ["external", "third-party", "third_party"].includes(
1467
+ String(args.provider || "").toLowerCase(),
1468
+ );
1469
+ const agentSurface =
1470
+ /(?:agent|delegate|subagent|sub_agent)/i.test(String(tool || "")) ||
1471
+ context.actionType === "agent";
1472
+ if (!thirdParty || !agentSurface) return false;
1473
+
1474
+ const isolation = String(context.isolation || args.isolation || "")
1475
+ .trim()
1476
+ .toLowerCase();
1477
+ const explicitlyHost =
1478
+ context.sandboxed === false ||
1479
+ args.sandboxed === false ||
1480
+ ["host", "none", "unisolated"].includes(isolation);
1481
+ const hasBoundaryEvidence =
1482
+ (context.processSandboxed === true || args.processSandboxed === true) &&
1483
+ (context.networkIsolated === true || args.networkIsolated === true) &&
1484
+ (context.credentialsIsolated === true ||
1485
+ args.credentialsIsolated === true) &&
1486
+ (context.approvalsRequired === true || args.approvalsRequired === true);
1487
+ return explicitlyHost || !hasBoundaryEvidence;
1488
+ }
1489
+
1490
+ function isDestructiveSegment(segment) {
1491
+ if (isOutputOnlyCommand(segment)) return false;
1492
+ const tokens = executableTokens(segment);
1493
+ const first = executableBase(tokens[0]);
1494
+ if (first === "find") {
1495
+ return (
1496
+ !["--help", "-help"].includes(String(tokens[1] || "").toLowerCase()) &&
1497
+ tokens.slice(1).includes("-delete")
1498
+ );
1499
+ }
1500
+ if (hasHelpArg(tokens)) return false;
1501
+ if (DESTRUCTIVE_COMMANDS.has(first) || first.startsWith("mkfs.")) {
1502
+ return true;
1503
+ }
1504
+ return (
1505
+ first === "dd" &&
1506
+ tokens.some((token) => /\bof=(?:\/dev\/|\\\\\.\\)/i.test(token))
1507
+ );
1508
+ }
1509
+
1510
+ function isInfrastructureDestructive(segment) {
1511
+ if (isOutputOnlyCommand(segment)) return false;
1512
+ const tokens = executableTokens(segment);
1513
+ if (hasHelpArg(tokens)) return false;
1514
+ const first = executableBase(tokens[0]);
1515
+ const lowerTokens = tokens.map((token) =>
1516
+ canonicalShellKeyword(token).toLowerCase(),
1517
+ );
1518
+ if (["terraform", "terragrunt"].includes(first)) {
1519
+ let operation = "";
1520
+ for (let i = 1; i < lowerTokens.length; i += 1) {
1521
+ const token = lowerTokens[i];
1522
+ if (token === "-chdir" && i + 1 < lowerTokens.length) {
1523
+ i += 1;
1524
+ continue;
1525
+ }
1526
+ if (token.startsWith("-")) continue;
1527
+ operation = token;
1528
+ break;
1529
+ }
1530
+ if (operation === "destroy") return true;
1531
+ if (
1532
+ operation === "apply" &&
1533
+ lowerTokens.some((token) => ["-destroy", "--destroy"].includes(token))
1534
+ ) {
1535
+ return true;
1536
+ }
1537
+ if (
1538
+ first === "terragrunt" &&
1539
+ ["run-all", "run"].includes(operation) &&
1540
+ lowerTokens.includes("destroy")
1541
+ ) {
1542
+ return true;
1543
+ }
1544
+ }
1545
+ if (
1546
+ ["pulumi", "cdk", "cdklocal"].includes(first) &&
1547
+ lowerTokens.slice(1).includes("destroy")
1548
+ ) {
1549
+ return true;
1550
+ }
1551
+ return (
1552
+ first === "kubectl" &&
1553
+ lowerTokens.some(
1554
+ (token, index) =>
1555
+ token === "delete" &&
1556
+ ["clusterrole", "crd", "namespace", "ns"].includes(
1557
+ lowerTokens[index + 1],
1558
+ ),
1559
+ )
1560
+ );
1561
+ }
1562
+
1563
+ function isPowerShellEncoded(segment) {
1564
+ const tokens = executableTokens(segment);
1565
+ const first = executableBase(tokens[0]);
1566
+ return (
1567
+ ["powershell", "pwsh"].includes(first) &&
1568
+ tokens.some((token) =>
1569
+ [
1570
+ "-e",
1571
+ "-ec",
1572
+ "-en",
1573
+ "-enc",
1574
+ "-enco",
1575
+ "-encod",
1576
+ "-encode",
1577
+ "-encoded",
1578
+ "-encodedcommand",
1579
+ ].includes(String(token).toLowerCase()),
1580
+ )
1581
+ );
1582
+ }
1583
+
1584
+ function mayContainRemoteExec(command) {
1585
+ const raw = String(command || "");
1586
+ if (
1587
+ !raw.includes("|") &&
1588
+ !raw.includes("$(") &&
1589
+ !raw.includes("<(") &&
1590
+ !/\b(?:iex|invoke-expression)\s*\(/i.test(raw)
1591
+ ) {
1592
+ return false;
1593
+ }
1594
+ return (
1595
+ /\b(?:curl|fetch|http|httpie|invoke-restmethod|invoke-webrequest|irm|iwr|wget)\b/i.test(
1596
+ raw,
1597
+ ) &&
1598
+ /\b(?:bash|dash|deno|eval|fish|iex|invoke-expression|ksh|node|perl|powershell|pwsh|python[0-9.]*|ruby|sh|source|zsh)\b/i.test(
1599
+ raw,
1600
+ )
1601
+ );
1602
+ }
1603
+
1604
+ function splitCommandPipelines(command) {
1605
+ const raw = String(command || "");
1606
+ const pipelines = [];
1607
+ let pipeline = [];
1608
+ let start = 0;
1609
+ let quote = "";
1610
+ let escaping = false;
1611
+
1612
+ const pushStage = (end) => {
1613
+ const stage = raw.slice(start, end).trim();
1614
+ if (stage) pipeline.push(stage);
1615
+ };
1616
+ const pushPipeline = () => {
1617
+ if (pipeline.length > 1) pipelines.push(pipeline);
1618
+ pipeline = [];
1619
+ };
1620
+
1621
+ for (let index = 0; index < raw.length; index += 1) {
1622
+ const ch = raw[index];
1623
+ if (escaping) {
1624
+ escaping = false;
1625
+ continue;
1626
+ }
1627
+ if (ch === "\\" && quote !== "'") {
1628
+ escaping = true;
1629
+ continue;
1630
+ }
1631
+ if (quote) {
1632
+ if (ch === quote) quote = "";
1633
+ continue;
1634
+ }
1635
+ if (ch === "'" || ch === '"') {
1636
+ quote = ch;
1637
+ continue;
1638
+ }
1639
+ const doubled = index + 1 < raw.length && raw[index + 1] === ch;
1640
+ if (ch === "|" && !doubled && raw[index - 1] !== "|") {
1641
+ pushStage(index);
1642
+ start = index + 1;
1643
+ continue;
1644
+ }
1645
+ if (
1646
+ ch === "\r" ||
1647
+ ch === "\n" ||
1648
+ ch === ";" ||
1649
+ ch === "&" ||
1650
+ (ch === "|" && doubled)
1651
+ ) {
1652
+ pushStage(index);
1653
+ pushPipeline();
1654
+ if (doubled) index += 1;
1655
+ start = index + 1;
1656
+ }
1657
+ }
1658
+ pushStage(raw.length);
1659
+ pushPipeline();
1660
+ return pipelines;
1661
+ }
1662
+
1663
+ function isRemoteFetcherStage(stage) {
1664
+ return [
1665
+ "curl",
1666
+ "fetch",
1667
+ "http",
1668
+ "httpie",
1669
+ "invoke-restmethod",
1670
+ "invoke-webrequest",
1671
+ "irm",
1672
+ "iwr",
1673
+ "wget",
1674
+ ].includes(executableBase(executableTokens(stage)[0]));
1675
+ }
1676
+
1677
+ function isDecodePassThroughStage(stage) {
1678
+ const tokens = executableTokens(stage);
1679
+ const first = executableBase(tokens[0]);
1680
+ if (["cat", "tee", "tr"].includes(first)) return true;
1681
+ if (
1682
+ first === "gzip" &&
1683
+ tokens
1684
+ .slice(1)
1685
+ .some((token) =>
1686
+ ["-d", "--decompress"].includes(String(token).toLowerCase()),
1687
+ )
1688
+ ) {
1689
+ return true;
1690
+ }
1691
+ return (
1692
+ first === "base64" &&
1693
+ tokens
1694
+ .slice(1)
1695
+ .some((token) => ["-d", "--decode"].includes(String(token).toLowerCase()))
1696
+ );
1697
+ }
1698
+
1699
+ function isExecutingInterpreterStage(stage) {
1700
+ const tokens = executableTokens(stage);
1701
+ const first = executableBase(tokens[0]);
1702
+ if (["iex", "invoke-expression"].includes(first)) return true;
1703
+ if (["bash", "dash", "fish", "ksh", "sh", "zsh"].includes(first)) {
1704
+ return !tokens
1705
+ .slice(1)
1706
+ .some((token) =>
1707
+ ["-n", "--noexec"].includes(String(token).toLowerCase()),
1708
+ );
1709
+ }
1710
+ return [
1711
+ "deno",
1712
+ "eval",
1713
+ "node",
1714
+ "perl",
1715
+ "powershell",
1716
+ "pwsh",
1717
+ "python",
1718
+ "python3",
1719
+ "ruby",
1720
+ "source",
1721
+ ].includes(first);
1722
+ }
1723
+
1724
+ function commandBodies(command, depth = 0) {
1725
+ const raw = String(command || "");
1726
+ const bodies = [raw];
1727
+ if (depth >= 3) return bodies;
1728
+ for (const segment of splitSafetyCommandSegments(raw)) {
1729
+ const inner = unwrapShellCommand(segment);
1730
+ if (inner && inner !== segment) {
1731
+ bodies.push(...commandBodies(inner, depth + 1));
1732
+ }
1733
+ }
1734
+ return [...new Set(bodies)];
1735
+ }
1736
+
1737
+ function isRemoteExec(command, segments) {
1738
+ if (segments.length === 1 && isOutputOnlyCommand(segments[0])) {
1739
+ return false;
1740
+ }
1741
+ for (const body of commandBodies(command)) {
1742
+ if (!mayContainRemoteExec(body)) continue;
1743
+ if (
1744
+ /\b(?:iex|invoke-expression)\s*\(\s*(?:invoke-restmethod|invoke-webrequest|irm|iwr)\b/i.test(
1745
+ body,
1746
+ )
1747
+ ) {
1748
+ return true;
1749
+ }
1750
+ for (const pipeline of splitCommandPipelines(body)) {
1751
+ const fetcher = pipeline.findIndex(isRemoteFetcherStage);
1752
+ if (fetcher < 0) continue;
1753
+ let index = fetcher + 1;
1754
+ while (
1755
+ index < pipeline.length &&
1756
+ isDecodePassThroughStage(pipeline[index])
1757
+ ) {
1758
+ index += 1;
1759
+ }
1760
+ if (
1761
+ index < pipeline.length &&
1762
+ isExecutingInterpreterStage(pipeline[index])
1763
+ ) {
1764
+ return true;
1765
+ }
1766
+ }
1767
+ if (
1768
+ (body.includes("$(") || body.includes("<(")) &&
1769
+ classifyRemoteExecCommand(body).isRemoteExec
1770
+ ) {
1771
+ return true;
1772
+ }
1773
+ }
1774
+ return false;
1775
+ }
1776
+
1777
+ function isPublicationSegment(segment) {
1778
+ if (isOutputOnlyCommand(segment)) return false;
1779
+ const tokens = executableTokens(segment);
1780
+ if (tokens.some(isDryRunArg) || hasHelpArg(tokens)) return false;
1781
+ const first = executableBase(tokens[0]);
1782
+ const second = canonicalShellKeyword(tokens[1]).toLowerCase();
1783
+ const third = canonicalShellKeyword(tokens[2]).toLowerCase();
1784
+ return (
1785
+ (["npm", "pnpm", "yarn", "bun", "cargo"].includes(first) &&
1786
+ second === "publish") ||
1787
+ (first === "yarn" && second === "npm" && third === "publish") ||
1788
+ (/^(?:py|python[0-9.]*)$/.test(first) &&
1789
+ second === "-m" &&
1790
+ third === "twine" &&
1791
+ canonicalShellKeyword(tokens[3]).toLowerCase() === "upload") ||
1792
+ (first === "twine" && second === "upload") ||
1793
+ (first === "docker" && second === "push") ||
1794
+ (first === "gh" && second === "release" && third === "create")
1795
+ );
1796
+ }
1797
+
1798
+ function makeSignal(reasonCode, category, severity, reason) {
1799
+ return Object.freeze({
1800
+ reasonCode,
1801
+ category,
1802
+ riskLevel: "high",
1803
+ severity,
1804
+ reason,
1805
+ });
1806
+ }
1807
+
1808
+ /**
1809
+ * Classify one proposed tool action. The result is pure and log-safe.
1810
+ *
1811
+ * @param {{tool?:string,args?:object,baseRiskLevel?:string,context?:object}} input
1812
+ * @returns {object}
1813
+ */
1814
+ export function classifyAutoModeSafety(input = {}) {
1815
+ const tool = String(input.tool || "unknown").trim() || "unknown";
1816
+ const toolKey = tool.toLowerCase();
1817
+ const args = input.args && typeof input.args === "object" ? input.args : {};
1818
+ const context =
1819
+ input.context && typeof input.context === "object" ? input.context : {};
1820
+ const command = inputCommand({ args });
1821
+ const segments = collectCommandSegments(command);
1822
+ const argsText = stableSerialize(args);
1823
+ const baseRiskLevel = normalizeRisk(input.baseRiskLevel, "medium");
1824
+ const signals = [];
1825
+ const seenCodes = new Set();
1826
+
1827
+ const add = (reasonCode, category, severity, reason) => {
1828
+ if (seenCodes.has(reasonCode)) return;
1829
+ seenCodes.add(reasonCode);
1830
+ signals.push(makeSignal(reasonCode, category, severity, reason));
1831
+ };
1832
+
1833
+ const roots = workspaceRoots(context);
1834
+ if (isMutatingPathTool(toolKey) && roots.length) {
1835
+ const pathTargets = collectPathArgs(args);
1836
+ if (toolLeaf(toolKey) === "apply_patch") {
1837
+ pathTargets.push(...collectPatchTargets(args));
1838
+ }
1839
+ const outside = pathTargets.some(
1840
+ (target) => !isPathWithinAnyRoot(target, roots, context),
1841
+ );
1842
+ if (outside) {
1843
+ add(
1844
+ "workspace.scope_escape",
1845
+ SAFETY_CATEGORY.WORKSPACE_SCOPE_ESCAPE,
1846
+ "critical",
1847
+ "A mutating file tool targets a path outside the declared workspace roots.",
1848
+ );
1849
+ }
1850
+ }
1851
+ if (toolKey === "run_shell" && roots.length) {
1852
+ const outside = shellWriteTargets(command).some(
1853
+ (target) => !isPathWithinAnyRoot(target, roots, context),
1854
+ );
1855
+ if (outside) {
1856
+ add(
1857
+ "workspace.scope_escape.shell_write",
1858
+ SAFETY_CATEGORY.WORKSPACE_SCOPE_ESCAPE,
1859
+ "critical",
1860
+ "A shell write target resolves outside the declared workspace roots.",
1861
+ );
1862
+ }
1863
+ }
1864
+
1865
+ const credentialRead =
1866
+ Boolean(commandReadsCredentials(command)) ||
1867
+ segments.some((segment) => Boolean(commandReadsCredentials(segment))) ||
1868
+ hasSensitivePath(command) ||
1869
+ referencesSecretEnvironment(command) ||
1870
+ collectPathArgs(args).some((target) => credentialFileReason(target)) ||
1871
+ containsSecret(argsText);
1872
+ const egress =
1873
+ commandHasEgressSink(segments) || toolHasEgressSink(tool, context);
1874
+ if (credentialRead && egress) {
1875
+ add(
1876
+ "secret.egress",
1877
+ SAFETY_CATEGORY.SECRET_EGRESS,
1878
+ "critical",
1879
+ "A credential source or secret value is combined with an external egress sink.",
1880
+ );
1881
+ }
1882
+
1883
+ if (
1884
+ segments.some(isProductionDeploySegment) ||
1885
+ isStructuredProductionDeployment(tool, args, context)
1886
+ ) {
1887
+ add(
1888
+ "deployment.production",
1889
+ SAFETY_CATEGORY.PRODUCTION_DEPLOYMENT,
1890
+ "critical",
1891
+ "The action deploys or mutates resources identified as production.",
1892
+ );
1893
+ }
1894
+
1895
+ if (segments.some(isForcePushSegment) || isStructuredForcePush(tool, args)) {
1896
+ add(
1897
+ "git.force_push",
1898
+ SAFETY_CATEGORY.GIT_FORCE_PUSH,
1899
+ "high",
1900
+ "A force push can overwrite shared remote history.",
1901
+ );
1902
+ }
1903
+
1904
+ if (isRemoteMergeCommand(command, tool, args, context)) {
1905
+ add(
1906
+ "merge.unreviewed",
1907
+ SAFETY_CATEGORY.UNREVIEWED_MERGE,
1908
+ "critical",
1909
+ "A remote merge lacks trusted proof of review, checks and settled approvals.",
1910
+ );
1911
+ }
1912
+
1913
+ if (
1914
+ segments.some(isUnsafeAgentCommand) ||
1915
+ isStructuredUnisolatedAgent(tool, context, args)
1916
+ ) {
1917
+ add(
1918
+ "agent.third_party_unisolated",
1919
+ SAFETY_CATEGORY.THIRD_PARTY_AGENT_UNISOLATED,
1920
+ "critical",
1921
+ "A third-party agent is configured to run without effective isolation or approval checks.",
1922
+ );
1923
+ }
1924
+
1925
+ if (segments.some(isDestructiveSegment)) {
1926
+ add(
1927
+ "filesystem.destructive",
1928
+ SAFETY_CATEGORY.FILESYSTEM_DESTRUCTIVE,
1929
+ "critical",
1930
+ "The command performs destructive filesystem or disk operations.",
1931
+ );
1932
+ }
1933
+
1934
+ if (segments.some(isInfrastructureDestructive)) {
1935
+ add(
1936
+ "infrastructure.destructive",
1937
+ SAFETY_CATEGORY.INFRASTRUCTURE_DESTRUCTIVE,
1938
+ "critical",
1939
+ "The command destroys or irreversibly mutates shared infrastructure.",
1940
+ );
1941
+ }
1942
+
1943
+ if (command && isRemoteExec(command, segments)) {
1944
+ add(
1945
+ "code.remote_exec",
1946
+ SAFETY_CATEGORY.REMOTE_CODE_EXECUTION,
1947
+ "critical",
1948
+ "Remote content is piped or substituted directly into an interpreter.",
1949
+ );
1950
+ }
1951
+
1952
+ if (segments.some(isPowerShellEncoded)) {
1953
+ add(
1954
+ "shell.encoded_execution",
1955
+ SAFETY_CATEGORY.SHELL_ENCODED_EXECUTION,
1956
+ "high",
1957
+ "Encoded shell execution obscures the command that would run.",
1958
+ );
1959
+ }
1960
+
1961
+ if (segments.some(isPublicationSegment)) {
1962
+ add(
1963
+ "artifact.publication",
1964
+ SAFETY_CATEGORY.PUBLICATION,
1965
+ "high",
1966
+ "The command publishes an artifact or release to a shared registry.",
1967
+ );
1968
+ }
1969
+
1970
+ let riskLevel = baseRiskLevel;
1971
+ let severity = "none";
1972
+ for (const signal of signals) {
1973
+ riskLevel = maxRisk(riskLevel, signal.riskLevel);
1974
+ severity = maxSeverity(severity, signal.severity);
1975
+ }
1976
+
1977
+ const categories = [...new Set(signals.map((signal) => signal.category))];
1978
+ return Object.freeze({
1979
+ schema: AUTO_MODE_SAFETY_CLASSIFICATION_SCHEMA,
1980
+ classifierVersion: AUTO_MODE_SAFETY_CLASSIFIER_VERSION,
1981
+ baseRiskLevel,
1982
+ riskLevel,
1983
+ severity,
1984
+ dangerous: signals.length > 0,
1985
+ escalated: RISK_RANK[riskLevel] > RISK_RANK[baseRiskLevel],
1986
+ categories: Object.freeze(categories),
1987
+ reasonCodes: Object.freeze(signals.map((signal) => signal.reasonCode)),
1988
+ signals: Object.freeze(signals),
1989
+ });
1990
+ }
1991
+
1992
+ /**
1993
+ * Compose the classifier with the existing shell hard-policy and the default
1994
+ * Auto-mode risk mapping. The layers stay separate in the returned verdict.
1995
+ */
1996
+ export function evaluateAutoModeSafety(input = {}, opts = {}) {
1997
+ const classifier =
1998
+ typeof opts.classifier === "function"
1999
+ ? opts.classifier
2000
+ : classifyAutoModeSafety;
2001
+ const classification = classifier(input);
2002
+ const tool = String(input?.tool || "unknown").toLowerCase();
2003
+ const command = inputCommand(input);
2004
+ let policy = null;
2005
+ if (tool === "run_shell") {
2006
+ const result = evaluateShellCommandPolicy(command, {
2007
+ classifyAllShell: input?.context?.classifyAllShell === true,
2008
+ });
2009
+ policy = {
2010
+ decision: result.decision,
2011
+ allowed: result.allowed,
2012
+ ruleId: result.ruleId,
2013
+ hardDenied: result.decision === "deny",
2014
+ reason: result.reason,
2015
+ };
2016
+ }
2017
+
2018
+ const classifiedRisk = normalizeRisk(
2019
+ classification?.riskLevel,
2020
+ normalizeRisk(input?.baseRiskLevel, "medium"),
2021
+ );
2022
+ let effectiveDecision = classifiedRisk === "high" ? "ask" : "allow";
2023
+ if (policy?.decision === "deny") effectiveDecision = "deny";
2024
+ else if (policy?.decision === "reroute") effectiveDecision = "reroute";
2025
+
2026
+ return Object.freeze({
2027
+ schema: AUTO_MODE_SAFETY_VERDICT_SCHEMA,
2028
+ classifierVersion:
2029
+ classification?.classifierVersion || AUTO_MODE_SAFETY_CLASSIFIER_VERSION,
2030
+ classification,
2031
+ policy: policy ? Object.freeze(policy) : null,
2032
+ effectiveDecision,
2033
+ });
2034
+ }