chainlesschain 0.162.176 → 0.162.178
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BDLu5Elz.js → AIOps-DRftcSue.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DH5us-CK.js → ActionButton-BFvUaMKR.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BZ0u8IFG.js → Analytics-C0-Rru4P.js} +3 -3
- package/src/assets/web-panel/assets/AppLayout-bx-BCljj.js +9 -0
- package/src/assets/web-panel/assets/{Artifacts-Br2YBbh_.js → Artifacts-DAJ8B3Un.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C3C6a482.js → Audit-hOk4L1lr.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CzHvebcj.js → BackgroundAgents-3pbDpM9z.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DZNHYDFd.js → Backup-BtziGDn9.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-VRiC4fIx.js → BaseInput-kmshphJ3.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Cm08WWAD.js → Chat-d7tGSosA.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CGHYrugQ.js → ChatBubbleRenderer-p-_PBpX_.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DtByk7d7.js → Checkbox-C4Eh4Tz5.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CphXXOfD.js → Codegen-r7gmhZyA.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Hz86TnLR.js → Col-U7mDaGlL.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HP03ltpD.js → Community-Bpu8aH1T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B_N1mqQz.js → Compact-CHb2XBJe.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CVztRpi8.js → Compliance-Cn_r4dey.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-wUVOyZih.js → Cowork-CdMWfDcz.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-DW5TLJKw.js → Cron-BtnREHWy.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CrAFGhWH.js → Crosschain-BKI9TSzr.js} +1 -1
- package/src/assets/web-panel/assets/{DID-pA-oD8ke.js → DID-pkNubzzJ.js} +2 -2
- package/src/assets/web-panel/assets/Dashboard-1rrz1snN.js +3 -0
- package/src/assets/web-panel/assets/{Dropdown-EweJcsek.js → Dropdown-DLyFVxDc.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-C9xVLPG4.js → EmailListRenderer-CtUWfYVq.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CDcEEqlu.js → FamilyGuardDashboard-B8eX2Qe4.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DtZda9A_.js → Federation-D8lsDGus.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CiYdcVz3.js → FormItemContext-CRYt_aIw.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DA13uwA0.js → GenericCardRenderer-ClKzY8_N.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DzRhZD1O.js → Git-BXj5Dney.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-D5RMgOSb.js → Governance-Cm-I8RMz.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BgrPfSNk.js → Inference-CPvbhvgt.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DzjnD571.js → KnowledgeGraph-sYNDZaoi.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BhWzVDtN.js → Logs-BZY_3CRZ.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BDape9cx.js → MarkdownRenderer-up0BW6pv.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DJtL5GV7.js → Marketplace-C5MEJ2Qe.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CZcQygI7.js → McpTools-ihdrn43I.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DUWSDUUu.js → Memory-Br8bPMlh.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BTwq2WKY.js → MobileBridge-BNntgrYW.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BdV915mw.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BiGugEQm.js → Mtc-A3wDmlqL.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DY7a6gUe.js → MtcAudit-CF-mBLWM.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BQbDu_w7.js → Multisig-Iy2k_MSo.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CM2wXTLv.js → NLProgramming-kk0W7HUb.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DI6gGs1v.js → Notes-B5u5t_4S.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BsZOLWAB.js → NotificationSettings-BN0POf6_.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-CHkSUrXB.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Cds0_tXr.js → Organization-DwWQESuN.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-WunYMD87.js → Overflow-DG6klrcC.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Bo6j4DDv.js → P2P-BwJcKkgZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Bh2XPD5f.js → PdhVaultBrowser-BwOHG0in.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-EMvMMC8N.js → Permissions-CGMXe4TX.js} +4 -4
- package/src/assets/web-panel/assets/PersonalDataHub-ziYqg7-A.js +8 -0
- package/src/assets/web-panel/assets/{Pipeline-BOx8HOgL.js → Pipeline-BzJfeoGt.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-tBeTVQVM.js → Privacy-DLh3Bblx.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CVYg1W4M.js → ProjectInit-USVWW36c.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-Dvt5I7y1.js → ProjectSettings-CiiOn62h.js} +2 -2
- package/src/assets/web-panel/assets/Projects-6V-PNmcA.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Ct8xqY-E.js → Providers-CBPWWzpu.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Cni8Ooja.js → QrScannerModal-QVwoRUq3.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BJVLKdxr.js → QuickAsk-CXnR-2Vn.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DPG8Rbbm.js → Recommend-ClaNzjmF.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DsdR7dCt.js → RemoteSession-C3rGw8vJ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Csg1YM-b.js → Reputation-Ci-OWAoP.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D9DoFoEf.js → Row-DRhAwXOS.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BsvKrXMS.js → RssFeed-BQLJQ2_y.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DFUStfgQ.js → Search-QQUpkfFd.js} +1 -1
- package/src/assets/web-panel/assets/{Security-ILg1zxdK.js → Security-DdXKzuKT.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Dd41PCaY.js → Services-x2Zh62Qd.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Bzo1cN80.js → Skeleton-GdY4KwRz.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BDHh17P0.js → Skills-Bv4tY1Fd.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jl7UTiRY.js → Sla-C3QnM-11.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BePa-RF6.js → SpeechSettings-Bl3ytvmI.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DiqqDKGZ.js → SyncSettings-Wp9IwTsP.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DYQ_6lxj.js → Tasks-CF3rIrDe.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-JiDeI06L.js → Templates-CGfvVVM9.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-hhgI6zwS.js → Tenant-CVXmwQ3U.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-bvwrt03E.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-B8uB5Llk.js → TimelineRenderer-D0g74KhH.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C3b_6r2z.js → Tokens-BH4IwzlT.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-tIP7HKVe.js → Trigger-DJOpM533.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-vjoc11vJ.js → Trust-B43EPRLr.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DUPBnGuH.js → UkeySign-BmBFG-em.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CBvhUY5T.js → VideoEditing-n9oJwB8k.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DUaQ0uA5.js → Wallet-D5QeivHp.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DllSivjE.js → WebAuthn-DcNy7igO.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-xc8D3uO6.js → WorkflowEditor-BVjc7-5-.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BVicU7oA.js → chat-Dy5Tfj3m.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DJzLmhLm.js → colors-C4c2CNZU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BvYRfThE.js → compact-item-DPa3O5vN.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DGXxCBKR.js → createContext-TLcfNA6_.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BAwdDA_5.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-SBDJB1y4.js → hasIn-D41LVFkp.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7HimK0M.js → index-0oevYjfT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCdRAgjy.js → index-388pLGJ1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BO1itZaY.js → index-B8nLBZbv.js} +3 -3
- package/src/assets/web-panel/assets/{index-DvUTDYXX.js → index-BPTGAjee.js} +1 -1
- package/src/assets/web-panel/assets/{index-hxTODbM1.js → index-BPtrOBmW.js} +1 -1
- package/src/assets/web-panel/assets/{index-wcCL-miV.js → index-BQiIxHBy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKyEivf8.js → index-BSM1cHVK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BJZf9DhV.js → index-BYPr9fkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHVLJ8q0.js → index-BsQ7Ev6i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cs2rmLFt.js → index-Btk9qLL2.js} +1 -1
- package/src/assets/web-panel/assets/{index-z1ZylsLq.js → index-BvmziZSG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf-O_g_8.js → index-BvopLC_T.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMarmfrv.js → index-C7xhTVyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CoJAO1Lm.js → index-CNk_ZooP.js} +1 -1
- package/src/assets/web-panel/assets/{index-9gdI7uUW.js → index-CQrEHoOG.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf-ctERC.js → index-CSxex23J.js} +1 -1
- package/src/assets/web-panel/assets/{index-MjC4G09d.js → index-CVjgm992.js} +1 -1
- package/src/assets/web-panel/assets/{index-e4fXH_WO.js → index-CWK4zOPe.js} +1 -1
- package/src/assets/web-panel/assets/{index-xfluRkCT.js → index-CX0VsoP4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRfiXB_p.js → index-CXl0YPNG.js} +1 -1
- package/src/assets/web-panel/assets/{index-d5BVd-cL.js → index-Cip7LWml.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dm_77jgR.js → index-Cq_hlWNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-YJkzy8ZF.js → index-CzZ49YZL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtwqO5qo.js → index-D2Nkivlm.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYqz4Wrr.js → index-DBUgRs4D.js} +1 -1
- package/src/assets/web-panel/assets/index-DEjVkdzq.js +1 -0
- package/src/assets/web-panel/assets/{index-CdGY9P8a.js → index-DNsBj2Ob.js} +1 -1
- package/src/assets/web-panel/assets/{index-DonCnfMr.js → index-Di4X8cfI.js} +1 -1
- package/src/assets/web-panel/assets/{index-1SdDS6lR.js → index-DljFsVGI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DnjPdvR6.js → index-Korc3QIe.js} +1 -1
- package/src/assets/web-panel/assets/index-LMOc5xrS.js +1 -0
- package/src/assets/web-panel/assets/{index-VMfwIH5G.js → index-MKZDje0H.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKrvvWm4.js → index-OHAfRu_3.js} +1 -1
- package/src/assets/web-panel/assets/{index-FxZzRyE1.js → index-WEJr8PL5.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crk5NTb0.js → index-WinCp6du.js} +1 -1
- package/src/assets/web-panel/assets/{index-DCqRS6da.js → index-Yjr6cqSK.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTrBGBJs.js → index-iQ6gtFih.js} +1 -1
- package/src/assets/web-panel/assets/{index-lpaGoTH3.js → index-pt3s7yvV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CloCgBUR.js → index-tQ3igERQ.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C1zO1KnY.js → initDefaultProps-BFZu87WZ.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CWLtEGmC.js → motion-DJIo7qvt.js} +1 -1
- package/src/assets/web-panel/assets/{move-DzD_C3Vm.js → move-Cfr5qknL.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-D880PYJl.js → mtc-parser-Bgc-OadH.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CFR_z2ov.js → omit-X3WI-TNp.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-srIhgrSC.js → pickAttrs-pJhU9IxK.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C1rXpibP.js → placementArrow-BNg_Vj3T.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-C30gRcSS.js → responsiveObserve-TeSEGQ5-.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CvtKiMfV.js → slide-D_t_P0bv.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DNHO7ITG.js → statusUtils-DrrIMwAT.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dt_Sl-g4.js → styleChecker-BjZCuFRm.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Cjev42Gh.js → useFlexGapSupport-rTFdlqdQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Baxglj2V.js → useFs-BidCpfLX.js} +1 -1
- package/src/assets/web-panel/assets/usePersonalDataHub-C5mHttA9.js +1 -0
- package/src/assets/web-panel/assets/{vnode-mIRkdT7o.js → vnode-BrbcRiHZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-HzI5DlH_.js → zoom-PISX7AkR.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/__tests__/hub-wechat.test.js +45 -0
- package/src/commands/agenda.js +22 -6
- package/src/commands/agent.js +3 -3
- package/src/commands/background-session.js +16 -6
- package/src/commands/batch.js +79 -27
- package/src/commands/changelog.js +15 -4
- package/src/commands/context.js +33 -1
- package/src/commands/eval.js +21 -6
- package/src/commands/hook.js +2 -2
- package/src/commands/hub.js +473 -39
- package/src/commands/init.js +212 -110
- package/src/commands/llm.js +22 -6
- package/src/commands/loop.js +9 -2
- package/src/commands/skill.js +13 -1
- package/src/commands/statusline.js +3 -3
- package/src/data/changelog.json +53 -5
- package/src/gateways/ws/personal-data-hub-protocol.js +22 -2
- package/src/harness/background-task-manager.js +18 -6
- package/src/harness/background-task-worker.js +11 -18
- package/src/harness/golden-transcript.js +3 -0
- package/src/harness/mcp-client.js +9 -15
- package/src/harness/worktree-isolator.js +18 -21
- package/src/index.js +18 -7
- package/src/lib/agent-core.js +5 -0
- package/src/lib/agent-ipc-bus.js +12 -3
- package/src/lib/agent-team/team-worktree.js +28 -6
- package/src/lib/agent-worktree.js +20 -19
- package/src/lib/approval-authority-store.js +891 -0
- package/src/lib/async-hook-supervisor.cjs +134 -24
- package/src/lib/async-hook-supervisor.js +47 -0
- package/src/lib/background-agent-supervisor.js +35 -12
- package/src/lib/background-interaction-resolver.js +399 -54
- package/src/lib/background-session-transport.js +3 -1
- package/src/lib/background-task-manager.js +5 -1
- package/src/lib/changelog.js +57 -0
- package/src/lib/chrome-connector.js +6 -2
- package/src/lib/claude-code-bridge.js +24 -8
- package/src/lib/cli-anything-bridge.js +82 -25
- package/src/lib/context-breakdown.js +82 -3
- package/src/lib/doctor-checkup.js +18 -6
- package/src/lib/eval/tasks.js +25 -31
- package/src/lib/event-runtime-host.js +225 -0
- package/src/lib/event-runtime-store.js +102 -21
- package/src/lib/event-runtime-worker.js +70 -2
- package/src/lib/hook-runner.cjs +71 -36
- package/src/lib/hook-runner.js +55 -0
- package/src/lib/hooks-v2-producers.js +33 -8
- package/src/lib/hooks-v2-runtime.js +360 -40
- package/src/lib/host-adb-bridge.js +18 -3
- package/src/lib/interaction-adapter.js +66 -0
- package/src/lib/lsp/__tests__/benchmark.test.js +61 -0
- package/src/lib/lsp/__tests__/lsp-client.test.js +6 -1
- package/src/lib/lsp/benchmark.js +20 -4
- package/src/lib/lsp/lsp-client.js +6 -4
- package/src/lib/lsp/lsp-server-registry.js +0 -2
- package/src/lib/operation-fingerprint.js +107 -2
- package/src/lib/orchestrator.js +8 -2
- package/src/lib/packer/pack-update-applier.js +14 -2
- package/src/lib/packer/smoke-runner.js +18 -5
- package/src/lib/paths.js +1 -1
- package/src/lib/pdh-dedicated-batch-collectors.js +124 -0
- package/src/lib/personal-data-hub-wiring.js +327 -52
- package/src/lib/plugin-monitor-supervisor.js +20 -13
- package/src/lib/plugin-runtime/install.js +10 -3
- package/src/lib/process-execution-broker/credential-agent.js +954 -111
- package/src/lib/process-execution-broker/index.js +369 -85
- package/src/lib/process-execution-broker/platform-sandbox.js +320 -140
- package/src/lib/provider-http-error.js +94 -0
- package/src/lib/remote-approval-bridge.js +222 -26
- package/src/lib/repl-bang-memorize.js +10 -2
- package/src/lib/repl-goal.js +19 -2
- package/src/lib/settings-hook-events.cjs +13 -8
- package/src/lib/settings-hook-events.js +20 -0
- package/src/lib/side-effect-ledger-store.js +65 -14
- package/src/lib/skill-loader.js +215 -12
- package/src/lib/skill-packs/generator.js +18 -9
- package/src/lib/skill-process-broker.js +85 -0
- package/src/lib/slash-commands.js +10 -2
- package/src/lib/status-line.cjs +28 -7
- package/src/lib/status-line.js +33 -0
- package/src/lib/sub-agent-context.js +42 -0
- package/src/lib/turn-binding-store.js +64 -15
- package/src/lib/turn-binding.js +85 -16
- package/src/repl/agent-repl.js +88 -49
- package/src/repl/remote-approval.js +13 -13
- package/src/repl/repl-turn-binding.js +46 -16
- package/src/runtime/agent-core.js +419 -84
- package/src/runtime/coding-agent-contract-shared.cjs +16 -0
- package/src/runtime/headless-runner.js +172 -44
- package/src/runtime/headless-stream.js +221 -11
- package/src/runtime/session-slash-commands.js +506 -0
- package/src/skills/video-editing/extractors/audio-extractor.js +13 -5
- package/src/skills/video-editing/extractors/video-extractor.js +13 -3
- package/src/skills/video-editing/media-process.js +20 -0
- package/src/skills/video-editing/render/audio-mix.js +13 -3
- package/src/skills/video-editing/render/ffmpeg-concat.js +7 -2
- package/src/skills/video-editing/render/ffmpeg-extract.js +7 -2
- package/src/workers/background-agent-worker.js +76 -40
- package/src/assets/web-panel/assets/AppLayout-Duu8xR14.js +0 -9
- package/src/assets/web-panel/assets/Dashboard-CXrd5gAw.js +0 -3
- package/src/assets/web-panel/assets/MobileProjects-NZvvfDME.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-C48mu09U.js +0 -1
- package/src/assets/web-panel/assets/PersonalDataHub-AX3ypUDV.js +0 -9
- package/src/assets/web-panel/assets/Projects-Cc7FC9-e.js +0 -1
- package/src/assets/web-panel/assets/Terminal-CjTZPa37.js +0 -3
- package/src/assets/web-panel/assets/devWarning-BT46Y_QI.js +0 -1
- package/src/assets/web-panel/assets/index-B7nuIywP.js +0 -1
- package/src/assets/web-panel/assets/index-ycwAfC-i.js +0 -1
- package/src/assets/web-panel/assets/usePersonalDataHub-B4NkcLaP.js +0 -1
|
@@ -1,21 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* In-process credential reference agent.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
* - Child processes request credentials through IPC channel
|
|
4
|
+
* The process execution broker calls this module after a command is approved.
|
|
5
|
+
* Long-lived credential values stay in the broker process while the child only
|
|
6
|
+
* receives short-lived opaque references. Resolving a reference requires an
|
|
7
|
+
* attested agent id and the exact process/host target recorded at issuance.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
9
|
+
* This module deliberately does not claim to provide an OS IPC transport. A
|
|
10
|
+
* future broker-owned IPC implementation can call resolveCredentialRef() after
|
|
11
|
+
* it has authenticated the requesting process and destination host.
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
import os from "node:os";
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
import fs from "node:fs";
|
|
16
14
|
import crypto from "node:crypto";
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
const REDACTED_VALUE = "***REDACTED***";
|
|
17
|
+
const DEFAULT_TTL_MS = 60_000;
|
|
18
|
+
const MAX_TTL_MS = 5 * 60_000;
|
|
19
|
+
const DEFAULT_MAX_USES = 1;
|
|
20
|
+
const MAX_USES_LIMIT = 16;
|
|
21
|
+
const DEFAULT_MAX_CREDENTIALS = 4096;
|
|
22
|
+
const DEFAULT_MAX_AUDIT_ENTRIES = 10_000;
|
|
23
|
+
|
|
24
|
+
const CREDENTIAL_ERROR_CODES = Object.freeze({
|
|
25
|
+
DISABLED: "CC_CREDENTIAL_AGENT_DISABLED",
|
|
26
|
+
APPROVAL_REQUIRED: "CC_CREDENTIAL_APPROVAL_REQUIRED",
|
|
27
|
+
TARGET_REQUIRED: "CC_CREDENTIAL_TARGET_REQUIRED",
|
|
28
|
+
INVALID_REQUEST: "CC_CREDENTIAL_INVALID_REQUEST",
|
|
29
|
+
STORE_FULL: "CC_CREDENTIAL_STORE_FULL",
|
|
30
|
+
NOT_FOUND: "CC_CREDENTIAL_REF_NOT_FOUND",
|
|
31
|
+
EXPIRED: "CC_CREDENTIAL_REF_EXPIRED",
|
|
32
|
+
EXHAUSTED: "CC_CREDENTIAL_REF_EXHAUSTED",
|
|
33
|
+
REVOKED: "CC_CREDENTIAL_REF_REVOKED",
|
|
34
|
+
AGENT_MISMATCH: "CC_CREDENTIAL_AGENT_MISMATCH",
|
|
35
|
+
TARGET_MISMATCH: "CC_CREDENTIAL_TARGET_MISMATCH",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Patterns for environment variables that contain secrets.
|
|
19
39
|
const SENSITIVE_ENV_PATTERNS = [
|
|
20
40
|
/API[_-]?KEY/i,
|
|
21
41
|
/SECRET/i,
|
|
@@ -47,7 +67,7 @@ const SENSITIVE_ENV_PATTERNS = [
|
|
|
47
67
|
/GITLAB_TOKEN/i,
|
|
48
68
|
];
|
|
49
69
|
|
|
50
|
-
// Keys that are always safe to pass through (non-sensitive)
|
|
70
|
+
// Keys that are always safe to pass through (non-sensitive).
|
|
51
71
|
const ENV_ALLOWLIST = new Set([
|
|
52
72
|
"PATH",
|
|
53
73
|
"HOME",
|
|
@@ -65,9 +85,7 @@ const ENV_ALLOWLIST = new Set([
|
|
|
65
85
|
"VISUAL",
|
|
66
86
|
"NODE_ENV",
|
|
67
87
|
"NODE_OPTIONS",
|
|
68
|
-
//
|
|
69
|
-
// child processes. These identify a run; they are not authentication
|
|
70
|
-
// credentials. Other SESSION-named variables remain filtered.
|
|
88
|
+
// These identify a run; they are not authentication credentials.
|
|
71
89
|
"CC_SESSION_ID",
|
|
72
90
|
"CLAUDE_CODE_SESSION_ID",
|
|
73
91
|
"npm_config_registry",
|
|
@@ -104,147 +122,956 @@ const ENV_ALLOWLIST = new Set([
|
|
|
104
122
|
"CHAINLESSCHAIN_CONFIG_DIR",
|
|
105
123
|
]);
|
|
106
124
|
|
|
125
|
+
const RESERVED_AGENT_ENV_KEYS = new Set([
|
|
126
|
+
"CC_CREDENTIAL_AGENT_ID",
|
|
127
|
+
"CC_CREDENTIAL_PROXY",
|
|
128
|
+
"CC_CREDENTIAL_TRANSPORT",
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
function credentialError(code, message) {
|
|
132
|
+
const error = new Error(message);
|
|
133
|
+
error.code = code;
|
|
134
|
+
return error;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isTruthyEnvironmentFlag(value) {
|
|
138
|
+
return ["1", "true", "yes", "on"].includes(
|
|
139
|
+
String(value ?? "")
|
|
140
|
+
.trim()
|
|
141
|
+
.toLowerCase(),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function positiveInteger(value, fallback, maximum) {
|
|
146
|
+
const parsed = Number(value);
|
|
147
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
148
|
+
const integer = Math.floor(parsed);
|
|
149
|
+
if (integer <= 0) return fallback;
|
|
150
|
+
return Math.min(integer, maximum);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function requestedPositiveInteger(value, fallback, maximum, fieldName) {
|
|
154
|
+
if (value === undefined || value === null) {
|
|
155
|
+
return Math.min(fallback, maximum);
|
|
156
|
+
}
|
|
157
|
+
const parsed = Number(value);
|
|
158
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
159
|
+
throw credentialError(
|
|
160
|
+
CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
161
|
+
`${fieldName} must be a positive integer`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const integer = Math.floor(parsed);
|
|
165
|
+
if (integer <= 0) {
|
|
166
|
+
throw credentialError(
|
|
167
|
+
CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
168
|
+
`${fieldName} must be a positive integer`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return Math.min(integer, maximum);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function normalizeProcessTarget(value) {
|
|
175
|
+
const normalized = String(value ?? "").trim();
|
|
176
|
+
if (!normalized) return null;
|
|
177
|
+
return process.platform === "win32"
|
|
178
|
+
? normalized.replaceAll("/", "\\").toLowerCase()
|
|
179
|
+
: normalized;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function normalizeHostTarget(value) {
|
|
183
|
+
const candidate = String(value ?? "").trim();
|
|
184
|
+
if (!candidate) return null;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
const parsed = candidate.includes("://")
|
|
188
|
+
? new URL(candidate)
|
|
189
|
+
: new URL(`https://${candidate}`);
|
|
190
|
+
return parsed.hostname.toLowerCase().replace(/\.$/, "");
|
|
191
|
+
} catch {
|
|
192
|
+
return candidate.toLowerCase().replace(/\.$/, "");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function cloneAuditEntry(entry) {
|
|
197
|
+
return {
|
|
198
|
+
...entry,
|
|
199
|
+
target: entry.target ? { ...entry.target } : undefined,
|
|
200
|
+
envRedacted: entry.envRedacted ? [...entry.envRedacted] : undefined,
|
|
201
|
+
argRedacted: entry.argRedacted ? [...entry.argRedacted] : undefined,
|
|
202
|
+
reservedEnvRemoved: entry.reservedEnvRemoved
|
|
203
|
+
? [...entry.reservedEnvRemoved]
|
|
204
|
+
: undefined,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
107
208
|
class CredentialAgent {
|
|
108
|
-
constructor() {
|
|
209
|
+
constructor(options = {}) {
|
|
109
210
|
this._credentialStore = new Map();
|
|
110
211
|
this._accessLog = [];
|
|
111
|
-
this.
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
212
|
+
this._environment = options.env ?? process.env;
|
|
213
|
+
this._now = typeof options.now === "function" ? options.now : Date.now;
|
|
214
|
+
this._randomBytes =
|
|
215
|
+
typeof options.randomBytes === "function"
|
|
216
|
+
? options.randomBytes
|
|
217
|
+
: crypto.randomBytes;
|
|
218
|
+
this._agentId = options.agentId || crypto.randomUUID();
|
|
219
|
+
this._enabledOverride =
|
|
220
|
+
typeof options.enabled === "boolean" ? options.enabled : undefined;
|
|
221
|
+
this._maxTtlMs = positiveInteger(
|
|
222
|
+
options.maxTtlMs,
|
|
223
|
+
MAX_TTL_MS,
|
|
224
|
+
Number.MAX_SAFE_INTEGER,
|
|
225
|
+
);
|
|
226
|
+
this._defaultTtlMs = Math.min(
|
|
227
|
+
positiveInteger(options.defaultTtlMs, DEFAULT_TTL_MS, this._maxTtlMs),
|
|
228
|
+
this._maxTtlMs,
|
|
229
|
+
);
|
|
230
|
+
this._maxUsesLimit = positiveInteger(
|
|
231
|
+
options.maxUsesLimit,
|
|
232
|
+
MAX_USES_LIMIT,
|
|
233
|
+
Number.MAX_SAFE_INTEGER,
|
|
234
|
+
);
|
|
235
|
+
this._defaultMaxUses = Math.min(
|
|
236
|
+
positiveInteger(
|
|
237
|
+
options.defaultMaxUses,
|
|
238
|
+
DEFAULT_MAX_USES,
|
|
239
|
+
this._maxUsesLimit,
|
|
240
|
+
),
|
|
241
|
+
this._maxUsesLimit,
|
|
242
|
+
);
|
|
243
|
+
this._maxCredentials = positiveInteger(
|
|
244
|
+
options.maxCredentials,
|
|
245
|
+
DEFAULT_MAX_CREDENTIALS,
|
|
246
|
+
Number.MAX_SAFE_INTEGER,
|
|
247
|
+
);
|
|
248
|
+
this._maxAuditEntries = positiveInteger(
|
|
249
|
+
options.maxAuditEntries,
|
|
250
|
+
DEFAULT_MAX_AUDIT_ENTRIES,
|
|
251
|
+
Number.MAX_SAFE_INTEGER,
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
// "broker-api" is an in-process primitive, not a socket/pipe endpoint.
|
|
255
|
+
this._transport = "broker-api";
|
|
256
|
+
this._proxyReady = false;
|
|
257
|
+
this._lastEnabledState = this._computeEnabled();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
_computeEnabled() {
|
|
261
|
+
if (this._enabledOverride !== undefined) {
|
|
262
|
+
return this._enabledOverride;
|
|
263
|
+
}
|
|
264
|
+
return !isTruthyEnvironmentFlag(this._environment?.CC_CRED_AGENT_DISABLE);
|
|
114
265
|
}
|
|
115
266
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
267
|
+
_syncEnabledState() {
|
|
268
|
+
const enabled = this._computeEnabled();
|
|
269
|
+
if (enabled === this._lastEnabledState) return enabled;
|
|
270
|
+
|
|
271
|
+
if (!enabled) {
|
|
272
|
+
let revoked = 0;
|
|
273
|
+
for (const record of this._credentialStore.values()) {
|
|
274
|
+
if (record.status === "active") {
|
|
275
|
+
record.value = null;
|
|
276
|
+
record.status = "disabled";
|
|
277
|
+
revoked += 1;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
this._recordAudit({
|
|
281
|
+
event: "credential_agent_disabled",
|
|
282
|
+
outcome: "allowed",
|
|
283
|
+
credentialsRevoked: revoked,
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
this._recordAudit({
|
|
287
|
+
event: "credential_agent_enabled",
|
|
288
|
+
outcome: "allowed",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this._lastEnabledState = enabled;
|
|
293
|
+
return enabled;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* CC_CRED_AGENT_DISABLE accepts 1/true/yes/on (case-insensitive).
|
|
298
|
+
*
|
|
299
|
+
* Disabled means references cannot be issued or resolved. Environment and
|
|
300
|
+
* argv filtering remains mandatory so disabling the agent can never restore
|
|
301
|
+
* plaintext credential inheritance.
|
|
302
|
+
*/
|
|
303
|
+
isEnabled() {
|
|
304
|
+
return this._syncEnabledState();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Build the explicit context the ProcessExecutionBroker should pass to
|
|
309
|
+
* applyWithReport(). The caller remains responsible for attesting that the
|
|
310
|
+
* approval and target describe the process it will actually launch.
|
|
311
|
+
*/
|
|
312
|
+
createBrokerContext({
|
|
313
|
+
approvalId,
|
|
314
|
+
process: processTarget,
|
|
315
|
+
host = null,
|
|
316
|
+
ttlMs,
|
|
317
|
+
maxUses,
|
|
318
|
+
} = {}) {
|
|
319
|
+
const normalizedProcess = normalizeProcessTarget(processTarget);
|
|
320
|
+
if (!approvalId) {
|
|
321
|
+
throw credentialError(
|
|
322
|
+
CREDENTIAL_ERROR_CODES.APPROVAL_REQUIRED,
|
|
323
|
+
"Credential approval id is required",
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
if (!normalizedProcess) {
|
|
327
|
+
throw credentialError(
|
|
328
|
+
CREDENTIAL_ERROR_CODES.TARGET_REQUIRED,
|
|
329
|
+
"Credential process target is required",
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return Object.freeze({
|
|
334
|
+
approved: true,
|
|
335
|
+
approvalId: String(approvalId),
|
|
336
|
+
target: Object.freeze({
|
|
337
|
+
process: normalizedProcess,
|
|
338
|
+
host: normalizeHostTarget(host),
|
|
339
|
+
}),
|
|
340
|
+
ttlMs,
|
|
341
|
+
maxUses,
|
|
342
|
+
});
|
|
120
343
|
}
|
|
121
344
|
|
|
122
345
|
/**
|
|
123
|
-
* Check if an env var key looks sensitive
|
|
346
|
+
* Check if an env var key looks sensitive.
|
|
124
347
|
*/
|
|
125
348
|
isSensitiveKey(key) {
|
|
126
349
|
if (ENV_ALLOWLIST.has(key)) return false;
|
|
127
350
|
return SENSITIVE_ENV_PATTERNS.some((pattern) => pattern.test(key));
|
|
128
351
|
}
|
|
129
352
|
|
|
353
|
+
isReservedAgentKey(key) {
|
|
354
|
+
return (
|
|
355
|
+
RESERVED_AGENT_ENV_KEYS.has(key) ||
|
|
356
|
+
key.startsWith("CC_CRED_REF_") ||
|
|
357
|
+
key.startsWith("CC_CRED_ARG_REF_")
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
_recordAudit(entry) {
|
|
362
|
+
this._accessLog.push({
|
|
363
|
+
timestamp: this._now(),
|
|
364
|
+
...entry,
|
|
365
|
+
});
|
|
366
|
+
while (this._accessLog.length > this._maxAuditEntries) {
|
|
367
|
+
this._accessLog.shift();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
_fingerprint(value) {
|
|
372
|
+
if (!value) return null;
|
|
373
|
+
return crypto
|
|
374
|
+
.createHash("sha256")
|
|
375
|
+
.update(String(value))
|
|
376
|
+
.digest("hex")
|
|
377
|
+
.slice(0, 16);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
_expireStaleCredentials() {
|
|
381
|
+
const now = this._now();
|
|
382
|
+
for (const record of this._credentialStore.values()) {
|
|
383
|
+
if (record.status === "active" && now >= record.expiresAt) {
|
|
384
|
+
record.value = null;
|
|
385
|
+
record.status = "expired";
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
_ensureStoreCapacity() {
|
|
391
|
+
this._expireStaleCredentials();
|
|
392
|
+
if (this._credentialStore.size < this._maxCredentials) return;
|
|
393
|
+
|
|
394
|
+
for (const [refId, record] of this._credentialStore) {
|
|
395
|
+
if (record.status !== "active") {
|
|
396
|
+
this._credentialStore.delete(refId);
|
|
397
|
+
if (this._credentialStore.size < this._maxCredentials) return;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
throw credentialError(
|
|
402
|
+
CREDENTIAL_ERROR_CODES.STORE_FULL,
|
|
403
|
+
"Credential reference store is full",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
_createReferenceId() {
|
|
408
|
+
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
409
|
+
const refId = `cc-cred-${this._randomBytes(16).toString("hex")}`;
|
|
410
|
+
if (!this._credentialStore.has(refId)) return refId;
|
|
411
|
+
}
|
|
412
|
+
throw credentialError(
|
|
413
|
+
CREDENTIAL_ERROR_CODES.STORE_FULL,
|
|
414
|
+
"Unable to allocate a unique credential reference",
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
_normalizeIssueRequest(request = {}) {
|
|
419
|
+
const approval = request.approval || {};
|
|
420
|
+
const target = request.target || {};
|
|
421
|
+
const processTarget = normalizeProcessTarget(
|
|
422
|
+
target.process ?? request.process,
|
|
423
|
+
);
|
|
424
|
+
const hostTarget = normalizeHostTarget(target.host ?? request.host);
|
|
425
|
+
const approvalId = request.approvalId ?? approval.id;
|
|
426
|
+
const approved = request.approved ?? approval.approved;
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
approved,
|
|
430
|
+
approvalId: approvalId ? String(approvalId) : null,
|
|
431
|
+
key: String(request.key ?? "credential"),
|
|
432
|
+
value: request.value,
|
|
433
|
+
target: {
|
|
434
|
+
process: processTarget,
|
|
435
|
+
host: hostTarget,
|
|
436
|
+
},
|
|
437
|
+
ttlMs: requestedPositiveInteger(
|
|
438
|
+
request.ttlMs,
|
|
439
|
+
this._defaultTtlMs,
|
|
440
|
+
this._maxTtlMs,
|
|
441
|
+
"Credential TTL",
|
|
442
|
+
),
|
|
443
|
+
maxUses: requestedPositiveInteger(
|
|
444
|
+
request.maxUses,
|
|
445
|
+
this._defaultMaxUses,
|
|
446
|
+
this._maxUsesLimit,
|
|
447
|
+
"Credential maxUses",
|
|
448
|
+
),
|
|
449
|
+
origin: String(request.origin ?? "unknown"),
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
130
453
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
454
|
+
* Issue a short-lived, target-bound reference.
|
|
455
|
+
*
|
|
456
|
+
* This is a parent/Broker API. A child must never be allowed to call it with
|
|
457
|
+
* self-asserted approval metadata.
|
|
133
458
|
*/
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
459
|
+
issueCredentialRef(request = {}) {
|
|
460
|
+
if (!this.isEnabled()) {
|
|
461
|
+
this._recordAudit({
|
|
462
|
+
event: "credential_ref_issue",
|
|
463
|
+
outcome: "denied",
|
|
464
|
+
reason: CREDENTIAL_ERROR_CODES.DISABLED,
|
|
465
|
+
key:
|
|
466
|
+
request && typeof request === "object"
|
|
467
|
+
? String(request.key ?? "credential")
|
|
468
|
+
: "credential",
|
|
469
|
+
});
|
|
470
|
+
throw credentialError(
|
|
471
|
+
CREDENTIAL_ERROR_CODES.DISABLED,
|
|
472
|
+
"Credential reference issuance is disabled",
|
|
473
|
+
);
|
|
474
|
+
}
|
|
137
475
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
safe[`CC_CRED_REF_${key}`] = refId;
|
|
146
|
-
} else {
|
|
147
|
-
safe[key] = value;
|
|
476
|
+
let normalized;
|
|
477
|
+
try {
|
|
478
|
+
if (!request || typeof request !== "object") {
|
|
479
|
+
throw credentialError(
|
|
480
|
+
CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
481
|
+
"Credential issuance request is required",
|
|
482
|
+
);
|
|
148
483
|
}
|
|
484
|
+
normalized = this._normalizeIssueRequest(request);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
this._recordAudit({
|
|
487
|
+
event: "credential_ref_issue",
|
|
488
|
+
outcome: "denied",
|
|
489
|
+
reason: error.code || CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
490
|
+
});
|
|
491
|
+
throw error;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (normalized.approved !== true || !normalized.approvalId) {
|
|
495
|
+
this._recordAudit({
|
|
496
|
+
event: "credential_ref_issue",
|
|
497
|
+
outcome: "denied",
|
|
498
|
+
reason: CREDENTIAL_ERROR_CODES.APPROVAL_REQUIRED,
|
|
499
|
+
key: normalized.key,
|
|
500
|
+
target: normalized.target,
|
|
501
|
+
});
|
|
502
|
+
throw credentialError(
|
|
503
|
+
CREDENTIAL_ERROR_CODES.APPROVAL_REQUIRED,
|
|
504
|
+
"Credential reference requires an approved broker decision",
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (!normalized.target.process) {
|
|
508
|
+
this._recordAudit({
|
|
509
|
+
event: "credential_ref_issue",
|
|
510
|
+
outcome: "denied",
|
|
511
|
+
reason: CREDENTIAL_ERROR_CODES.TARGET_REQUIRED,
|
|
512
|
+
key: normalized.key,
|
|
513
|
+
});
|
|
514
|
+
throw credentialError(
|
|
515
|
+
CREDENTIAL_ERROR_CODES.TARGET_REQUIRED,
|
|
516
|
+
"Credential process target is required",
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
if (normalized.value === undefined || normalized.value === null) {
|
|
520
|
+
this._recordAudit({
|
|
521
|
+
event: "credential_ref_issue",
|
|
522
|
+
outcome: "denied",
|
|
523
|
+
reason: CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
524
|
+
key: normalized.key,
|
|
525
|
+
target: normalized.target,
|
|
526
|
+
});
|
|
527
|
+
throw credentialError(
|
|
528
|
+
CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
529
|
+
"Credential value is required",
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
let refId;
|
|
534
|
+
try {
|
|
535
|
+
this._ensureStoreCapacity();
|
|
536
|
+
refId = this._createReferenceId();
|
|
537
|
+
} catch (error) {
|
|
538
|
+
this._recordAudit({
|
|
539
|
+
event: "credential_ref_issue",
|
|
540
|
+
outcome: "denied",
|
|
541
|
+
reason: error.code || CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
542
|
+
key: normalized.key,
|
|
543
|
+
target: normalized.target,
|
|
544
|
+
});
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
const createdAt = this._now();
|
|
548
|
+
this._credentialStore.set(refId, {
|
|
549
|
+
key: normalized.key,
|
|
550
|
+
value: normalized.value,
|
|
551
|
+
createdAt,
|
|
552
|
+
expiresAt: createdAt + normalized.ttlMs,
|
|
553
|
+
maxUses: normalized.maxUses,
|
|
554
|
+
useCount: 0,
|
|
555
|
+
status: "active",
|
|
556
|
+
target: normalized.target,
|
|
557
|
+
approvalFingerprint: this._fingerprint(normalized.approvalId),
|
|
558
|
+
});
|
|
559
|
+
this._recordAudit({
|
|
560
|
+
event: "credential_ref_issue",
|
|
561
|
+
outcome: "allowed",
|
|
562
|
+
key: normalized.key,
|
|
563
|
+
target: normalized.target,
|
|
564
|
+
refFingerprint: this._fingerprint(refId),
|
|
565
|
+
approvalFingerprint: this._fingerprint(normalized.approvalId),
|
|
566
|
+
ttlMs: normalized.ttlMs,
|
|
567
|
+
maxUses: normalized.maxUses,
|
|
568
|
+
origin: normalized.origin,
|
|
569
|
+
});
|
|
570
|
+
return refId;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
_assertResolutionTarget(record, request) {
|
|
574
|
+
if (request.agentId !== this._agentId) {
|
|
575
|
+
throw credentialError(
|
|
576
|
+
CREDENTIAL_ERROR_CODES.AGENT_MISMATCH,
|
|
577
|
+
"Credential agent identity does not match",
|
|
578
|
+
);
|
|
149
579
|
}
|
|
150
580
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
581
|
+
const requestTarget = {
|
|
582
|
+
process: normalizeProcessTarget(
|
|
583
|
+
request.target?.process ?? request.process,
|
|
584
|
+
),
|
|
585
|
+
host: normalizeHostTarget(request.target?.host ?? request.host),
|
|
586
|
+
};
|
|
587
|
+
if (
|
|
588
|
+
!requestTarget.process ||
|
|
589
|
+
requestTarget.process !== record.target.process ||
|
|
590
|
+
requestTarget.host !== record.target.host
|
|
591
|
+
) {
|
|
592
|
+
throw credentialError(
|
|
593
|
+
CREDENTIAL_ERROR_CODES.TARGET_MISMATCH,
|
|
594
|
+
"Credential resolution target does not match",
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
154
598
|
|
|
155
|
-
|
|
599
|
+
_throwStoredCredentialState(record) {
|
|
600
|
+
if (record.status === "expired") {
|
|
601
|
+
throw credentialError(
|
|
602
|
+
CREDENTIAL_ERROR_CODES.EXPIRED,
|
|
603
|
+
"Credential reference has expired",
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
if (record.status === "exhausted") {
|
|
607
|
+
throw credentialError(
|
|
608
|
+
CREDENTIAL_ERROR_CODES.EXHAUSTED,
|
|
609
|
+
"Credential reference usage limit has been reached",
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
throw credentialError(
|
|
613
|
+
CREDENTIAL_ERROR_CODES.REVOKED,
|
|
614
|
+
"Credential reference has been revoked",
|
|
615
|
+
);
|
|
156
616
|
}
|
|
157
617
|
|
|
158
618
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
619
|
+
* Resolve a reference for a Broker-attested request.
|
|
620
|
+
*
|
|
621
|
+
* The raw value is returned only to the trusted caller. An eventual IPC
|
|
622
|
+
* adapter must authenticate the OS process and host before constructing this
|
|
623
|
+
* request object; child-provided claims are not sufficient attestation.
|
|
161
624
|
*/
|
|
162
|
-
|
|
163
|
-
const
|
|
625
|
+
resolveCredentialRef(refId, request = {}) {
|
|
626
|
+
const refFingerprint = this._fingerprint(refId);
|
|
627
|
+
if (!this.isEnabled()) {
|
|
628
|
+
this._recordAudit({
|
|
629
|
+
event: "credential_ref_resolve",
|
|
630
|
+
outcome: "denied",
|
|
631
|
+
reason: CREDENTIAL_ERROR_CODES.DISABLED,
|
|
632
|
+
refFingerprint,
|
|
633
|
+
});
|
|
634
|
+
throw credentialError(
|
|
635
|
+
CREDENTIAL_ERROR_CODES.DISABLED,
|
|
636
|
+
"Credential reference resolution is disabled",
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
this._expireStaleCredentials();
|
|
641
|
+
const record =
|
|
642
|
+
typeof refId === "string" ? this._credentialStore.get(refId) : null;
|
|
643
|
+
if (!record) {
|
|
644
|
+
this._recordAudit({
|
|
645
|
+
event: "credential_ref_resolve",
|
|
646
|
+
outcome: "denied",
|
|
647
|
+
reason: CREDENTIAL_ERROR_CODES.NOT_FOUND,
|
|
648
|
+
refFingerprint,
|
|
649
|
+
});
|
|
650
|
+
throw credentialError(
|
|
651
|
+
CREDENTIAL_ERROR_CODES.NOT_FOUND,
|
|
652
|
+
"Credential reference was not found",
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (record.status !== "active") {
|
|
657
|
+
const code =
|
|
658
|
+
record.status === "expired"
|
|
659
|
+
? CREDENTIAL_ERROR_CODES.EXPIRED
|
|
660
|
+
: record.status === "exhausted"
|
|
661
|
+
? CREDENTIAL_ERROR_CODES.EXHAUSTED
|
|
662
|
+
: CREDENTIAL_ERROR_CODES.REVOKED;
|
|
663
|
+
this._recordAudit({
|
|
664
|
+
event: "credential_ref_resolve",
|
|
665
|
+
outcome: "denied",
|
|
666
|
+
reason: code,
|
|
667
|
+
key: record.key,
|
|
668
|
+
target: record.target,
|
|
669
|
+
refFingerprint,
|
|
670
|
+
});
|
|
671
|
+
this._throwStoredCredentialState(record);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
this._assertResolutionTarget(
|
|
676
|
+
record,
|
|
677
|
+
request && typeof request === "object" ? request : {},
|
|
678
|
+
);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
this._recordAudit({
|
|
681
|
+
event: "credential_ref_resolve",
|
|
682
|
+
outcome: "denied",
|
|
683
|
+
reason: error.code || CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
684
|
+
key: record.key,
|
|
685
|
+
target: record.target,
|
|
686
|
+
refFingerprint,
|
|
687
|
+
});
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const value = record.value;
|
|
692
|
+
record.useCount += 1;
|
|
693
|
+
const remainingUses = record.maxUses - record.useCount;
|
|
694
|
+
if (remainingUses <= 0) {
|
|
695
|
+
record.value = null;
|
|
696
|
+
record.status = "exhausted";
|
|
697
|
+
}
|
|
698
|
+
this._recordAudit({
|
|
699
|
+
event: "credential_ref_resolve",
|
|
700
|
+
outcome: "allowed",
|
|
701
|
+
key: record.key,
|
|
702
|
+
target: record.target,
|
|
703
|
+
refFingerprint,
|
|
704
|
+
useCount: record.useCount,
|
|
705
|
+
remainingUses: Math.max(remainingUses, 0),
|
|
706
|
+
});
|
|
707
|
+
return value;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Strict backward-compatible name for callers migrating to the new API. */
|
|
711
|
+
resolveCredential(refId, request = {}) {
|
|
712
|
+
return this.resolveCredentialRef(refId, request);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
revokeCredentialRef(refId, reason = "broker-revoked") {
|
|
716
|
+
const record = this._credentialStore.get(refId);
|
|
717
|
+
if (!record) return false;
|
|
718
|
+
record.value = null;
|
|
719
|
+
record.status = "revoked";
|
|
720
|
+
this._recordAudit({
|
|
721
|
+
event: "credential_ref_revoke",
|
|
722
|
+
outcome: "allowed",
|
|
723
|
+
reason: "broker-revoked",
|
|
724
|
+
reasonFingerprint: this._fingerprint(reason),
|
|
725
|
+
key: record.key,
|
|
726
|
+
target: record.target,
|
|
727
|
+
refFingerprint: this._fingerprint(refId),
|
|
728
|
+
});
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
_contextForSpawn(spawnOptions) {
|
|
733
|
+
if (
|
|
734
|
+
Object.prototype.hasOwnProperty.call(spawnOptions, "credentialContext")
|
|
735
|
+
) {
|
|
736
|
+
const supplied = spawnOptions.credentialContext || {};
|
|
737
|
+
return {
|
|
738
|
+
approved: supplied.approved === true,
|
|
739
|
+
approvalId: supplied.approvalId,
|
|
740
|
+
target: {
|
|
741
|
+
process:
|
|
742
|
+
supplied.target?.process ??
|
|
743
|
+
supplied.process ??
|
|
744
|
+
spawnOptions.file ??
|
|
745
|
+
spawnOptions.command,
|
|
746
|
+
host: supplied.target?.host ?? supplied.host ?? null,
|
|
747
|
+
},
|
|
748
|
+
ttlMs: supplied.ttlMs,
|
|
749
|
+
maxUses: supplied.maxUses,
|
|
750
|
+
origin: spawnOptions.origin,
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const processTarget = spawnOptions.file ?? spawnOptions.command;
|
|
755
|
+
if (!normalizeProcessTarget(processTarget)) return null;
|
|
756
|
+
|
|
757
|
+
/*
|
|
758
|
+
* Compatibility bridge for the current ProcessExecutionBroker: it invokes
|
|
759
|
+
* applyWithReport only after an allow/elevated decision but does not yet
|
|
760
|
+
* forward the decision id. The intended integration is to pass the result
|
|
761
|
+
* of createBrokerContext() as credentialContext.
|
|
762
|
+
*/
|
|
763
|
+
return {
|
|
764
|
+
approved: spawnOptions.credentialApproved !== false,
|
|
765
|
+
approvalId:
|
|
766
|
+
spawnOptions.credentialApprovalId ||
|
|
767
|
+
`broker-boundary:${spawnOptions.origin || "unknown"}`,
|
|
768
|
+
target: {
|
|
769
|
+
process: processTarget,
|
|
770
|
+
host:
|
|
771
|
+
spawnOptions.credentialTarget?.host ??
|
|
772
|
+
spawnOptions.credentialTargetHost ??
|
|
773
|
+
null,
|
|
774
|
+
},
|
|
775
|
+
ttlMs: spawnOptions.credentialTtlMs,
|
|
776
|
+
maxUses: spawnOptions.credentialMaxUses,
|
|
777
|
+
origin: spawnOptions.origin,
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
_canIssueForContext(context) {
|
|
782
|
+
return Boolean(
|
|
783
|
+
context?.approved === true &&
|
|
784
|
+
context.approvalId &&
|
|
785
|
+
normalizeProcessTarget(context.target?.process ?? context.process),
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
_setReferenceMarkers(safeEnv) {
|
|
790
|
+
safeEnv.CC_CREDENTIAL_AGENT_ID = this._agentId;
|
|
791
|
+
safeEnv.CC_CREDENTIAL_TRANSPORT = this._transport;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Strip sensitive and reserved environment variables from spawn options.
|
|
796
|
+
*/
|
|
797
|
+
filterEnvironment(env = process.env, context = null) {
|
|
798
|
+
const safeEnv = {};
|
|
164
799
|
const redacted = [];
|
|
800
|
+
const reservedRemoved = [];
|
|
801
|
+
const enabled = this.isEnabled();
|
|
802
|
+
let refsIssued = 0;
|
|
165
803
|
|
|
166
|
-
for (
|
|
167
|
-
|
|
804
|
+
for (const [key, value] of Object.entries(env || {})) {
|
|
805
|
+
if (this.isReservedAgentKey(key)) {
|
|
806
|
+
reservedRemoved.push(key);
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (!this.isSensitiveKey(key)) {
|
|
810
|
+
safeEnv[key] = value;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const entry = { key, refIssued: false };
|
|
815
|
+
if (
|
|
816
|
+
enabled &&
|
|
817
|
+
this._canIssueForContext(context) &&
|
|
818
|
+
value !== undefined &&
|
|
819
|
+
value !== null
|
|
820
|
+
) {
|
|
821
|
+
const refId = this.issueCredentialRef({
|
|
822
|
+
...context,
|
|
823
|
+
key,
|
|
824
|
+
value,
|
|
825
|
+
});
|
|
826
|
+
safeEnv[`CC_CRED_REF_${key}`] = refId;
|
|
827
|
+
entry.refIssued = true;
|
|
828
|
+
refsIssued += 1;
|
|
829
|
+
}
|
|
830
|
+
redacted.push(entry);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
if (refsIssued > 0) this._setReferenceMarkers(safeEnv);
|
|
834
|
+
return {
|
|
835
|
+
safeEnv,
|
|
836
|
+
redacted,
|
|
837
|
+
reservedRemoved,
|
|
838
|
+
refsIssued,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
_sanitizeArgsWithSecrets(args = []) {
|
|
843
|
+
const sanitizedArgs = [];
|
|
844
|
+
const redacted = [];
|
|
845
|
+
const inputArgs = Array.isArray(args) ? args : [];
|
|
846
|
+
const secretOption =
|
|
847
|
+
/^--[^=]*(?:token|api[-_]?key|secret|password|passwd|credential)[^=]*$/i;
|
|
848
|
+
|
|
849
|
+
for (let index = 0; index < inputArgs.length; index += 1) {
|
|
850
|
+
const arg = String(inputArgs[index]);
|
|
851
|
+
const equalsIndex = arg.indexOf("=");
|
|
852
|
+
const optionName = equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
|
|
168
853
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
854
|
+
if (equalsIndex >= 0 && secretOption.test(optionName)) {
|
|
855
|
+
sanitizedArgs.push(`${optionName}=${REDACTED_VALUE}`);
|
|
856
|
+
redacted.push({
|
|
857
|
+
index,
|
|
858
|
+
pattern: "arg-with-secret",
|
|
859
|
+
secrets: [
|
|
860
|
+
{
|
|
861
|
+
key: optionName.replace(/^--/, "") || `argv-${index}`,
|
|
862
|
+
value: arg.slice(equalsIndex + 1),
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
});
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (secretOption.test(arg) && index + 1 < inputArgs.length) {
|
|
870
|
+
sanitizedArgs.push(arg, REDACTED_VALUE);
|
|
871
|
+
redacted.push({
|
|
872
|
+
index: index + 1,
|
|
873
|
+
pattern: "arg-after-secret-option",
|
|
874
|
+
secrets: [
|
|
875
|
+
{
|
|
876
|
+
key: arg.replace(/^--/, "") || `argv-${index + 1}`,
|
|
877
|
+
value: String(inputArgs[index + 1]),
|
|
878
|
+
},
|
|
879
|
+
],
|
|
880
|
+
});
|
|
881
|
+
index += 1;
|
|
173
882
|
continue;
|
|
174
883
|
}
|
|
175
884
|
|
|
176
|
-
// Check for -H "Authorization: Bearer xxx"
|
|
177
885
|
if (arg === "-H" || /^--header$/i.test(arg)) {
|
|
178
|
-
|
|
179
|
-
if (
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
886
|
+
sanitizedArgs.push(arg);
|
|
887
|
+
if (index + 1 < inputArgs.length) {
|
|
888
|
+
const headerArg = String(inputArgs[index + 1]);
|
|
889
|
+
const headerMatch = headerArg.match(
|
|
890
|
+
/^(Authorization|Proxy-Authorization|X-API-Key|Cookie|Set-Cookie)\s*:\s*(.*)$/i,
|
|
891
|
+
);
|
|
892
|
+
if (headerMatch) {
|
|
893
|
+
sanitizedArgs.push(`${headerMatch[1]}: ${REDACTED_VALUE}`);
|
|
894
|
+
redacted.push({
|
|
895
|
+
index: index + 1,
|
|
896
|
+
pattern: "header-with-secret",
|
|
897
|
+
secrets: [
|
|
898
|
+
{
|
|
899
|
+
key: `header-${headerMatch[1].toLowerCase()}`,
|
|
900
|
+
value: headerMatch[2],
|
|
901
|
+
},
|
|
902
|
+
],
|
|
903
|
+
});
|
|
904
|
+
index += 1;
|
|
186
905
|
}
|
|
187
906
|
}
|
|
188
907
|
continue;
|
|
189
908
|
}
|
|
190
909
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
);
|
|
196
|
-
redacted.push({
|
|
910
|
+
const directHeaderMatch = arg.match(
|
|
911
|
+
/^(Authorization|Proxy-Authorization|X-API-Key|Cookie|Set-Cookie)\s*:\s*(.*)$/i,
|
|
912
|
+
);
|
|
913
|
+
if (directHeaderMatch) {
|
|
914
|
+
sanitizedArgs.push(`${directHeaderMatch[1]}: ${REDACTED_VALUE}`);
|
|
915
|
+
redacted.push({
|
|
916
|
+
index,
|
|
917
|
+
pattern: "header-with-secret",
|
|
918
|
+
secrets: [
|
|
919
|
+
{
|
|
920
|
+
key: `header-${directHeaderMatch[1].toLowerCase()}`,
|
|
921
|
+
value: directHeaderMatch[2],
|
|
922
|
+
},
|
|
923
|
+
],
|
|
924
|
+
});
|
|
197
925
|
continue;
|
|
198
926
|
}
|
|
199
927
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
928
|
+
const inlineSecrets = [];
|
|
929
|
+
let sanitized = arg.replace(
|
|
930
|
+
/Bearer\s+([A-Za-z0-9._-]{20,})/g,
|
|
931
|
+
(_match, value) => {
|
|
932
|
+
inlineSecrets.push({
|
|
933
|
+
key: `argv-${index}-bearer`,
|
|
934
|
+
value,
|
|
935
|
+
});
|
|
936
|
+
return `Bearer ${REDACTED_VALUE}`;
|
|
937
|
+
},
|
|
938
|
+
);
|
|
939
|
+
sanitized = sanitized.replace(
|
|
940
|
+
/\bsk-([A-Za-z0-9]{20,})\b/g,
|
|
941
|
+
(_match, value) => {
|
|
942
|
+
inlineSecrets.push({
|
|
943
|
+
key: `argv-${index}-sk-key`,
|
|
944
|
+
value: `sk-${value}`,
|
|
945
|
+
});
|
|
946
|
+
return `sk-${REDACTED_VALUE}`;
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
sanitizedArgs.push(sanitized);
|
|
950
|
+
if (inlineSecrets.length > 0) {
|
|
951
|
+
redacted.push({
|
|
952
|
+
index,
|
|
953
|
+
pattern: "inline-secret",
|
|
954
|
+
secrets: inlineSecrets,
|
|
955
|
+
});
|
|
207
956
|
}
|
|
208
|
-
|
|
209
|
-
sanitized.push(arg);
|
|
210
957
|
}
|
|
211
958
|
|
|
212
|
-
return { sanitizedArgs
|
|
959
|
+
return { sanitizedArgs, redacted };
|
|
213
960
|
}
|
|
214
961
|
|
|
215
962
|
/**
|
|
216
|
-
*
|
|
217
|
-
|
|
963
|
+
* Sanitize command line arguments without exposing captured values.
|
|
964
|
+
*/
|
|
965
|
+
sanitizeArgs(args = []) {
|
|
966
|
+
const { sanitizedArgs, redacted } = this._sanitizeArgsWithSecrets(args);
|
|
967
|
+
return {
|
|
968
|
+
sanitizedArgs,
|
|
969
|
+
redacted: redacted.map(({ index, pattern }) => ({
|
|
970
|
+
index,
|
|
971
|
+
pattern,
|
|
972
|
+
})),
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/**
|
|
977
|
+
* Apply filtering and issue references for a broker-approved spawn.
|
|
218
978
|
*/
|
|
219
979
|
applyWithReport(spawnOptions) {
|
|
980
|
+
if (!spawnOptions || typeof spawnOptions !== "object") {
|
|
981
|
+
throw credentialError(
|
|
982
|
+
CREDENTIAL_ERROR_CODES.INVALID_REQUEST,
|
|
983
|
+
"Spawn options are required",
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const context = this._contextForSpawn(spawnOptions);
|
|
220
988
|
const env = spawnOptions.env || process.env;
|
|
221
|
-
const {
|
|
222
|
-
|
|
989
|
+
const {
|
|
990
|
+
safeEnv,
|
|
991
|
+
redacted: envRedacted,
|
|
992
|
+
reservedRemoved,
|
|
993
|
+
refsIssued: envRefsIssued,
|
|
994
|
+
} = this.filterEnvironment(env, context);
|
|
223
995
|
|
|
224
996
|
const args = spawnOptions.args || [];
|
|
225
|
-
const { sanitizedArgs, redacted:
|
|
997
|
+
const { sanitizedArgs, redacted: internalArgRedacted } =
|
|
998
|
+
this._sanitizeArgsWithSecrets(args);
|
|
999
|
+
|
|
1000
|
+
let argRefsIssued = 0;
|
|
1001
|
+
if (this.isEnabled() && this._canIssueForContext(context)) {
|
|
1002
|
+
for (const entry of internalArgRedacted) {
|
|
1003
|
+
for (
|
|
1004
|
+
let secretIndex = 0;
|
|
1005
|
+
secretIndex < entry.secrets.length;
|
|
1006
|
+
secretIndex += 1
|
|
1007
|
+
) {
|
|
1008
|
+
const secret = entry.secrets[secretIndex];
|
|
1009
|
+
const refId = this.issueCredentialRef({
|
|
1010
|
+
...context,
|
|
1011
|
+
key: secret.key,
|
|
1012
|
+
value: secret.value,
|
|
1013
|
+
});
|
|
1014
|
+
const suffix = secretIndex === 0 ? "" : `_${secretIndex}`;
|
|
1015
|
+
safeEnv[`CC_CRED_ARG_REF_${entry.index}${suffix}`] = refId;
|
|
1016
|
+
argRefsIssued += 1;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
const refsIssued = envRefsIssued + argRefsIssued;
|
|
1022
|
+
if (refsIssued > 0) this._setReferenceMarkers(safeEnv);
|
|
1023
|
+
spawnOptions.env = safeEnv;
|
|
226
1024
|
spawnOptions.args = sanitizedArgs;
|
|
227
1025
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
1026
|
+
const envKeys = envRedacted.map((entry) => entry.key);
|
|
1027
|
+
const argIndexes = internalArgRedacted.map((entry) => entry.index);
|
|
1028
|
+
const filtered =
|
|
1029
|
+
envRedacted.length > 0 ||
|
|
1030
|
+
internalArgRedacted.length > 0 ||
|
|
1031
|
+
reservedRemoved.length > 0;
|
|
1032
|
+
const enabled = this.isEnabled();
|
|
1033
|
+
|
|
1034
|
+
if (filtered) {
|
|
1035
|
+
this._recordAudit({
|
|
1036
|
+
event: "credential_boundary_apply",
|
|
1037
|
+
outcome: "allowed",
|
|
1038
|
+
origin: String(spawnOptions.origin || "unknown"),
|
|
1039
|
+
target: context
|
|
1040
|
+
? {
|
|
1041
|
+
process: normalizeProcessTarget(
|
|
1042
|
+
context.target?.process ?? context.process,
|
|
1043
|
+
),
|
|
1044
|
+
host: normalizeHostTarget(context.target?.host ?? context.host),
|
|
1045
|
+
}
|
|
1046
|
+
: null,
|
|
1047
|
+
envRedacted: envKeys,
|
|
1048
|
+
argRedacted: argIndexes,
|
|
1049
|
+
reservedEnvRemoved: reservedRemoved,
|
|
1050
|
+
refsIssued,
|
|
1051
|
+
mode: enabled
|
|
1052
|
+
? refsIssued > 0
|
|
1053
|
+
? "broker-reference"
|
|
1054
|
+
: "redact-only"
|
|
1055
|
+
: "redact-only-disabled",
|
|
236
1056
|
});
|
|
237
|
-
if (this._accessLog.length > 10000) this._accessLog.shift();
|
|
238
1057
|
}
|
|
239
1058
|
|
|
240
1059
|
return {
|
|
241
1060
|
spawnOptions,
|
|
242
1061
|
report: {
|
|
243
|
-
envKeys
|
|
244
|
-
argIndexes
|
|
1062
|
+
envKeys,
|
|
1063
|
+
argIndexes,
|
|
245
1064
|
envCount: envRedacted.length,
|
|
246
|
-
argCount:
|
|
247
|
-
filtered
|
|
1065
|
+
argCount: internalArgRedacted.length,
|
|
1066
|
+
filtered,
|
|
1067
|
+
refsIssued,
|
|
1068
|
+
agentDisabled: !enabled,
|
|
1069
|
+
mode: enabled
|
|
1070
|
+
? refsIssued > 0
|
|
1071
|
+
? "broker-reference"
|
|
1072
|
+
: "redact-only"
|
|
1073
|
+
: "redact-only-disabled",
|
|
1074
|
+
targetBound: refsIssued > 0,
|
|
248
1075
|
},
|
|
249
1076
|
};
|
|
250
1077
|
}
|
|
@@ -254,41 +1081,57 @@ class CredentialAgent {
|
|
|
254
1081
|
return this.applyWithReport(spawnOptions).spawnOptions;
|
|
255
1082
|
}
|
|
256
1083
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
key: cred.key,
|
|
269
|
-
});
|
|
270
|
-
return cred.value;
|
|
1084
|
+
getAuditLog(limit = this._accessLog.length) {
|
|
1085
|
+
const safeLimit = Math.max(
|
|
1086
|
+
0,
|
|
1087
|
+
Math.min(Number(limit) || 0, this._accessLog.length),
|
|
1088
|
+
);
|
|
1089
|
+
if (safeLimit === 0) return [];
|
|
1090
|
+
return this._accessLog.slice(-safeLimit).map(cloneAuditEntry);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
clearAuditLog() {
|
|
1094
|
+
this._accessLog = [];
|
|
271
1095
|
}
|
|
272
1096
|
|
|
273
1097
|
/**
|
|
274
|
-
* Get credential agent info for status reporting
|
|
1098
|
+
* Get credential agent info for status reporting. No raw references or
|
|
1099
|
+
* credential values are included.
|
|
275
1100
|
*/
|
|
276
1101
|
getInfo() {
|
|
1102
|
+
this.isEnabled();
|
|
1103
|
+
this._expireStaleCredentials();
|
|
1104
|
+
const statusCounts = {};
|
|
1105
|
+
for (const record of this._credentialStore.values()) {
|
|
1106
|
+
statusCounts[record.status] = (statusCounts[record.status] || 0) + 1;
|
|
1107
|
+
}
|
|
277
1108
|
return {
|
|
278
1109
|
agentId: this._agentId,
|
|
1110
|
+
enabled: this._lastEnabledState,
|
|
1111
|
+
mode: this._lastEnabledState
|
|
1112
|
+
? "broker-reference"
|
|
1113
|
+
: "redact-only-disabled",
|
|
1114
|
+
transport: this._transport,
|
|
1115
|
+
proxyReady: this._proxyReady,
|
|
1116
|
+
ipcAvailable: false,
|
|
279
1117
|
credentialsTracked: this._credentialStore.size,
|
|
1118
|
+
activeCredentials: statusCounts.active || 0,
|
|
280
1119
|
accessCount: this._accessLog.length,
|
|
281
1120
|
sensitivePatterns: SENSITIVE_ENV_PATTERNS.length,
|
|
282
1121
|
allowlistSize: ENV_ALLOWLIST.size,
|
|
1122
|
+
defaultTtlMs: this._defaultTtlMs,
|
|
1123
|
+
defaultMaxUses: this._defaultMaxUses,
|
|
283
1124
|
defaultOn: true,
|
|
284
1125
|
};
|
|
285
1126
|
}
|
|
286
1127
|
}
|
|
287
1128
|
|
|
288
1129
|
const credentialAgent = new CredentialAgent();
|
|
1130
|
+
|
|
289
1131
|
export {
|
|
290
1132
|
credentialAgent,
|
|
291
1133
|
CredentialAgent,
|
|
1134
|
+
CREDENTIAL_ERROR_CODES,
|
|
292
1135
|
SENSITIVE_ENV_PATTERNS,
|
|
293
1136
|
ENV_ALLOWLIST,
|
|
294
1137
|
};
|