@theokit/sdk 4.53.0 → 4.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -8
- package/LICENSE +2 -2
- package/README.md +5 -5
- package/bin/theokit-migrate-config.mjs +11 -2
- package/dist/{agent-BiCINq25.d.ts → agent-B9HXlwID.d.ts} +21 -8
- package/dist/agent-FFVV4U5N.js +50 -0
- package/dist/{agent-ZAGG6ZBS.js.map → agent-FFVV4U5N.js.map} +1 -1
- package/dist/agent-YLAHVHEH.cjs +59 -0
- package/dist/{agent-JX5SBYDE.cjs.map → agent-YLAHVHEH.cjs.map} +1 -1
- package/dist/{agent-Zta1kvGH.d.cts → agent-nLqeXKIX.d.cts} +21 -8
- package/dist/agent-session-store-DUGBAX5V.cjs +23 -0
- package/dist/{agent-session-store-EPX2WQI4.cjs.map → agent-session-store-DUGBAX5V.cjs.map} +1 -1
- package/dist/agent-session-store-TNCGL5RN.js +6 -0
- package/dist/{agent-session-store-JMU7ASDB.js.map → agent-session-store-TNCGL5RN.js.map} +1 -1
- package/dist/agent.d.ts +16 -1
- package/dist/auth/index.cjs +18 -18
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +6 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/{batch-NLIS4QTW.js → batch-UPKN4SG6.js} +9 -9
- package/dist/{batch-NLIS4QTW.js.map → batch-UPKN4SG6.js.map} +1 -1
- package/dist/{batch-WQ3AJCDV.cjs → batch-ZEFN5SO7.cjs} +26 -26
- package/dist/{batch-WQ3AJCDV.cjs.map → batch-ZEFN5SO7.cjs.map} +1 -1
- package/dist/{chunk-2QMK627M.cjs → chunk-2D43RD5D.cjs} +8 -8
- package/dist/{chunk-2QMK627M.cjs.map → chunk-2D43RD5D.cjs.map} +1 -1
- package/dist/{chunk-DM6Y5B2G.cjs → chunk-2FYEVT2B.cjs} +121 -121
- package/dist/{chunk-DM6Y5B2G.cjs.map → chunk-2FYEVT2B.cjs.map} +1 -1
- package/dist/{chunk-BBZYXVLZ.cjs → chunk-2ZPEDVLM.cjs} +2 -2
- package/dist/chunk-2ZPEDVLM.cjs.map +1 -0
- package/dist/{chunk-T6QUCG7L.cjs → chunk-3BK6YR5P.cjs} +4 -4
- package/dist/{chunk-T6QUCG7L.cjs.map → chunk-3BK6YR5P.cjs.map} +1 -1
- package/dist/{chunk-23VZBRDQ.js → chunk-3V7UJLQT.js} +3 -3
- package/dist/chunk-3V7UJLQT.js.map +1 -0
- package/dist/{chunk-ITQ4NO4P.js → chunk-3WVMJY7F.js} +3 -3
- package/dist/{chunk-ITQ4NO4P.js.map → chunk-3WVMJY7F.js.map} +1 -1
- package/dist/{chunk-RAACTJ7C.cjs → chunk-3X6PM7DU.cjs} +4 -4
- package/dist/{chunk-RAACTJ7C.cjs.map → chunk-3X6PM7DU.cjs.map} +1 -1
- package/dist/{chunk-DEZ75ET5.js → chunk-4Q4K4WB5.js} +3 -3
- package/dist/{chunk-DEZ75ET5.js.map → chunk-4Q4K4WB5.js.map} +1 -1
- package/dist/{chunk-4X3SBHPK.js → chunk-5F5NWKPN.js} +20 -4
- package/dist/chunk-5F5NWKPN.js.map +1 -0
- package/dist/{chunk-NUEL6RCZ.cjs → chunk-5YKNEMXR.cjs} +4 -4
- package/dist/chunk-5YKNEMXR.cjs.map +1 -0
- package/dist/{chunk-3YYDHUNH.cjs → chunk-665SFFFR.cjs} +11 -11
- package/dist/{chunk-3YYDHUNH.cjs.map → chunk-665SFFFR.cjs.map} +1 -1
- package/dist/{chunk-CHVSMEKM.js → chunk-6AZDM6LK.js} +3 -3
- package/dist/chunk-6AZDM6LK.js.map +1 -0
- package/dist/{chunk-AMYY3JHQ.cjs → chunk-ADLLFY3H.cjs} +9 -9
- package/dist/{chunk-AMYY3JHQ.cjs.map → chunk-ADLLFY3H.cjs.map} +1 -1
- package/dist/{chunk-634YVZLU.js → chunk-B6HOIWQ6.js} +5 -5
- package/dist/{chunk-634YVZLU.js.map → chunk-B6HOIWQ6.js.map} +1 -1
- package/dist/{chunk-EQMLJ52C.cjs → chunk-BDTVFIPB.cjs} +26 -26
- package/dist/{chunk-EQMLJ52C.cjs.map → chunk-BDTVFIPB.cjs.map} +1 -1
- package/dist/{chunk-UJ3SXRZZ.js → chunk-BERBYXFH.js} +3 -3
- package/dist/{chunk-UJ3SXRZZ.js.map → chunk-BERBYXFH.js.map} +1 -1
- package/dist/{chunk-KZBB4YKU.js → chunk-BFYGJGC5.js} +3 -3
- package/dist/{chunk-KZBB4YKU.js.map → chunk-BFYGJGC5.js.map} +1 -1
- package/dist/{chunk-SXSPMRSV.cjs → chunk-BM6N4KHO.cjs} +21 -3
- package/dist/chunk-BM6N4KHO.cjs.map +1 -0
- package/dist/{chunk-2CFEET3Y.cjs → chunk-C4H6PB5J.cjs} +4 -4
- package/dist/chunk-C4H6PB5J.cjs.map +1 -0
- package/dist/{chunk-NFATC6ZF.cjs → chunk-C5DEO6OI.cjs} +4 -4
- package/dist/{chunk-NFATC6ZF.cjs.map → chunk-C5DEO6OI.cjs.map} +1 -1
- package/dist/{chunk-EUBGGYPH.cjs → chunk-CZ2GKKYL.cjs} +5 -5
- package/dist/{chunk-EUBGGYPH.cjs.map → chunk-CZ2GKKYL.cjs.map} +1 -1
- package/dist/{chunk-I732VEDW.js → chunk-D24WJWN2.js} +3 -3
- package/dist/{chunk-I732VEDW.js.map → chunk-D24WJWN2.js.map} +1 -1
- package/dist/{chunk-3R4ZCQAZ.cjs → chunk-D75S2X6I.cjs} +5 -5
- package/dist/{chunk-3R4ZCQAZ.cjs.map → chunk-D75S2X6I.cjs.map} +1 -1
- package/dist/{chunk-5XXUYU3M.js → chunk-DGOG5NXR.js} +3 -3
- package/dist/{chunk-5XXUYU3M.js.map → chunk-DGOG5NXR.js.map} +1 -1
- package/dist/{chunk-RTLUZKDL.cjs → chunk-E7FJOO4G.cjs} +11 -11
- package/dist/{chunk-RTLUZKDL.cjs.map → chunk-E7FJOO4G.cjs.map} +1 -1
- package/dist/{chunk-BWGXYOBZ.cjs → chunk-ELKO4ZLR.cjs} +4 -4
- package/dist/{chunk-BWGXYOBZ.cjs.map → chunk-ELKO4ZLR.cjs.map} +1 -1
- package/dist/{chunk-FBZMSLDC.cjs → chunk-EZ2YEF4F.cjs} +4 -4
- package/dist/{chunk-FBZMSLDC.cjs.map → chunk-EZ2YEF4F.cjs.map} +1 -1
- package/dist/{chunk-2VFZQEDW.cjs → chunk-F77UENR6.cjs} +343 -324
- package/dist/chunk-F77UENR6.cjs.map +1 -0
- package/dist/{chunk-OZXPA3ME.js → chunk-FFM3SJTQ.js} +4 -4
- package/dist/{chunk-OZXPA3ME.js.map → chunk-FFM3SJTQ.js.map} +1 -1
- package/dist/{chunk-PLUCC4N5.js → chunk-FIOCJYLA.js} +3 -3
- package/dist/{chunk-PLUCC4N5.js.map → chunk-FIOCJYLA.js.map} +1 -1
- package/dist/{chunk-Z5U2JGEK.cjs → chunk-FOXKFTVZ.cjs} +11 -11
- package/dist/{chunk-Z5U2JGEK.cjs.map → chunk-FOXKFTVZ.cjs.map} +1 -1
- package/dist/{chunk-K42QGAKM.cjs → chunk-FPYT5WZP.cjs} +10 -10
- package/dist/{chunk-K42QGAKM.cjs.map → chunk-FPYT5WZP.cjs.map} +1 -1
- package/dist/{chunk-NZOR3N4E.js → chunk-G3JWXOGY.js} +78 -58
- package/dist/chunk-G3JWXOGY.js.map +1 -0
- package/dist/{chunk-DHVNJYTO.js → chunk-GBBDINFK.js} +3 -3
- package/dist/{chunk-DHVNJYTO.js.map → chunk-GBBDINFK.js.map} +1 -1
- package/dist/{chunk-V45L2OYP.cjs → chunk-GD63JPGC.cjs} +9 -9
- package/dist/{chunk-V45L2OYP.cjs.map → chunk-GD63JPGC.cjs.map} +1 -1
- package/dist/{chunk-I5OTK2RP.js → chunk-GOVJO4YE.js} +5 -5
- package/dist/{chunk-I5OTK2RP.js.map → chunk-GOVJO4YE.js.map} +1 -1
- package/dist/{chunk-CZHPR2G7.cjs → chunk-HAWE74GQ.cjs} +8 -8
- package/dist/{chunk-CZHPR2G7.cjs.map → chunk-HAWE74GQ.cjs.map} +1 -1
- package/dist/{chunk-KIX45IKP.js → chunk-HE4BIAOS.js} +4 -4
- package/dist/{chunk-KIX45IKP.js.map → chunk-HE4BIAOS.js.map} +1 -1
- package/dist/{chunk-AM2CNBKE.js → chunk-HKCKOAIO.js} +2 -2
- package/dist/chunk-HKCKOAIO.js.map +1 -0
- package/dist/{chunk-2YWWPCGX.js → chunk-HSQ5Q3I5.js} +10 -12
- package/dist/chunk-HSQ5Q3I5.js.map +1 -0
- package/dist/{chunk-QLAWEGTZ.cjs → chunk-JIQRXKTA.cjs} +7 -7
- package/dist/chunk-JIQRXKTA.cjs.map +1 -0
- package/dist/{chunk-EFXJ5C7X.js → chunk-KSWHTR54.js} +5 -5
- package/dist/{chunk-EFXJ5C7X.js.map → chunk-KSWHTR54.js.map} +1 -1
- package/dist/{chunk-6PTCKXD3.js → chunk-LD4MOBFX.js} +8 -8
- package/dist/{chunk-6PTCKXD3.js.map → chunk-LD4MOBFX.js.map} +1 -1
- package/dist/{chunk-2YGIBOUH.cjs → chunk-LPVIRJVF.cjs} +9 -9
- package/dist/chunk-LPVIRJVF.cjs.map +1 -0
- package/dist/{chunk-2Y5NO2SY.js → chunk-LS3LMQ23.js} +9 -9
- package/dist/{chunk-2Y5NO2SY.js.map → chunk-LS3LMQ23.js.map} +1 -1
- package/dist/{chunk-N43XLTHZ.js → chunk-LTSTGCL6.js} +3 -3
- package/dist/{chunk-N43XLTHZ.js.map → chunk-LTSTGCL6.js.map} +1 -1
- package/dist/{chunk-W4FV7JBH.js → chunk-MSFLYJDY.js} +4 -4
- package/dist/chunk-MSFLYJDY.js.map +1 -0
- package/dist/{chunk-C4EU627W.js → chunk-N5CR3TJZ.js} +4 -4
- package/dist/{chunk-C4EU627W.js.map → chunk-N5CR3TJZ.js.map} +1 -1
- package/dist/{chunk-HTK54K2J.js → chunk-NZL4BNFQ.js} +3 -3
- package/dist/{chunk-HTK54K2J.js.map → chunk-NZL4BNFQ.js.map} +1 -1
- package/dist/{chunk-T5ZWI3MC.cjs → chunk-OUOXM36O.cjs} +11 -11
- package/dist/{chunk-T5ZWI3MC.cjs.map → chunk-OUOXM36O.cjs.map} +1 -1
- package/dist/{chunk-BXVSBYGW.js → chunk-OY7O3Q6R.js} +5 -5
- package/dist/{chunk-BXVSBYGW.js.map → chunk-OY7O3Q6R.js.map} +1 -1
- package/dist/{chunk-L7EGRCKJ.js → chunk-PBZ7HMBP.js} +4 -4
- package/dist/{chunk-L7EGRCKJ.js.map → chunk-PBZ7HMBP.js.map} +1 -1
- package/dist/{chunk-OIBHY6JQ.js → chunk-PGP7IOJ2.js} +4 -4
- package/dist/{chunk-OIBHY6JQ.js.map → chunk-PGP7IOJ2.js.map} +1 -1
- package/dist/{chunk-R7TKOQMJ.js → chunk-POWRZSK4.js} +3 -3
- package/dist/{chunk-R7TKOQMJ.js.map → chunk-POWRZSK4.js.map} +1 -1
- package/dist/{chunk-R3CKCRK3.cjs → chunk-Q4RESANI.cjs} +4 -4
- package/dist/{chunk-R3CKCRK3.cjs.map → chunk-Q4RESANI.cjs.map} +1 -1
- package/dist/{chunk-QN5N3ZVT.cjs → chunk-QBTVATSF.cjs} +25 -24
- package/dist/chunk-QBTVATSF.cjs.map +1 -0
- package/dist/{chunk-ZSJPRPN7.js → chunk-R5YZACGK.js} +3 -3
- package/dist/{chunk-ZSJPRPN7.js.map → chunk-R5YZACGK.js.map} +1 -1
- package/dist/{chunk-ADVZIC43.cjs → chunk-RAHSWE5C.cjs} +10 -10
- package/dist/{chunk-ADVZIC43.cjs.map → chunk-RAHSWE5C.cjs.map} +1 -1
- package/dist/{chunk-NOS7PTKP.js → chunk-SEL2ZQMT.js} +7 -7
- package/dist/{chunk-NOS7PTKP.js.map → chunk-SEL2ZQMT.js.map} +1 -1
- package/dist/{chunk-XWCTBGYK.js → chunk-SK4QLUBK.js} +3 -3
- package/dist/chunk-SK4QLUBK.js.map +1 -0
- package/dist/{chunk-XRI6DXPZ.js → chunk-SSDD676S.js} +3 -3
- package/dist/chunk-SSDD676S.js.map +1 -0
- package/dist/{chunk-E3OCRJU6.cjs → chunk-SVO5WPZX.cjs} +11 -11
- package/dist/{chunk-E3OCRJU6.cjs.map → chunk-SVO5WPZX.cjs.map} +1 -1
- package/dist/{chunk-44IISBRZ.cjs → chunk-SZHU7A5B.cjs} +4 -4
- package/dist/{chunk-44IISBRZ.cjs.map → chunk-SZHU7A5B.cjs.map} +1 -1
- package/dist/{chunk-TR4V2LHV.cjs → chunk-TTJ54H3E.cjs} +5 -5
- package/dist/{chunk-TR4V2LHV.cjs.map → chunk-TTJ54H3E.cjs.map} +1 -1
- package/dist/{chunk-HI4ZW62S.js → chunk-U4UJHOSH.js} +4 -4
- package/dist/chunk-U4UJHOSH.js.map +1 -0
- package/dist/{chunk-XGYI2KQH.js → chunk-UFIXH26P.js} +4 -4
- package/dist/{chunk-XGYI2KQH.js.map → chunk-UFIXH26P.js.map} +1 -1
- package/dist/{chunk-JJZ4NIAG.js → chunk-V26QVGUG.js} +7 -6
- package/dist/chunk-V26QVGUG.js.map +1 -0
- package/dist/{chunk-3SHW7XKK.js → chunk-V6HWJQQV.js} +6 -5
- package/dist/chunk-V6HWJQQV.js.map +1 -0
- package/dist/{chunk-MV4TOCUK.js → chunk-VC5ABJXC.js} +3 -3
- package/dist/{chunk-MV4TOCUK.js.map → chunk-VC5ABJXC.js.map} +1 -1
- package/dist/{chunk-33L2NVTJ.cjs → chunk-VF7X6HDR.cjs} +8 -8
- package/dist/chunk-VF7X6HDR.cjs.map +1 -0
- package/dist/{chunk-7HQVDLFI.cjs → chunk-VGPXHHF3.cjs} +11 -9
- package/dist/chunk-VGPXHHF3.cjs.map +1 -0
- package/dist/{chunk-BC5EUG7R.cjs → chunk-VUKQL3L2.cjs} +18 -20
- package/dist/chunk-VUKQL3L2.cjs.map +1 -0
- package/dist/{chunk-PVBANCWU.cjs → chunk-VW6M4B3Z.cjs} +5 -5
- package/dist/{chunk-PVBANCWU.cjs.map → chunk-VW6M4B3Z.cjs.map} +1 -1
- package/dist/{chunk-KNZU4YO5.cjs → chunk-WQ53UQL4.cjs} +4 -4
- package/dist/{chunk-KNZU4YO5.cjs.map → chunk-WQ53UQL4.cjs.map} +1 -1
- package/dist/{chunk-6DCTL32L.cjs → chunk-XPR366DL.cjs} +17 -17
- package/dist/{chunk-6DCTL32L.cjs.map → chunk-XPR366DL.cjs.map} +1 -1
- package/dist/{chunk-7TB5U7RK.js → chunk-XROIW6BH.js} +6 -6
- package/dist/{chunk-7TB5U7RK.js.map → chunk-XROIW6BH.js.map} +1 -1
- package/dist/{chunk-ECPL5RV6.cjs → chunk-Y4AXQOT4.cjs} +8 -8
- package/dist/{chunk-ECPL5RV6.cjs.map → chunk-Y4AXQOT4.cjs.map} +1 -1
- package/dist/{chunk-E6T264NS.js → chunk-Y4I4EQFK.js} +3 -3
- package/dist/{chunk-E6T264NS.js.map → chunk-Y4I4EQFK.js.map} +1 -1
- package/dist/{chunk-FUASIT3E.cjs → chunk-ZN2DGEY3.cjs} +15 -15
- package/dist/{chunk-FUASIT3E.cjs.map → chunk-ZN2DGEY3.cjs.map} +1 -1
- package/dist/{chunk-YTO5BRBD.cjs → chunk-ZT57WTUJ.cjs} +8 -8
- package/dist/chunk-ZT57WTUJ.cjs.map +1 -0
- package/dist/compact-session-4MNKNMO2.js +21 -0
- package/dist/{compact-session-EJ36VH5I.js.map → compact-session-4MNKNMO2.js.map} +1 -1
- package/dist/compact-session-EPFBQ46Z.cjs +58 -0
- package/dist/{compact-session-6GKIS4SD.cjs.map → compact-session-EPFBQ46Z.cjs.map} +1 -1
- package/dist/compaction.cjs +16 -16
- package/dist/compaction.js +3 -3
- package/dist/concurrency.cjs +6 -6
- package/dist/concurrency.js +4 -4
- package/dist/context-7USNN6LP.cjs +22 -0
- package/dist/{context-4HGPCOH6.cjs.map → context-7USNN6LP.cjs.map} +1 -1
- package/dist/context-ENXZNF6J.js +5 -0
- package/dist/{context-DCECDKWN.js.map → context-ENXZNF6J.js.map} +1 -1
- package/dist/{cron-DyWQsEG6.d.ts → cron-BMjgFH-_.d.ts} +1 -1
- package/dist/{cron-DgHJnMAK.d.cts → cron-DFDdCdMM.d.cts} +1 -1
- package/dist/cron.cjs +33 -33
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +32 -32
- package/dist/errors.cjs +21 -21
- package/dist/errors.js +2 -2
- package/dist/eval.cjs +43 -43
- package/dist/eval.js +33 -33
- package/dist/{executor-4SW7QGZ4.cjs → executor-2Z3LGG3I.cjs} +24 -24
- package/dist/{executor-4SW7QGZ4.cjs.map → executor-2Z3LGG3I.cjs.map} +1 -1
- package/dist/{executor-3CGPLVJL.js → executor-2Z6XJJJG.js} +7 -7
- package/dist/{executor-3CGPLVJL.js.map → executor-2Z6XJJJG.js.map} +1 -1
- package/dist/filesystem/index.cjs +7 -7
- package/dist/filesystem/index.js +3 -3
- package/dist/fs-session-store-R5FD3EN6.js +10 -0
- package/dist/{fs-session-store-LIKYT24K.js.map → fs-session-store-R5FD3EN6.js.map} +1 -1
- package/dist/fs-session-store-VVY2AHWG.cjs +19 -0
- package/dist/{fs-session-store-VXXDIBGM.cjs.map → fs-session-store-VVY2AHWG.cjs.map} +1 -1
- package/dist/generate-object-AVXOJK2I.cjs +20 -0
- package/dist/{generate-object-E465DX5I.cjs.map → generate-object-AVXOJK2I.cjs.map} +1 -1
- package/dist/generate-object-WQRCXFD2.js +7 -0
- package/dist/{generate-object-TVQCW2IZ.js.map → generate-object-WQRCXFD2.js.map} +1 -1
- package/dist/index-manager-VRU3A2YL.cjs +21 -0
- package/dist/{index-manager-HGGL4DD5.cjs.map → index-manager-VRU3A2YL.cjs.map} +1 -1
- package/dist/index-manager-WS7TVKUW.js +12 -0
- package/dist/{index-manager-EA6FGIQG.js.map → index-manager-WS7TVKUW.js.map} +1 -1
- package/dist/index.cjs +143 -135
- package/dist/index.d.cts +64 -8
- package/dist/index.d.ts +64 -8
- package/dist/index.js +45 -45
- package/dist/{inject-session-67SF65FC.cjs → inject-session-AUQLBDRS.cjs} +9 -9
- package/dist/{inject-session-67SF65FC.cjs.map → inject-session-AUQLBDRS.cjs.map} +1 -1
- package/dist/{inject-session-HO7FYVCX.js → inject-session-GMBMJVNZ.js} +7 -7
- package/dist/{inject-session-HO7FYVCX.js.map → inject-session-GMBMJVNZ.js.map} +1 -1
- package/dist/internal/auth/auth-types.d.ts +1 -1
- package/dist/internal/auth/oauth-device.d.ts +2 -2
- package/dist/internal/local-agent/mcp-pool.d.ts +2 -4
- package/dist/internal/mcp/oauth.d.ts +14 -1
- package/dist/internal/mcp/token-storage.d.ts +18 -1
- package/dist/internal/memory/adapters/index.cjs +8 -8
- package/dist/internal/memory/adapters/index.js +7 -7
- package/dist/internal/persistence/atomic-write.d.cts +93 -0
- package/dist/internal/persistence/atomic-write.d.ts +93 -0
- package/dist/internal/persistence/file-lock.d.cts +13 -1
- package/dist/internal/persistence/file-lock.d.ts +13 -1
- package/dist/internal/persistence/fts5-sanitize.d.cts +9 -1
- package/dist/internal/persistence/fts5-sanitize.d.ts +9 -1
- package/dist/internal/persistence/index.cjs +25 -25
- package/dist/internal/persistence/index.js +6 -6
- package/dist/internal/persistence/session-dir.d.cts +1 -0
- package/dist/internal/persistence/session-dir.d.ts +1 -0
- package/dist/internal/persistence/session-writer.d.cts +13 -0
- package/dist/internal/persistence/session-writer.d.ts +13 -0
- package/dist/internal/persistence/sqlite-wal.d.cts +9 -1
- package/dist/internal/persistence/sqlite-wal.d.ts +9 -1
- package/dist/internal/providers/builtin/cerebras.d.ts +2 -2
- package/dist/internal/providers/builtin/deepinfra.d.ts +1 -1
- package/dist/internal/providers/catalog-source-models-dev.d.ts +1 -1
- package/dist/internal/runtime/lifecycle/env-policy.d.ts +1 -3
- package/dist/internal/security/index.cjs +13 -13
- package/dist/internal/security/index.js +3 -3
- package/dist/internal/security/path-guard.d.cts +61 -0
- package/dist/internal/security/path-guard.d.ts +61 -0
- package/dist/internal/session/compact-session.d.ts +2 -2
- package/dist/internal/telemetry/tracer.d.ts +24 -0
- package/dist/judge-call-G5SGV2M2.cjs +22 -0
- package/dist/{judge-call-GVIJWQVE.cjs.map → judge-call-G5SGV2M2.cjs.map} +1 -1
- package/dist/judge-call-MDIPTBJI.js +5 -0
- package/dist/{judge-call-QMKGC2ZK.js.map → judge-call-MDIPTBJI.js.map} +1 -1
- package/dist/mcp-auth.cjs +14 -14
- package/dist/mcp-auth.cjs.map +1 -1
- package/dist/mcp-auth.js +4 -4
- package/dist/mcp-auth.js.map +1 -1
- package/dist/models.cjs +22 -22
- package/dist/models.cjs.map +1 -1
- package/dist/models.js +7 -7
- package/dist/models.js.map +1 -1
- package/dist/path-safety.cjs +10 -10
- package/dist/path-safety.js +3 -3
- package/dist/persistence.cjs +28 -28
- package/dist/persistence.js +8 -8
- package/dist/project.cjs +5 -5
- package/dist/project.js +3 -3
- package/dist/registry-63ASMXT4.cjs +46 -0
- package/dist/{registry-UBY26R4I.cjs.map → registry-63ASMXT4.cjs.map} +1 -1
- package/dist/registry-O7FJ2MPM.js +9 -0
- package/dist/{registry-GC7BQMSV.js.map → registry-O7FJ2MPM.js.map} +1 -1
- package/dist/retry.cjs +4 -4
- package/dist/retry.js +3 -3
- package/dist/sandbox/bwrap.d.cts +1 -2
- package/dist/sandbox/bwrap.d.ts +1 -2
- package/dist/sandbox/index.cjs +20 -20
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +5 -5
- package/dist/sandbox/index.js.map +1 -1
- package/dist/server/errors-envelope.cjs +14 -14
- package/dist/server/errors-envelope.js +3 -3
- package/dist/session-transcript-DOM3UTXR.cjs +50 -0
- package/dist/{session-transcript-TNAJ4O3M.cjs.map → session-transcript-DOM3UTXR.cjs.map} +1 -1
- package/dist/session-transcript-RAKEK76V.js +5 -0
- package/dist/{session-transcript-SKIRBEJE.js.map → session-transcript-RAKEK76V.js.map} +1 -1
- package/dist/skills.cjs +7 -7
- package/dist/skills.js +5 -5
- package/dist/stream-object-L3Q7PBMF.cjs +20 -0
- package/dist/{stream-object-T4DAMNVB.cjs.map → stream-object-L3Q7PBMF.cjs.map} +1 -1
- package/dist/stream-object-ZYXWS4B2.js +7 -0
- package/dist/{stream-object-QMNR3YFF.js.map → stream-object-ZYXWS4B2.js.map} +1 -1
- package/dist/subagents-loader-I56GSKAQ.js +6 -0
- package/dist/{subagents-loader-WN2SB6PV.js.map → subagents-loader-I56GSKAQ.js.map} +1 -1
- package/dist/subagents-loader-VRW73QQT.cjs +15 -0
- package/dist/{subagents-loader-3CXBQWWY.cjs.map → subagents-loader-VRW73QQT.cjs.map} +1 -1
- package/dist/subagents-loader.cjs +6 -6
- package/dist/subagents-loader.d.cts +1 -1
- package/dist/subagents-loader.d.ts +1 -1
- package/dist/subagents-loader.js +4 -4
- package/dist/subscription/index.cjs +3 -3
- package/dist/subscription/index.js +2 -2
- package/dist/task-store.cjs +7 -7
- package/dist/task-store.js +4 -4
- package/dist/types/agent.d.ts +19 -6
- package/dist/types/provider-profile.d.ts +2 -2
- package/dist/workflow.cjs +27 -27
- package/dist/workflow.js +6 -6
- package/package.json +18 -18
- package/dist/agent-JX5SBYDE.cjs +0 -59
- package/dist/agent-ZAGG6ZBS.js +0 -50
- package/dist/agent-session-store-EPX2WQI4.cjs +0 -23
- package/dist/agent-session-store-JMU7ASDB.js +0 -6
- package/dist/chunk-23VZBRDQ.js.map +0 -1
- package/dist/chunk-2CFEET3Y.cjs.map +0 -1
- package/dist/chunk-2VFZQEDW.cjs.map +0 -1
- package/dist/chunk-2YGIBOUH.cjs.map +0 -1
- package/dist/chunk-2YWWPCGX.js.map +0 -1
- package/dist/chunk-33L2NVTJ.cjs.map +0 -1
- package/dist/chunk-3SHW7XKK.js.map +0 -1
- package/dist/chunk-4X3SBHPK.js.map +0 -1
- package/dist/chunk-7HQVDLFI.cjs.map +0 -1
- package/dist/chunk-AM2CNBKE.js.map +0 -1
- package/dist/chunk-BBZYXVLZ.cjs.map +0 -1
- package/dist/chunk-BC5EUG7R.cjs.map +0 -1
- package/dist/chunk-CHVSMEKM.js.map +0 -1
- package/dist/chunk-HI4ZW62S.js.map +0 -1
- package/dist/chunk-JJZ4NIAG.js.map +0 -1
- package/dist/chunk-NUEL6RCZ.cjs.map +0 -1
- package/dist/chunk-NZOR3N4E.js.map +0 -1
- package/dist/chunk-QLAWEGTZ.cjs.map +0 -1
- package/dist/chunk-QN5N3ZVT.cjs.map +0 -1
- package/dist/chunk-SXSPMRSV.cjs.map +0 -1
- package/dist/chunk-W4FV7JBH.js.map +0 -1
- package/dist/chunk-XRI6DXPZ.js.map +0 -1
- package/dist/chunk-XWCTBGYK.js.map +0 -1
- package/dist/chunk-YTO5BRBD.cjs.map +0 -1
- package/dist/compact-session-6GKIS4SD.cjs +0 -58
- package/dist/compact-session-EJ36VH5I.js +0 -21
- package/dist/context-4HGPCOH6.cjs +0 -22
- package/dist/context-DCECDKWN.js +0 -5
- package/dist/fs-session-store-LIKYT24K.js +0 -10
- package/dist/fs-session-store-VXXDIBGM.cjs +0 -19
- package/dist/generate-object-E465DX5I.cjs +0 -20
- package/dist/generate-object-TVQCW2IZ.js +0 -7
- package/dist/index-manager-EA6FGIQG.js +0 -12
- package/dist/index-manager-HGGL4DD5.cjs +0 -21
- package/dist/judge-call-GVIJWQVE.cjs +0 -22
- package/dist/judge-call-QMKGC2ZK.js +0 -5
- package/dist/registry-GC7BQMSV.js +0 -9
- package/dist/registry-UBY26R4I.cjs +0 -46
- package/dist/session-transcript-SKIRBEJE.js +0 -5
- package/dist/session-transcript-TNAJ4O3M.cjs +0 -50
- package/dist/stream-object-QMNR3YFF.js +0 -7
- package/dist/stream-object-T4DAMNVB.cjs +0 -20
- package/dist/subagents-loader-3CXBQWWY.cjs +0 -15
- package/dist/subagents-loader-WN2SB6PV.js +0 -6
- package/docs/error-codes.md +0 -179
- package/docs/harness-capability-map.md +0 -304
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
* @internal
|
|
19
19
|
*/
|
|
20
20
|
import { ConfigurationError } from "../../errors.js";
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when a path operation would escape its allowed base directory.
|
|
23
|
+
* Extends `ConfigurationError` (no new error hierarchy per ADR D65).
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
export declare class PathTraversalError extends ConfigurationError {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
constructor(input: string, resolvedPath: string);
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* Thrown when an agent tool is asked to read or write a sensitive path
|
|
23
32
|
* that the blocklist forbids (`.env`, `.git/`, `node_modules/`, `.theo/`,
|
|
@@ -32,6 +41,33 @@ export declare class ForbiddenPathError extends ConfigurationError {
|
|
|
32
41
|
readonly name: string;
|
|
33
42
|
constructor(path: string);
|
|
34
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Join `base` with `...parts` and ensure the resolved absolute path stays
|
|
46
|
+
* under `base`. Resolves FIRST, then prefix-checks (ADR D80) — prevents
|
|
47
|
+
* normalized-escape bypasses like `subdir/.\\./bar`.
|
|
48
|
+
*
|
|
49
|
+
* Returns the safe absolute path. Throws `PathTraversalError` if escape.
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
export declare function safePathJoin(base: string, ...parts: string[]): string;
|
|
53
|
+
/**
|
|
54
|
+
* Assert that `path` — including every directory component in the chain —
|
|
55
|
+
* stays under `base` after symlink resolution. No-op when nothing on the
|
|
56
|
+
* path exists yet.
|
|
57
|
+
*
|
|
58
|
+
* Two-bug history:
|
|
59
|
+
* 1. **EC-1** (original fix, kept): a multi-level symlink chain A → B → C
|
|
60
|
+
* must be resolved end-to-end. `realpathSync` does this in 1 syscall.
|
|
61
|
+
* 2. **Defence-in-depth** (added v1.x): the previous implementation only
|
|
62
|
+
* called `lstatSync(path)` on the terminal component. If an INTERMEDIATE
|
|
63
|
+
* directory was a symlink (`base/inner-symlink → /outside`), `lstat` on
|
|
64
|
+
* `base/inner-symlink/file.txt` followed the symlink and reported the
|
|
65
|
+
* regular file — escape went undetected. Fix: walk up to the nearest
|
|
66
|
+
* existing ancestor and `realpath` THAT, then re-attach the suffix and
|
|
67
|
+
* check the result against the canonical base.
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
export declare function assertNoSymlinkEscape(path: string, base: string): void;
|
|
35
71
|
/**
|
|
36
72
|
* Decide whether a project-relative path points to a known-sensitive file
|
|
37
73
|
* that a coding agent must not read or write.
|
|
@@ -57,3 +93,28 @@ export declare function isForbiddenPath(input: string): boolean;
|
|
|
57
93
|
export declare function sanitizeIdentifier(input: string, options?: {
|
|
58
94
|
maxLen?: number;
|
|
59
95
|
}): string;
|
|
96
|
+
/**
|
|
97
|
+
* Convert ANY opaque id (agent id, run id, conversation id, namespace, email,
|
|
98
|
+
* arbitrary string) into a deterministic, filesystem-safe filename component.
|
|
99
|
+
*
|
|
100
|
+
* Unlike {@link sanitizeIdentifier} (which THROWS on non-conforming input),
|
|
101
|
+
* this is a total function: it NEVER throws on a non-empty string. It returns
|
|
102
|
+
* the lowercased id verbatim when it already matches the safe grammar
|
|
103
|
+
* `^[a-z0-9][a-z0-9-_]*$` and fits `maxLen` (so UUIDs, hashes, and slugs stay
|
|
104
|
+
* human-readable), otherwise a deterministic `h-<16 hex>` sha256 token
|
|
105
|
+
* (collision-resistant and always a valid filename). The output charset is
|
|
106
|
+
* always `[a-z0-9_-]`, safe as a literal path segment on every filesystem.
|
|
107
|
+
*
|
|
108
|
+
* @param id - any opaque identifier (must be a non-empty string)
|
|
109
|
+
* @param options.maxLen - max length for the passthrough branch (default 128).
|
|
110
|
+
* Ids longer than this are hashed; the hash token itself is always short.
|
|
111
|
+
* @throws ConfigurationError (code `invalid_filename_id`) only on empty input.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* safeFilenameForId("550e8400-e29b-41d4-a716-446655440000") // passthrough
|
|
115
|
+
* safeFilenameForId("user@example.com") // "h-<16hex>"
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
export declare function safeFilenameForId(id: string, options?: {
|
|
119
|
+
maxLen?: number;
|
|
120
|
+
}): string;
|
|
@@ -18,6 +18,15 @@
|
|
|
18
18
|
* @internal
|
|
19
19
|
*/
|
|
20
20
|
import { ConfigurationError } from "../../errors.js";
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when a path operation would escape its allowed base directory.
|
|
23
|
+
* Extends `ConfigurationError` (no new error hierarchy per ADR D65).
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
export declare class PathTraversalError extends ConfigurationError {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
constructor(input: string, resolvedPath: string);
|
|
29
|
+
}
|
|
21
30
|
/**
|
|
22
31
|
* Thrown when an agent tool is asked to read or write a sensitive path
|
|
23
32
|
* that the blocklist forbids (`.env`, `.git/`, `node_modules/`, `.theo/`,
|
|
@@ -32,6 +41,33 @@ export declare class ForbiddenPathError extends ConfigurationError {
|
|
|
32
41
|
readonly name: string;
|
|
33
42
|
constructor(path: string);
|
|
34
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Join `base` with `...parts` and ensure the resolved absolute path stays
|
|
46
|
+
* under `base`. Resolves FIRST, then prefix-checks (ADR D80) — prevents
|
|
47
|
+
* normalized-escape bypasses like `subdir/.\\./bar`.
|
|
48
|
+
*
|
|
49
|
+
* Returns the safe absolute path. Throws `PathTraversalError` if escape.
|
|
50
|
+
*
|
|
51
|
+
*/
|
|
52
|
+
export declare function safePathJoin(base: string, ...parts: string[]): string;
|
|
53
|
+
/**
|
|
54
|
+
* Assert that `path` — including every directory component in the chain —
|
|
55
|
+
* stays under `base` after symlink resolution. No-op when nothing on the
|
|
56
|
+
* path exists yet.
|
|
57
|
+
*
|
|
58
|
+
* Two-bug history:
|
|
59
|
+
* 1. **EC-1** (original fix, kept): a multi-level symlink chain A → B → C
|
|
60
|
+
* must be resolved end-to-end. `realpathSync` does this in 1 syscall.
|
|
61
|
+
* 2. **Defence-in-depth** (added v1.x): the previous implementation only
|
|
62
|
+
* called `lstatSync(path)` on the terminal component. If an INTERMEDIATE
|
|
63
|
+
* directory was a symlink (`base/inner-symlink → /outside`), `lstat` on
|
|
64
|
+
* `base/inner-symlink/file.txt` followed the symlink and reported the
|
|
65
|
+
* regular file — escape went undetected. Fix: walk up to the nearest
|
|
66
|
+
* existing ancestor and `realpath` THAT, then re-attach the suffix and
|
|
67
|
+
* check the result against the canonical base.
|
|
68
|
+
*
|
|
69
|
+
*/
|
|
70
|
+
export declare function assertNoSymlinkEscape(path: string, base: string): void;
|
|
35
71
|
/**
|
|
36
72
|
* Decide whether a project-relative path points to a known-sensitive file
|
|
37
73
|
* that a coding agent must not read or write.
|
|
@@ -57,3 +93,28 @@ export declare function isForbiddenPath(input: string): boolean;
|
|
|
57
93
|
export declare function sanitizeIdentifier(input: string, options?: {
|
|
58
94
|
maxLen?: number;
|
|
59
95
|
}): string;
|
|
96
|
+
/**
|
|
97
|
+
* Convert ANY opaque id (agent id, run id, conversation id, namespace, email,
|
|
98
|
+
* arbitrary string) into a deterministic, filesystem-safe filename component.
|
|
99
|
+
*
|
|
100
|
+
* Unlike {@link sanitizeIdentifier} (which THROWS on non-conforming input),
|
|
101
|
+
* this is a total function: it NEVER throws on a non-empty string. It returns
|
|
102
|
+
* the lowercased id verbatim when it already matches the safe grammar
|
|
103
|
+
* `^[a-z0-9][a-z0-9-_]*$` and fits `maxLen` (so UUIDs, hashes, and slugs stay
|
|
104
|
+
* human-readable), otherwise a deterministic `h-<16 hex>` sha256 token
|
|
105
|
+
* (collision-resistant and always a valid filename). The output charset is
|
|
106
|
+
* always `[a-z0-9_-]`, safe as a literal path segment on every filesystem.
|
|
107
|
+
*
|
|
108
|
+
* @param id - any opaque identifier (must be a non-empty string)
|
|
109
|
+
* @param options.maxLen - max length for the passthrough branch (default 128).
|
|
110
|
+
* Ids longer than this are hashed; the hash token itself is always short.
|
|
111
|
+
* @throws ConfigurationError (code `invalid_filename_id`) only on empty input.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* safeFilenameForId("550e8400-e29b-41d4-a716-446655440000") // passthrough
|
|
115
|
+
* safeFilenameForId("user@example.com") // "h-<16hex>"
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
export declare function safeFilenameForId(id: string, options?: {
|
|
119
|
+
maxLen?: number;
|
|
120
|
+
}): string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* M50 (agent-builder) — session-transcript compaction
|
|
2
|
+
* M50 (agent-builder) — session-transcript compaction.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The mechanism:
|
|
5
5
|
* - the replacement history = recent USER messages verbatim (newest→oldest under a token budget;
|
|
6
6
|
* prior summaries filtered by marker) + ONE summary message with a textual marker prefix,
|
|
7
7
|
* injected as `role:"user"` (`build_compacted_history`, compact.rs:589-663);
|
|
@@ -14,6 +14,30 @@ export interface OTelSpan {
|
|
|
14
14
|
};
|
|
15
15
|
isRecording(): boolean;
|
|
16
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Telemetry handle returned by {@link createTelemetry}. Callers use this to
|
|
19
|
+
* start spans, add events, etc. When telemetry is disabled OR OTel is not
|
|
20
|
+
* installed, every method is a safe no-op.
|
|
21
|
+
*
|
|
22
|
+
*/
|
|
23
|
+
export interface TelemetryHandle {
|
|
24
|
+
readonly enabled: boolean;
|
|
25
|
+
readonly includeContent: boolean;
|
|
26
|
+
/** Start a span. Returns a no-op span if telemetry is disabled. */
|
|
27
|
+
startSpan(name: string, attrs?: Record<string, string | number | boolean>): OTelSpan;
|
|
28
|
+
/** Start a child span nested under `parent` (M3 #64). `undefined` parent →
|
|
29
|
+
* a root span (parentless callers, or telemetry with no run span). */
|
|
30
|
+
startChildSpan(parent: OTelSpan | undefined, name: string, attrs?: Record<string, string | number | boolean>): OTelSpan;
|
|
31
|
+
/**
|
|
32
|
+
* Record a histogram observation. T0.1 ships
|
|
33
|
+
* `theokit_memory_recall_duration_ms`; downstream tasks add more.
|
|
34
|
+
* No-op when telemetry is disabled or when `@opentelemetry/api`'s metrics
|
|
35
|
+
* namespace is unavailable. ADR D34 — exporter errors are swallowed by `safe()`.
|
|
36
|
+
*/
|
|
37
|
+
recordHistogram(name: string, valueMs: number, attrs?: Record<string, string | number | boolean>): void;
|
|
38
|
+
/** End all open spans on this handle (best-effort, used by Agent.dispose). */
|
|
39
|
+
endAll(): void;
|
|
40
|
+
}
|
|
17
41
|
export declare const NOOP_SPAN: OTelSpan;
|
|
18
42
|
/** Exported for tests — resets the OTel cache and the warning-once latch. */
|
|
19
43
|
export declare function _resetTelemetryCacheForTests(): void;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkELKO4ZLR_cjs = require('./chunk-ELKO4ZLR.cjs');
|
|
4
|
+
require('./chunk-LPVIRJVF.cjs');
|
|
5
|
+
require('./chunk-BM6N4KHO.cjs');
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
Object.defineProperty(exports, "JudgeCredentialError", {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: function () { return chunkELKO4ZLR_cjs.JudgeCredentialError; }
|
|
12
|
+
});
|
|
13
|
+
Object.defineProperty(exports, "composeJudgePrompt", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function () { return chunkELKO4ZLR_cjs.composeJudgePrompt; }
|
|
16
|
+
});
|
|
17
|
+
Object.defineProperty(exports, "judgeCallImpl", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return chunkELKO4ZLR_cjs.judgeCallImpl; }
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=judge-call-G5SGV2M2.cjs.map
|
|
22
|
+
//# sourceMappingURL=judge-call-G5SGV2M2.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-G5SGV2M2.cjs"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"judge-call-MDIPTBJI.js"}
|
package/dist/mcp-auth.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
3
|
+
var chunkC4H6PB5J_cjs = require('./chunk-C4H6PB5J.cjs');
|
|
4
|
+
var chunk5YKNEMXR_cjs = require('./chunk-5YKNEMXR.cjs');
|
|
5
|
+
var chunkLPVIRJVF_cjs = require('./chunk-LPVIRJVF.cjs');
|
|
6
|
+
var chunkBM6N4KHO_cjs = require('./chunk-BM6N4KHO.cjs');
|
|
7
7
|
var crypto = require('crypto');
|
|
8
8
|
var http = require('http');
|
|
9
9
|
var promises = require('readline/promises');
|
|
@@ -32,7 +32,7 @@ function tryRequireKeytar() {
|
|
|
32
32
|
cachedKeytar = r("keytar");
|
|
33
33
|
} catch {
|
|
34
34
|
if (!keytarWarned) {
|
|
35
|
-
|
|
35
|
+
chunkBM6N4KHO_cjs.diag(
|
|
36
36
|
"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\n"
|
|
37
37
|
);
|
|
38
38
|
keytarWarned = true;
|
|
@@ -59,7 +59,7 @@ async function setTokens(serverName, tokens) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
allTokens[serverName] = tokens;
|
|
62
|
-
await
|
|
62
|
+
await chunkC4H6PB5J_cjs.atomicWriteJson(FILE_PATH, allTokens);
|
|
63
63
|
try {
|
|
64
64
|
fs.chmodSync(FILE_PATH, 384);
|
|
65
65
|
} catch {
|
|
@@ -77,7 +77,7 @@ async function getTokens(serverName) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
if (!fs.existsSync(FILE_PATH)) return void 0;
|
|
80
|
-
|
|
80
|
+
chunk5YKNEMXR_cjs.assertSecureModes(path.dirname(FILE_PATH), FILE_PATH);
|
|
81
81
|
try {
|
|
82
82
|
const all = JSON.parse(fs.readFileSync(FILE_PATH, "utf8"));
|
|
83
83
|
return all[serverName];
|
|
@@ -118,7 +118,7 @@ async function refreshAccessToken(serverName, clientId, refreshToken, oauth) {
|
|
|
118
118
|
body: body.toString()
|
|
119
119
|
});
|
|
120
120
|
if (!response.ok) {
|
|
121
|
-
throw new
|
|
121
|
+
throw new chunkLPVIRJVF_cjs.ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {
|
|
122
122
|
code: "oauth_refresh_failed"
|
|
123
123
|
});
|
|
124
124
|
}
|
|
@@ -164,7 +164,7 @@ async function runPkceFlow(serverName, clientId, scopes, oauth) {
|
|
|
164
164
|
authUrl.searchParams.set("code_challenge", challenge);
|
|
165
165
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
166
166
|
authUrl.searchParams.set("state", state);
|
|
167
|
-
|
|
167
|
+
chunkBM6N4KHO_cjs.diag(
|
|
168
168
|
`
|
|
169
169
|
[theokit-sdk] OAuth required for MCP server "${serverName}".
|
|
170
170
|
Open this URL to authenticate:
|
|
@@ -174,7 +174,7 @@ Open this URL to authenticate:
|
|
|
174
174
|
);
|
|
175
175
|
const { code, state: returnedState } = await codeWaiter;
|
|
176
176
|
if (returnedState !== state) {
|
|
177
|
-
throw new
|
|
177
|
+
throw new chunkLPVIRJVF_cjs.ConfigurationError(
|
|
178
178
|
"OAuth state mismatch \u2014 possible CSRF attempt; aborting token exchange.",
|
|
179
179
|
{ code: "oauth_state_mismatch" }
|
|
180
180
|
);
|
|
@@ -193,7 +193,7 @@ Open this URL to authenticate:
|
|
|
193
193
|
});
|
|
194
194
|
if (!response.ok) {
|
|
195
195
|
const errText = await response.text();
|
|
196
|
-
throw new
|
|
196
|
+
throw new chunkLPVIRJVF_cjs.ConfigurationError(
|
|
197
197
|
`OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,
|
|
198
198
|
{ code: "oauth_token_exchange_failed" }
|
|
199
199
|
);
|
|
@@ -239,7 +239,7 @@ async function openLocalhostCallback(expectedState, timeoutMs) {
|
|
|
239
239
|
});
|
|
240
240
|
server.listen(0, "127.0.0.1");
|
|
241
241
|
const timeoutHandle = setTimeout(() => {
|
|
242
|
-
rejectCode?.(new
|
|
242
|
+
rejectCode?.(new chunkLPVIRJVF_cjs.ConfigurationError("OAuth flow timed out", { code: "oauth_timeout" }));
|
|
243
243
|
}, timeoutMs);
|
|
244
244
|
await new Promise((r) => {
|
|
245
245
|
if (server.listening) r();
|
|
@@ -248,7 +248,7 @@ async function openLocalhostCallback(expectedState, timeoutMs) {
|
|
|
248
248
|
const address = server.address();
|
|
249
249
|
if (typeof address !== "object" || address === null) {
|
|
250
250
|
server.close();
|
|
251
|
-
throw new
|
|
251
|
+
throw new chunkLPVIRJVF_cjs.ConfigurationError("OAuth localhost callback failed to bind", {
|
|
252
252
|
code: "oauth_bind_failed"
|
|
253
253
|
});
|
|
254
254
|
}
|
|
@@ -284,7 +284,7 @@ async function readCodeFromStdin(expectedState, timeoutMs) {
|
|
|
284
284
|
}).catch(reject);
|
|
285
285
|
setTimeout(() => {
|
|
286
286
|
rl.close();
|
|
287
|
-
reject(new
|
|
287
|
+
reject(new chunkLPVIRJVF_cjs.ConfigurationError("OAuth manual input timed out", { code: "oauth_timeout" }));
|
|
288
288
|
}, timeoutMs);
|
|
289
289
|
});
|
|
290
290
|
return promise;
|
package/dist/mcp-auth.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":["join","homedir","dirname","mkdirSync","chmodSync","createRequire","diag","existsSync","readFileSync","atomicWriteJson","assertSecureModes","randomBytes","createHash","ConfigurationError","createServer","createInterface"],"mappings":"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","file":"mcp-auth.cjs","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n * @internal\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n * @internal\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n * @internal\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n * @internal\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":["join","homedir","dirname","mkdirSync","chmodSync","createRequire","diag","existsSync","readFileSync","atomicWriteJson","assertSecureModes","randomBytes","createHash","ConfigurationError","createServer","createInterface"],"mappings":"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","file":"mcp-auth.cjs","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
|
package/dist/mcp-auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { atomicWriteJson } from './chunk-
|
|
2
|
-
import { assertSecureModes } from './chunk-
|
|
3
|
-
import { ConfigurationError } from './chunk-
|
|
4
|
-
import { diag } from './chunk-
|
|
1
|
+
import { atomicWriteJson } from './chunk-6AZDM6LK.js';
|
|
2
|
+
import { assertSecureModes } from './chunk-3V7UJLQT.js';
|
|
3
|
+
import { ConfigurationError } from './chunk-U4UJHOSH.js';
|
|
4
|
+
import { diag } from './chunk-5F5NWKPN.js';
|
|
5
5
|
import { randomBytes, createHash } from 'crypto';
|
|
6
6
|
import { createServer } from 'http';
|
|
7
7
|
import { createInterface } from 'readline/promises';
|
package/dist/mcp-auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":[],"mappings":"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","file":"mcp-auth.js","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n * @internal\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n * @internal\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n * @internal\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n * @internal\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n * @internal\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/internal/mcp/token-storage.ts","../src/internal/mcp/oauth.ts"],"names":[],"mappings":"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","file":"mcp-auth.js","sourcesContent":["import { chmodSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { assertSecureModes } from \"../auth/credential-store.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { atomicWriteJson } from \"../persistence/atomic-write.js\";\n\n/**\n * OAuth token bundle persisted per MCP server. See ADR D41.\n *\n * @internal\n */\nexport interface OAuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt: number; // ms epoch\n scope?: string;\n obtainedAt: number; // ms epoch\n}\n\nconst KEYTAR_SERVICE = \"theokit-mcp\";\nconst FILE_PATH = join(homedir(), \".theokit\", \"mcp-tokens.json\");\n\n/**\n * Make the directory private BEFORE anything is written into it.\n *\n * `atomicWriteJson` auto-creates the parent with a bare recursive `mkdir`, so under the common umask\n * 002 `~/.theokit` was born 0775 — and the `chmod 600` applied to the file afterwards protects the\n * wrong thing. Write permission on a DIRECTORY is permission to unlink and recreate its contents, so\n * a 0600 file inside a group-writable directory can be replaced wholesale by another local user. The\n * secret here is a REFRESH TOKEN: replacing it swaps which account the agent authenticates as.\n *\n * The `chmod` is unconditional and not a fallback for the `mkdir` mode: `mkdir`'s mode applies only\n * at CREATION, so every machine that already ran an older build has the loose directory sitting\n * there, and a fix that only covers fresh installs does not reach the population that has the\n * problem.\n */\nfunction ensurePrivateStoreDir(): void {\n const dir = dirname(FILE_PATH);\n try {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n chmodSync(dir, 0o700);\n } catch {\n // Windows: chmod is a no-op and the mode argument is meaningless. Same posture as the file\n // chmod below — documented in ADR D41 / EC-14.\n }\n}\n\ninterface KeytarLike {\n setPassword: (service: string, account: string, password: string) => Promise<void>;\n getPassword: (service: string, account: string) => Promise<string | null>;\n deletePassword: (service: string, account: string) => Promise<boolean>;\n}\n\nlet keytarWarned = false;\nlet cachedKeytar: KeytarLike | undefined | null;\nfunction tryRequireKeytar(): KeytarLike | null {\n if (cachedKeytar !== undefined) return cachedKeytar;\n try {\n const r = createRequire(import.meta.url);\n cachedKeytar = r(\"keytar\") as KeytarLike;\n } catch {\n if (!keytarWarned) {\n diag(\n \"[theokit-sdk] keytar not installed; MCP OAuth tokens will be stored in ~/.theokit/mcp-tokens.json (chmod 600 where POSIX). Install `keytar` for OS keychain storage.\\n\",\n );\n keytarWarned = true;\n }\n cachedKeytar = null;\n }\n return cachedKeytar;\n}\n\n// EC-9 (race): serialize concurrent refreshes per server. Each key maps to\n// the in-flight refresh Promise; subsequent callers await the same promise.\nconst inflightRefresh = new Map<string, Promise<OAuthTokens>>();\n\n/**\n * Persist tokens for `serverName`. Tries keychain first, falls back to file\n * with chmod 600 (POSIX). Idempotent.\n *\n */\nexport async function setTokens(serverName: string, tokens: OAuthTokens): Promise<void> {\n const kt = tryRequireKeytar();\n const payload = JSON.stringify(tokens);\n if (kt !== null && kt !== undefined) {\n await kt.setPassword(KEYTAR_SERVICE, serverName, payload);\n return;\n }\n // File fallback. The directory is locked down first — see `ensurePrivateStoreDir`.\n ensurePrivateStoreDir();\n let allTokens: Record<string, OAuthTokens> = {};\n if (existsSync(FILE_PATH)) {\n try {\n allTokens = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n } catch {\n // corrupt file — start fresh\n allTokens = {};\n }\n }\n allTokens[serverName] = tokens;\n await atomicWriteJson(FILE_PATH, allTokens);\n try {\n chmodSync(FILE_PATH, 0o600);\n } catch {\n // Windows: chmod is a no-op. Documented in ADR D41 / EC-14.\n }\n}\n\n/**\n * Retrieve tokens for `serverName`. Returns undefined if absent.\n *\n */\nexport async function getTokens(serverName: string): Promise<OAuthTokens | undefined> {\n const kt = tryRequireKeytar();\n if (kt !== null && kt !== undefined) {\n const value = await kt.getPassword(KEYTAR_SERVICE, serverName);\n if (typeof value !== \"string\") return undefined;\n try {\n return JSON.parse(value) as OAuthTokens;\n } catch {\n return undefined;\n }\n }\n if (!existsSync(FILE_PATH)) return undefined;\n // The same gate the credential file gets, and deliberately the same implementation rather than a\n // second one: a refresh token is a credential, and `assertSecureModes` already carries the attack\n // it defends against in its docstring. It is called OUTSIDE the try/catch on purpose — the catch\n // below exists to treat a corrupt file as absent, and letting it swallow this would turn \"someone\n // else can replace your token\" into a silent `undefined`, which reads as \"not logged in\".\n assertSecureModes(dirname(FILE_PATH), FILE_PATH);\n try {\n const all = JSON.parse(readFileSync(FILE_PATH, \"utf8\")) as Record<string, OAuthTokens>;\n return all[serverName];\n } catch {\n return undefined;\n }\n}\n\n/**\n * Serialize concurrent refresh attempts per server (EC-9). If a refresh is\n * already in flight for the same `serverName`, the second caller awaits\n * the same Promise.\n *\n */\nexport function lockedRefresh(\n serverName: string,\n refreshFn: () => Promise<OAuthTokens>,\n): Promise<OAuthTokens> {\n const existing = inflightRefresh.get(serverName);\n if (existing !== undefined) return existing;\n const promise = (async () => {\n try {\n return await refreshFn();\n } finally {\n inflightRefresh.delete(serverName);\n }\n })();\n inflightRefresh.set(serverName, promise);\n return promise;\n}\n\n/**\n * Reset all cached state. Test-only helper.\n *\n * @internal\n */\nexport function _resetForTests(): void {\n inflightRefresh.clear();\n cachedKeytar = undefined;\n keytarWarned = false;\n}\n","import { createHash, randomBytes } from \"node:crypto\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport { ConfigurationError } from \"../../errors.js\";\nimport type { McpOAuthConfig } from \"../../types/mcp.js\";\nimport { diag } from \"../diagnostics.js\";\nimport { type OAuthTokens, setTokens } from \"./token-storage.js\";\n\n/**\n * OAuth 2.1 PKCE flow runner for MCP HTTP servers. See ADR D41.\n *\n * @internal\n */\n\n/**\n * Generate a random base64url-encoded string. Used for both code_verifier\n * (43-128 chars) and state (CSRF protection).\n */\nfunction base64url(n: number): string {\n return randomBytes(n).toString(\"base64url\");\n}\n\n/**\n * Compute PKCE code_challenge = base64url(SHA256(code_verifier)).\n */\nfunction challengeFor(verifier: string): string {\n return createHash(\"sha256\").update(verifier).digest(\"base64url\");\n}\n\n/**\n * Refresh an access token using `refresh_token`. Throws on failure.\n *\n */\nexport async function refreshAccessToken(\n serverName: string,\n clientId: string,\n refreshToken: string,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n client_id: clientId,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: body.toString(),\n });\n if (!response.ok) {\n throw new ConfigurationError(`OAuth refresh failed: HTTP ${response.status}`, {\n code: \"oauth_refresh_failed\",\n });\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n const tokens: OAuthTokens = {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n // EC-10: default conservative 3600s if expires_in missing.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n await setTokens(serverName, tokens);\n return tokens;\n}\n\n/**\n * Run the full PKCE flow. Generates verifier+challenge+state, opens the\n * authorization URL (browser or manual print), waits for the callback\n * (localhost or stdin), exchanges code for tokens.\n *\n */\n// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: PKCE flow is a linear protocol step (verifier → challenge → state → URL → wait → exchange); refactoring fragments the RFC 7636 narrative.\nexport async function runPkceFlow(\n serverName: string,\n clientId: string,\n scopes: string[] | undefined,\n oauth: McpOAuthConfig,\n): Promise<OAuthTokens> {\n const verifier = base64url(48);\n const challenge = challengeFor(verifier);\n const state = base64url(16);\n const timeoutMs = oauth.timeoutMs ?? 300_000;\n\n let redirectUri: string;\n let codeWaiter: Promise<{ code: string; state: string }>;\n let cleanup: () => void = () => {};\n\n if (oauth.redirectMode === \"localhost\") {\n const result = await openLocalhostCallback(state, timeoutMs);\n redirectUri = result.redirectUri;\n codeWaiter = result.waitFor;\n cleanup = result.close;\n } else {\n redirectUri = \"urn:ietf:wg:oauth:2.0:oob\";\n codeWaiter = readCodeFromStdin(state, timeoutMs);\n }\n\n try {\n const authUrl = new URL(oauth.authorizationEndpoint);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"client_id\", clientId);\n authUrl.searchParams.set(\"redirect_uri\", redirectUri);\n if (scopes !== undefined && scopes.length > 0) {\n authUrl.searchParams.set(\"scope\", scopes.join(\" \"));\n }\n authUrl.searchParams.set(\"code_challenge\", challenge);\n authUrl.searchParams.set(\"code_challenge_method\", \"S256\");\n authUrl.searchParams.set(\"state\", state);\n\n diag(\n `\\n[theokit-sdk] OAuth required for MCP server \"${serverName}\".\\n` +\n `Open this URL to authenticate:\\n ${authUrl.toString()}\\n\\n`,\n );\n\n const { code, state: returnedState } = await codeWaiter;\n // EC-2 MUST FIX: validate state.\n if (returnedState !== state) {\n throw new ConfigurationError(\n \"OAuth state mismatch — possible CSRF attempt; aborting token exchange.\",\n { code: \"oauth_state_mismatch\" },\n );\n }\n\n // Exchange code for tokens.\n const tokenBody = new URLSearchParams({\n grant_type: \"authorization_code\",\n code,\n client_id: clientId,\n code_verifier: verifier,\n redirect_uri: redirectUri,\n });\n const response = await fetch(oauth.tokenEndpoint, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: tokenBody.toString(),\n });\n if (!response.ok) {\n const errText = await response.text();\n throw new ConfigurationError(\n `OAuth token exchange failed: HTTP ${response.status}: ${errText.slice(0, 200)}`,\n { code: \"oauth_token_exchange_failed\" },\n );\n }\n const data = (await response.json()) as {\n access_token: string;\n refresh_token?: string;\n expires_in?: number;\n scope?: string;\n };\n const obtainedAt = Date.now();\n return {\n accessToken: data.access_token,\n ...(data.refresh_token !== undefined ? { refreshToken: data.refresh_token } : {}),\n // EC-10: default 3600s when expires_in absent.\n expiresAt: obtainedAt + (data.expires_in ?? 3600) * 1000,\n obtainedAt,\n ...(data.scope !== undefined ? { scope: data.scope } : {}),\n };\n } finally {\n cleanup();\n }\n}\n\n/**\n * Start a localhost HTTP server on a free port; return the redirect URI,\n * a promise that resolves with the code+state from the first valid request,\n * and a close handle.\n *\n * @internal\n */\nasync function openLocalhostCallback(\n expectedState: string,\n timeoutMs: number,\n): Promise<{\n redirectUri: string;\n waitFor: Promise<{ code: string; state: string }>;\n close: () => void;\n}> {\n let resolveCode: ((value: { code: string; state: string }) => void) | undefined;\n let rejectCode: ((reason: unknown) => void) | undefined;\n const waitFor = new Promise<{ code: string; state: string }>((res, rej) => {\n resolveCode = res;\n rejectCode = rej;\n });\n const server = createServer((req: IncomingMessage, res: ServerResponse) => {\n const url = new URL(req.url ?? \"/\", \"http://127.0.0.1\");\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (typeof code !== \"string\" || typeof state !== \"string\") {\n res.statusCode = 400;\n res.end(\"Missing code or state\");\n return;\n }\n // EC-2: state must match the one we generated. We DO NOT trust any\n // localhost GET — only the one carrying our exact state.\n if (state !== expectedState) {\n res.statusCode = 400;\n res.end(\"State mismatch — request rejected.\");\n return;\n }\n res.statusCode = 200;\n res.end(\"Authentication successful. You can close this tab and return to the terminal.\");\n resolveCode?.({ code, state });\n });\n server.listen(0, \"127.0.0.1\");\n const timeoutHandle = setTimeout(() => {\n rejectCode?.(new ConfigurationError(\"OAuth flow timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n // Wait for server.listen to bind so we can read the port.\n await new Promise<void>((r) => {\n if (server.listening) r();\n else server.once(\"listening\", () => r());\n });\n const address = server.address();\n if (typeof address !== \"object\" || address === null) {\n server.close();\n throw new ConfigurationError(\"OAuth localhost callback failed to bind\", {\n code: \"oauth_bind_failed\",\n });\n }\n const port = address.port;\n return {\n redirectUri: `http://127.0.0.1:${port}/callback`,\n waitFor,\n close: () => {\n clearTimeout(timeoutHandle);\n server.close();\n },\n };\n}\n\n/**\n * Read `code` (and `state`) from stdin (manual mode). User pastes the\n * full callback URL or just the code value.\n *\n * @internal\n */\nasync function readCodeFromStdin(\n expectedState: string,\n timeoutMs: number,\n): Promise<{ code: string; state: string }> {\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const promise = new Promise<{ code: string; state: string }>((resolve, reject) => {\n rl.question(\"Paste the full callback URL (or just the code): \")\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stdin reader handles 2 input formats (full URL vs raw code) inline; splitting fragments the manual-mode UX.\n .then((answer) => {\n rl.close();\n const trimmed = answer.trim();\n // Try to parse as URL first.\n if (trimmed.includes(\"?\")) {\n try {\n const parsed = new URL(trimmed);\n const code = parsed.searchParams.get(\"code\");\n const state = parsed.searchParams.get(\"state\") ?? \"\";\n if (typeof code === \"string\") {\n resolve({ code, state });\n return;\n }\n } catch {\n // not a URL — fall through\n }\n }\n // Treat the entire input as the code. State validation will fail\n // unless the user also provided state — but in manual mode they\n // typically paste the URL with state baked in.\n resolve({ code: trimmed, state: expectedState });\n })\n .catch(reject);\n setTimeout(() => {\n rl.close();\n reject(new ConfigurationError(\"OAuth manual input timed out\", { code: \"oauth_timeout\" }));\n }, timeoutMs);\n });\n return promise;\n}\n"]}
|