@maudecode/talaria-web 1.15.0 → 1.17.2
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/_release.json +4 -4
- package/dist/api/automation-raw.js +6 -3
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +158 -16
- package/dist/api/automation-router.js +12 -6
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +193 -6
- package/dist/api/chat-router.js +39 -16
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +12 -1
- package/dist/api/git-router.js +60 -38
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +6 -0
- package/dist/api/raw-routes.js +35 -58
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +4 -2
- package/dist/api/router.js +8 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +974 -42
- package/dist/api/sessions-router.js +87 -25
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +254 -11
- package/dist/api/settings-router.js +68 -18
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +11 -3
- package/dist/api/sse-routes.js +145 -65
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +6 -2
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +86 -9
- package/dist/api/tools-router.js +19 -15
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +0 -8397
- package/dist/app.js +7 -2
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +11 -4
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +6 -2
- package/dist/auth/oidc.js +16 -11
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/store.d.ts +14 -2
- package/dist/auth/store.js +26 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/cli/dotenv.d.ts +1 -1
- package/dist/cli/dotenv.js +36 -3
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/config/agent-config.d.ts +2 -0
- package/dist/config/agent-config.js +9 -0
- package/dist/config/agent-config.js.map +1 -1
- package/dist/http/addresses.d.ts +6 -0
- package/dist/http/addresses.js +26 -0
- package/dist/http/addresses.js.map +1 -1
- package/dist/http/context.d.ts +6 -3
- package/dist/http/context.js +10 -4
- package/dist/http/context.js.map +1 -1
- package/dist/links.d.ts +26 -0
- package/dist/links.js +60 -0
- package/dist/links.js.map +1 -0
- package/dist/onboarding.js +30 -7
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.js +1 -1
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +167 -5
- package/dist/providers/catalog.js +662 -135
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +9 -4
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.d.ts +1 -0
- package/dist/redact.js +518 -15
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +79 -37
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +24 -3
- package/dist/sessions/anchor.js +94 -44
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +4 -1
- package/dist/sessions/background-tasks.js +7 -4
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/claude-code.d.ts +19 -0
- package/dist/sessions/claude-code.js +250 -0
- package/dist/sessions/claude-code.js.map +1 -0
- package/dist/sessions/completions.d.ts +13 -1
- package/dist/sessions/completions.js +64 -12
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +14 -1
- package/dist/sessions/compress.js +52 -11
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/journal.d.ts +8 -0
- package/dist/sessions/journal.js +44 -10
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +13 -5
- package/dist/sessions/list.js +56 -26
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +122 -9
- package/dist/sessions/merge.js +544 -33
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +5 -0
- package/dist/sessions/pending.js +30 -10
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/service.d.ts +100 -4
- package/dist/sessions/service.js +567 -102
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +16 -1
- package/dist/sessions/session.js +29 -2
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.js +11 -2
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/state-db.d.ts +26 -4
- package/dist/sessions/state-db.js +126 -8
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.js +6 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +10 -0
- package/dist/sessions/streams.js +14 -3
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/todo.d.ts +6 -1
- package/dist/sessions/todo.js +11 -4
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +12 -1
- package/dist/sessions/tool-display.js +76 -1
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn.d.ts +49 -10
- package/dist/sessions/turn.js +459 -185
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +20 -2
- package/dist/settings.js +49 -2
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js +7 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.js +2 -1
- package/dist/spa.js.map +1 -1
- package/dist/test/harness.js +2 -0
- package/dist/test/harness.js.map +1 -1
- package/dist/tools/commands.d.ts +15 -0
- package/dist/tools/commands.js +52 -0
- package/dist/tools/commands.js.map +1 -0
- package/dist/tools/crons.d.ts +4 -1
- package/dist/tools/crons.js +58 -11
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/insights.d.ts +8 -2
- package/dist/tools/insights.js +18 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/kanban.d.ts +41 -1
- package/dist/tools/kanban.js +113 -2
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp.d.ts +2 -1
- package/dist/tools/mcp.js +16 -6
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/terminal.d.ts +1 -0
- package/dist/tools/terminal.js +1 -1
- package/dist/tools/terminal.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +1 -1
- package/dist/tools/update-notifications.js +1 -1
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +15 -3
- package/dist/tools/updates.js +207 -41
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/fs.d.ts +7 -3
- package/dist/workspace/fs.js +5 -1
- package/dist/workspace/fs.js.map +1 -1
- package/dist/workspace/git.d.ts +57 -10
- package/dist/workspace/git.js +302 -195
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media-refs.d.ts +13 -0
- package/dist/workspace/media-refs.js +165 -0
- package/dist/workspace/media-refs.js.map +1 -0
- package/dist/workspace/media.d.ts +19 -0
- package/dist/workspace/media.js +63 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.d.ts +28 -0
- package/dist/workspace/preview.js +110 -0
- package/dist/workspace/preview.js.map +1 -0
- package/dist/workspace/rollback.d.ts +5 -4
- package/dist/workspace/rollback.js +12 -9
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/upload.d.ts +2 -0
- package/dist/workspace/upload.js +4 -1
- package/dist/workspace/upload.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +7 -1
- package/dist/workspace/workspaces.js +30 -21
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -4
- package/dist/workspace/worktrees.js +28 -33
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/scripts/replay_sidecar.py +4 -2
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/__main__.py +7 -1
- package/sidecar/talaria_sidecar/home.py +98 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +19 -3
- package/sidecar/talaria_sidecar/methods/chat.py +551 -61
- package/sidecar/talaria_sidecar/methods/commands.py +61 -33
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/cron.py +10 -6
- package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/process.py +60 -1
- package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
- package/sidecar/talaria_sidecar/methods/text.py +18 -21
- package/sidecar/talaria_sidecar/methods/usage.py +264 -5
- package/static/dist/FILES.txt +126 -121
- package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
- package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
- package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
- package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
- package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
- package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
- package/static/dist/assets/Select-bilaypej.js +1 -0
- package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
- package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
- package/static/dist/assets/States-nmejrwrm.js +1 -0
- package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
- package/static/dist/assets/_app-gdidzd94.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
- package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
- package/static/dist/assets/_app.insights-luywlarz.js +1 -0
- package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
- package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
- package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
- package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
- package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
- package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
- package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
- package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
- package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
- package/static/dist/assets/cancel-drd0g6d9.js +1 -0
- package/static/dist/assets/channel-cpdgf867.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
- package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
- package/static/dist/assets/copy-fw5b1pc6.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
- package/static/dist/assets/endpoints-d33siokx.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
- package/static/dist/assets/index-cxq58xb1.js +11 -0
- package/static/dist/assets/index-hgtx08n5.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
- package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
- package/static/dist/assets/link-jyxicdhg.js +1 -0
- package/static/dist/assets/login-ec21t39x.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
- package/static/dist/assets/onboarding-btnltvyz.js +1 -0
- package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
- package/static/dist/assets/refresh-b4uams1i.js +1 -0
- package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
- package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
- package/static/dist/assets/share._token-f97e94r0.js +2 -0
- package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
- package/static/dist/assets/sse-fwpqtzbg.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
- package/static/dist/assets/todoStore-eginf9h4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
- package/static/dist/assets/tts-lxdxnmjd.js +1 -0
- package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
- package/static/dist/assets/useForm-bcblv1rx.js +1 -0
- package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
- package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
- package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +2 -2
- package/static/dist/assets/AppShell-orat1s43.js +0 -1
- package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
- package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
- package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
- package/static/dist/assets/Select-fswf66up.js +0 -1
- package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
- package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
- package/static/dist/assets/States-kxyu50at.js +0 -1
- package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
- package/static/dist/assets/_app-fowyruiu.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
- package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
- package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
- package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
- package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
- package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
- package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
- package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
- package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
- package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
- package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
- package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
- package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
- package/static/dist/assets/channel-gozy3dp3.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
- package/static/dist/assets/endpoints-k8agnro8.js +0 -9
- package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
- package/static/dist/assets/index-nfy0ezs6.css +0 -2
- package/static/dist/assets/index-nqed3oum.js +0 -11
- package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
- package/static/dist/assets/link-m90ahx3n.js +0 -1
- package/static/dist/assets/login-bw7m2fzo.js +0 -1
- package/static/dist/assets/matchContext-f8oy3lo7.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
- package/static/dist/assets/onboarding-ret37j1e.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
- package/static/dist/assets/refresh-cw-k66keho6.js +0 -1
- package/static/dist/assets/search-j5ki6a9o.js +0 -1
- package/static/dist/assets/share._token-kg8w647b.js +0 -2
- package/static/dist/assets/sliders-horizontal-c4wya4dy.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
- package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
- package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
- package/static/dist/assets/useForm-ocxu460f.js +0 -1
- package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
package/dist/redact.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { str } from './util.js';
|
|
2
2
|
import { snapshotArgs, toolArgs, toolDisplay, toolName } from './sessions/tool-display.js';
|
|
3
|
+
import { messageText } from './sessions/merge.js';
|
|
4
|
+
import { stripAttachedFilesMarker } from './sessions/session.js';
|
|
3
5
|
/**
|
|
4
6
|
* Credential redaction and the public session projection (Python
|
|
5
7
|
* `api/helpers.py`). API responses are a hard boundary: transcript-bearing
|
|
6
8
|
* fields are masked with the local pattern set, and private replay aliases
|
|
7
|
-
* are stripped at their schema positions. The Agent's
|
|
8
|
-
*
|
|
9
|
-
* its
|
|
9
|
+
* are stripped at their schema positions. The Agent's redactor
|
|
10
|
+
* (`agent/redact.py`, `redact_sensitive_text(force=True)`) is not consulted
|
|
11
|
+
* at runtime; its pattern families are ported here and held to parity by
|
|
12
|
+
* the `Agent redactor parity` tests.
|
|
10
13
|
*/
|
|
11
14
|
const CRED_RE = new RegExp('(?<![A-Za-z0-9_-])(' +
|
|
12
15
|
'sk-[A-Za-z0-9_-]{10,}' +
|
|
@@ -16,6 +19,7 @@ const CRED_RE = new RegExp('(?<![A-Za-z0-9_-])(' +
|
|
|
16
19
|
'|ghu_[A-Za-z0-9]{10,}' +
|
|
17
20
|
'|ghs_[A-Za-z0-9]{10,}' +
|
|
18
21
|
'|ghr_[A-Za-z0-9]{10,}' +
|
|
22
|
+
'|xapp-\\d+-[A-Za-z0-9-]{10,}' +
|
|
19
23
|
'|xox[baprs]-[A-Za-z0-9-]{10,}' +
|
|
20
24
|
'|AIza[A-Za-z0-9_-]{30,}' +
|
|
21
25
|
'|pplx-[A-Za-z0-9]{10,}' +
|
|
@@ -44,6 +48,14 @@ const CRED_RE = new RegExp('(?<![A-Za-z0-9_-])(' +
|
|
|
44
48
|
'|hsk-[A-Za-z0-9]{10,}' +
|
|
45
49
|
'|mem0_[A-Za-z0-9]{10,}' +
|
|
46
50
|
'|brv_[A-Za-z0-9]{10,}' +
|
|
51
|
+
'|xai-[A-Za-z0-9]{30,}' +
|
|
52
|
+
'|ntn_[A-Za-z0-9]{10,}' +
|
|
53
|
+
'|fw[-_][A-Za-z0-9]{30,}' +
|
|
54
|
+
'|fpk_[A-Za-z0-9]{30,}' +
|
|
55
|
+
'|(?:glpat|gloas|gldt|glcbt|glptt|glft|glimt|glagent|glsoat|glffct|glwt)-[A-Za-z0-9_-]{10,}' +
|
|
56
|
+
'|glrtr?-[A-Za-z0-9_.-]{10,}' +
|
|
57
|
+
'|GR1348941[A-Za-z0-9_-]{10,}' +
|
|
58
|
+
'|pk-lf-[A-Za-z0-9-]{8,}' +
|
|
47
59
|
')(?![A-Za-z0-9_-])', 'g');
|
|
48
60
|
/** One `name=value` auth parameter: an escaped-quoted, quoted or bare value. */
|
|
49
61
|
const AUTH_PARAM = String.raw `[A-Za-z0-9_-]+=(?:\\"(?:[^"\\\r\n]|\\[^"])*\\"|"(?:[^"\\\r\n]|\\.)*"|'[^'\r\n]*'|[^\s,"'\\]*)`;
|
|
@@ -54,8 +66,8 @@ const AUTH_PARAM = String.raw `[A-Za-z0-9_-]+=(?:\\"(?:[^"\\\r\n]|\\[^"])*\\"|"(
|
|
|
54
66
|
* A parameterized credential (`Digest username="bob", response="..."`, `Credential=..., Signature=...`) is masked whole.
|
|
55
67
|
*/
|
|
56
68
|
const AUTH_HDR_RE = new RegExp(String.raw `(Authorization:\s*(?:(?!\*+\s)[A-Za-z0-9!#$%&*+.^_|~-]+\s+(?=[^\s,\])]))?)(${AUTH_PARAM}(?:\s*,\s*${AUTH_PARAM})*|[^\s,\])][^\s'",\])]*)`, 'gi');
|
|
57
|
-
/** A JSON Web Token anywhere (`eyJ<header>.<payload>.<signature>`). */
|
|
58
|
-
const JWT_RE = /\beyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g;
|
|
69
|
+
/** A JSON Web Token anywhere (`eyJ<header>.<payload>.<signature>`), or its header alone or with its payload. */
|
|
70
|
+
const JWT_RE = /\beyJ(?:[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}|[A-Za-z0-9_-]{10,}(?:\.[A-Za-z0-9_=-]{4,}){0,2})/g;
|
|
59
71
|
/** A bearer credential in any header or text (`X-Auth: Bearer ...`); `AUTH_HDR_RE` owns the `Authorization:` header. */
|
|
60
72
|
const BEARER_RE = /((?<!Authorization:\s{0,8})\bBearer\s+)([^\s,\])][^\s'",\])]*)/gi;
|
|
61
73
|
/** One shell-quoted piece, which may span lines: `'...'`, `"..."` (with backslash escapes), bash `$'...'` / `$"..."`, or JSON escaped inside a shell string (`\"...\"`). */
|
|
@@ -100,6 +112,36 @@ const percentDecode = (text) => text.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => S
|
|
|
100
112
|
const LISTED_FLAG_RE = /(["'])(-{1,2})([A-Za-z0-9_][A-Za-z0-9_.-]*)\1(\s*,\s*)(?:(["'])((?:\\.|(?!\5)[^\\])*)\5|(\[[^[\]]*\]|\{[^{}]*\}|[^\s,\])}'"[{]+))/g;
|
|
101
113
|
const EMBEDDED_AWS_RE = /(?:AKIA|ASIA)[A-Z0-9]{16}/g;
|
|
102
114
|
const ENV_RE = /([A-Z0-9_]{0,50}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Z0-9_]{0,50})\s*=\s*(['"]?)(\S+)\2/g;
|
|
115
|
+
/**
|
|
116
|
+
* The Agent's other env names: an all-caps one holding `KEY`, `PASS` or `PW` (`OPENAI_KEY`, `DB_PW`), or a `name_key` /
|
|
117
|
+
* `name_pass` / `name_pw` in any case, URL query parameters included (the Agent skips any text with a URL). One attempt per
|
|
118
|
+
* identifier, which must hold a keyword, so the scan stays linear on long runs (`PWPWPW…`). The value is the shell word
|
|
119
|
+
* after `=`, read by `shellWordEnd` (quotes, escapes, `$'…'`).
|
|
120
|
+
*/
|
|
121
|
+
const ENV_SUFFIX_RE = /(?<![A-Za-z0-9_])((?=[A-Z0-9_]*(?:KEY|PASS|PW))[A-Z0-9_]+|[A-Za-z0-9_]+_(?:[Kk][Ee][Yy]|[Pp][Aa][Ss][Ss]|[Pp][Ww])(?![A-Za-z0-9_]))[ \t]*=[ \t]*/g;
|
|
122
|
+
/** The prefilter's view of `ENV_SUFFIX_RE`. */
|
|
123
|
+
const ENV_SUFFIX_TEST_RE = new RegExp(ENV_SUFFIX_RE.source);
|
|
124
|
+
/**
|
|
125
|
+
* The Agent's env-name keywords, as whole name words (`DB_PW`, `GITHUB_TOKEN`), never inside a word (`KEYBOARD`, `PASSAGE`,
|
|
126
|
+
* `TOKENIZER`, `AUTHOR_KEY`'s `AUTH`).
|
|
127
|
+
*/
|
|
128
|
+
const ENV_KEYWORD_RE = /(?:^|[^A-Za-z])(?:(?:api|auth|access|refresh|session|secret)[ _.-]?(?:key|token)|token|secret|passwd|password|pass|pw|credential|auth|key)s?(?![A-Za-z])/i;
|
|
129
|
+
/**
|
|
130
|
+
* Env names whose value is a credential whatever its shape, as whole name words (`DB_PASS`, not `COMPASS_KEY` or
|
|
131
|
+
* `AUTHOR_KEY`); a bare `KEY` needs an opaque value (`SORT_KEY=name` stays).
|
|
132
|
+
*/
|
|
133
|
+
const ENV_STRONG_NAME_RE = /(?:^|[^A-Za-z])(?:(?:api|auth|access|refresh|session|id|bearer)[ _.-]?(?:key|token)|key[ _.-]?material|secret|passwd|password|pass|pw|credentials?|auth|bearer)s?(?![A-Za-z])/i;
|
|
134
|
+
/**
|
|
135
|
+
* The Agent's env-assignment gate: a name holding a whole keyword whose value is a credential, because the name is strong
|
|
136
|
+
* or the value opaque (`GITHUB_TOKEN=abc` and `MAX_TOKENS=100` stay). Env lookups (`os.getenv(…)`) name a variable.
|
|
137
|
+
*/
|
|
138
|
+
const isEnvCredential = (key, value) => !/^(?:os\.(?:getenv|environ)|process\.env|\$ENV\{)/.test(value) && ENV_KEYWORD_RE.test(key) && (ENV_STRONG_NAME_RE.test(key) || looksOpaque(value));
|
|
139
|
+
/** The Agent's `_looks_like_opaque_credential`: a value shaped like a generated secret rather than a word. */
|
|
140
|
+
function looksOpaque(value) {
|
|
141
|
+
if (value === '***' || /^[A-Fa-f0-9]{16,}$/.test(value) || /^[A-Za-z0-9_./+=-]{20,}$/.test(value))
|
|
142
|
+
return true;
|
|
143
|
+
return value.length >= 12 && [/[a-z]/, /[A-Z]/, /[0-9]/].filter((re) => re.test(value)).length >= 2;
|
|
144
|
+
}
|
|
103
145
|
/**
|
|
104
146
|
* `scheme://user:secret@host` (database and basic-auth URLs): the password is masked, the user and host stay. The user
|
|
105
147
|
* and password may be assembled from quoted and escaped shell pieces (`bob:hun'ter2'@`), and every delimiter may be
|
|
@@ -171,12 +213,214 @@ const USER_FLAG_RE = /(?<![A-Za-z0-9-])(?:-[uU][ \t]*|--(?:user|proxy-u(?:s(?:e(
|
|
|
171
213
|
/** The prefilter's view of `USER_FLAG_RE`. */
|
|
172
214
|
const USER_FLAG_TEST_RE = new RegExp(USER_FLAG_RE.source);
|
|
173
215
|
const QUERY_KEY_RE = /([?&]key=)([^\s"'&#]+)/gi;
|
|
216
|
+
/**
|
|
217
|
+
* A Telegram bot token (`bot<id>:<secret>`): the id stays. The id starts a digit run, so a long number is scanned once
|
|
218
|
+
* rather than from every digit (a match from mid-run would end at the same `:`).
|
|
219
|
+
*/
|
|
220
|
+
const TELEGRAM_TOKEN_RE = /(bot)?(?<!\d)(\d{8,}):([-A-Za-z0-9_]{30,})/g;
|
|
221
|
+
/** An E.164 phone number, masked to its first and last digits; never inside a word or encoded data (`ab+1234567`). */
|
|
222
|
+
const PHONE_RE = /(?<![A-Za-z0-9])\+[1-9]\d{6,14}(?![A-Za-z0-9])/g;
|
|
223
|
+
/**
|
|
224
|
+
* A bare token as URL userinfo (`https://TOKEN@github.com`, `ssh://…@`): no `user:` part, at least 8 characters. Round-trip
|
|
225
|
+
* URLs carry tokens in the query, so a bare userinfo credential is never one.
|
|
226
|
+
*/
|
|
227
|
+
const URL_BARE_TOKEN_RE = /((?:https?|wss?|git|ssh|ftps?|sftp):\/\/)([^\s:@/?#]{8,})(?=@\S)/gi;
|
|
228
|
+
/** C0, DEL, C1 and zero-width characters that can split a token body (`ghp_abc\x1bdef`, `sk-abc\u200bdef`). */
|
|
229
|
+
const CONTROL_CHAR_RE = /[\x00-\x1f\x7f-\x9f\u200b-\u200f\u2028-\u202f\u2060\ufeff]/;
|
|
230
|
+
const CONTROL_CHARS_RE = new RegExp(CONTROL_CHAR_RE.source, 'g');
|
|
231
|
+
/** C0 controls a terminal runs inside an escape sequence without ending it (all but CAN, SUB and ESC), and DEL. */
|
|
232
|
+
const SEQ_C0 = String.raw `\x00-\x17\x19\x1c-\x1f\x7f`;
|
|
233
|
+
/** Where a sequence ends early: CAN or SUB cancels it, and ESC, a C1 control or the end of the text interrupts it. */
|
|
234
|
+
const SEQ_CUT = String.raw `(?:[\x18\x1a]|(?=[\x1b\x80-\x9f]|$))`;
|
|
235
|
+
const SEQ_ESC = String.raw `\x1b[${SEQ_C0}]*`;
|
|
236
|
+
/** Parameters and intermediates in any order: a parameter after an intermediate makes a terminal ignore the CSI. */
|
|
237
|
+
const SEQ_CSI = String.raw `(?:${SEQ_ESC}\[|\x9b)[ -?${SEQ_C0}]*(?:[@-~]|${SEQ_CUT})`;
|
|
238
|
+
const SEQ_NF = String.raw `${SEQ_ESC}[ -/][ -/${SEQ_C0}]*(?:[0-~]|${SEQ_CUT})`;
|
|
239
|
+
const SEQ_SINGLE = String.raw `${SEQ_ESC}[0-OQ-WYZ\\\x60-~]`;
|
|
240
|
+
/**
|
|
241
|
+
* An ECMA-48 escape sequence as a terminal parses it, or one control or zero-width character. The sequences, 7-bit
|
|
242
|
+
* `ESC …` or the C1 code point: CSI (`ESC [` … final byte), the strings OSC (… BEL or ST) and DCS, SOS, PM and APC
|
|
243
|
+
* (… ST), a character-set selection (`ESC ( B`), and a single-character escape (`ESC 7`); each also ends at `SEQ_CUT`.
|
|
244
|
+
* No part of a sequence can start the part after it, which keeps the scan linear.
|
|
245
|
+
*/
|
|
246
|
+
const ANSI_GAPS_RE = new RegExp([
|
|
247
|
+
SEQ_CSI,
|
|
248
|
+
String.raw `(?:${SEQ_ESC}\]|\x9d)[^\x07\x18\x1a\x1b\x80-\x9f]*(?:\x07|\x1b\\|\x9c|${SEQ_CUT})`,
|
|
249
|
+
String.raw `(?:${SEQ_ESC}[PX^_]|[\x90\x98\x9e\x9f])[^\x18\x1a\x1b\x80-\x9f]*(?:\x1b\\|\x9c|${SEQ_CUT})`,
|
|
250
|
+
SEQ_NF,
|
|
251
|
+
SEQ_SINGLE,
|
|
252
|
+
CONTROL_CHAR_RE.source,
|
|
253
|
+
].join('|'), 'g');
|
|
254
|
+
/** `ANSI_GAPS_RE` with a string's payload kept: only its opener and terminator are gaps. */
|
|
255
|
+
const ANSI_PAYLOAD_GAPS_RE = new RegExp([SEQ_CSI, String.raw `${SEQ_ESC}[\]PX^_]`, SEQ_NF, SEQ_SINGLE, CONTROL_CHAR_RE.source].join('|'), 'g');
|
|
256
|
+
/**
|
|
257
|
+
* The views a split token is matched on: without whole escape sequences, without them but keeping string payloads (a
|
|
258
|
+
* terminal title can hold a token), and without control characters only (an escape's final byte can be a token's).
|
|
259
|
+
*/
|
|
260
|
+
const SPLIT_VIEWS = [ANSI_GAPS_RE, ANSI_PAYLOAD_GAPS_RE, CONTROL_CHARS_RE];
|
|
261
|
+
const ANSI_GAP_AT_RE = new RegExp(ANSI_GAPS_RE.source, 'y');
|
|
262
|
+
const TOKEN_CHAR_RE = /[A-Za-z0-9_-]/;
|
|
263
|
+
/** A string sequence's opener, with an OSC's command number (`8;`), which is no payload. */
|
|
264
|
+
const STRING_OPENER_RE = new RegExp(String.raw `^(?:(?:${SEQ_ESC}\]|\x9d)(?:\d*;)?|${SEQ_ESC}[PX^_]|[\x90\x98\x9e\x9f])`);
|
|
265
|
+
const PAYLOAD_RUN_CAP = 65_536;
|
|
266
|
+
const CRED_TEST_RE = new RegExp(CRED_RE.source);
|
|
267
|
+
/** `CRED_RE`'s prefix and body from a position, without its boundaries (a split token checks the original ones). */
|
|
268
|
+
const CRED_RUN_RE = new RegExp(CRED_RE.source.replace(/^\(\?<!\[A-Za-z0-9_-\]\)/, '').replace(/\(\?!\[A-Za-z0-9_-\]\)$/, ''), 'y');
|
|
269
|
+
const SPLIT_TOKEN_CAP = 1024;
|
|
270
|
+
/** Where a `CRED_RE` prefix and body start at a boundary, however they end. */
|
|
271
|
+
const CRED_START_RE = new RegExp(CRED_RE.source.replace(/\(\?!\[A-Za-z0-9_-\]\)$/, ''), 'g');
|
|
272
|
+
const CRED_WHOLE_RE = new RegExp(`^${CRED_RE.source}$`);
|
|
273
|
+
/** `CRED_START_RE` and `CRED_RUN_RE` without their body: a credential's prefix, however little of its body follows. */
|
|
274
|
+
const CRED_PREFIX_RE = new RegExp(CRED_START_RE.source.replace(/\{\d+,?\}/g, '{0}'), 'g');
|
|
275
|
+
const CRED_PREFIX_RUN_RE = new RegExp(CRED_RUN_RE.source.replace(/\{\d+,?\}/g, '{0}'), 'y');
|
|
276
|
+
/** A URL query parameter's value, up to the next `&` or `#`. */
|
|
277
|
+
const QUERY_VALUE_RE = /[^&#]*/y;
|
|
174
278
|
const PRIVKEY_RE = /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*?-----END[A-Z ]*PRIVATE KEY-----/g;
|
|
175
279
|
/** A private key whose end marker is missing (a display cap cut it off): masked to the end of the text. */
|
|
176
280
|
const PRIVKEY_OPEN_RE = /-----BEGIN[A-Z ]*PRIVATE KEY-----[\s\S]*$/;
|
|
177
281
|
const CODE_ENV_KEY_LITERAL_RE = /([A-Z0-9_]{0,50}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Z0-9_]{0,50}=)(["'][)\]:,]+|[)\]:,]+)/y;
|
|
178
282
|
const ENV_KEY_PREFIX_RE = /([A-Z0-9_]{0,50}(?:API_?KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|AUTH)[A-Z0-9_]{0,50}=)/g;
|
|
179
283
|
const REDACTED_ENV_VALUE_RE = /(?:\*{3,}|[A-Za-z0-9][A-Za-z0-9_.:/+-]{0,32}\.\.\.[A-Za-z0-9_.:/+-]{1,16})/y;
|
|
284
|
+
/**
|
|
285
|
+
* Where a credential split by control or zero-width characters (`ghp_abc\x1bdef`, `ghp_abc\ndef…`) starting at `i` of the
|
|
286
|
+
* stripped text ends there, or -1. The run of its prefix and body is read once (capped), then the longest candidate is
|
|
287
|
+
* taken that ends at a piece boundary of the original, is a whole credential, is not the name of a `KEY=` that follows,
|
|
288
|
+
* and never reaches a line holding a whole token itself (that line is the prefix pass's and joins nothing).
|
|
289
|
+
*/
|
|
290
|
+
function splitTokenEnd(text, stripped, kept, i) {
|
|
291
|
+
CRED_RUN_RE.lastIndex = 0;
|
|
292
|
+
// ponytail: a split credential longer than SPLIT_TOKEN_CAP is cut at the cap; raise it if longer tokens appear.
|
|
293
|
+
const run = CRED_RUN_RE.exec(stripped.slice(i, i + SPLIT_TOKEN_CAP))?.[0];
|
|
294
|
+
if (!run)
|
|
295
|
+
return -1;
|
|
296
|
+
const start = kept[i];
|
|
297
|
+
let limit = kept[i + run.length - 1] + 1;
|
|
298
|
+
for (let lineStart = start; lineStart < limit;) {
|
|
299
|
+
const lineEnd = text.slice(lineStart, limit).search(/[\r\n]/);
|
|
300
|
+
const end = lineEnd === -1 ? limit : lineStart + lineEnd;
|
|
301
|
+
if (CRED_TEST_RE.test(text.slice(lineStart, end))) {
|
|
302
|
+
limit = lineStart === start ? end : lineStart;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (lineEnd === -1)
|
|
306
|
+
break;
|
|
307
|
+
lineStart = end + 1;
|
|
308
|
+
}
|
|
309
|
+
for (let e = i + run.length; e > i; e -= 1) {
|
|
310
|
+
const atBoundary = e === stripped.length || kept[e] - kept[e - 1] > 1 || (e === i + run.length && !/[A-Za-z0-9_-]/.test(stripped[e]));
|
|
311
|
+
const originalEnd = kept[e - 1] + 1;
|
|
312
|
+
if (!atBoundary || originalEnd > limit || !CRED_WHOLE_RE.test(stripped.slice(i, e)) || /^[ \t]*=/.test(text.slice(originalEnd, originalEnd + 64)))
|
|
313
|
+
continue;
|
|
314
|
+
return e;
|
|
315
|
+
}
|
|
316
|
+
return -1;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* The original spans, and the joined text, of prefixed credentials whose body a gap of one of `SPLIT_VIEWS` splits,
|
|
320
|
+
* matched on the text without those gaps, plus `payloadRunSpan`'s from each prefix. A token starts at a boundary of the
|
|
321
|
+
* stripped text, or where a stripped gap hid the original one (`note\nghp_…`); one starting inside a token already
|
|
322
|
+
* masked is part of it.
|
|
323
|
+
*/
|
|
324
|
+
function splitTokenSpans(text, gaps) {
|
|
325
|
+
const stripped = text.replace(gaps, '');
|
|
326
|
+
if (stripped.length === text.length)
|
|
327
|
+
return [];
|
|
328
|
+
// The original index of each kept character.
|
|
329
|
+
const kept = [];
|
|
330
|
+
let at = 0;
|
|
331
|
+
for (const m of text.matchAll(gaps)) {
|
|
332
|
+
while (at < m.index)
|
|
333
|
+
kept.push(at++);
|
|
334
|
+
at += m[0].length;
|
|
335
|
+
}
|
|
336
|
+
while (at < text.length)
|
|
337
|
+
kept.push(at++);
|
|
338
|
+
const starts = [...stripped.matchAll(CRED_START_RE)].map((m) => m.index);
|
|
339
|
+
const prefixStarts = [...stripped.matchAll(CRED_PREFIX_RE)].map((m) => m.index);
|
|
340
|
+
for (let i = 1; i < kept.length; i += 1) {
|
|
341
|
+
if (kept[i] - kept[i - 1] <= 1 || !TOKEN_CHAR_RE.test(stripped[i - 1]))
|
|
342
|
+
continue;
|
|
343
|
+
starts.push(i);
|
|
344
|
+
CRED_PREFIX_RUN_RE.lastIndex = i;
|
|
345
|
+
if (CRED_PREFIX_RUN_RE.test(stripped))
|
|
346
|
+
prefixStarts.push(i);
|
|
347
|
+
}
|
|
348
|
+
const spans = [];
|
|
349
|
+
let scanned = 0;
|
|
350
|
+
for (const i of starts.sort((a, b) => a - b)) {
|
|
351
|
+
if (i < scanned)
|
|
352
|
+
continue;
|
|
353
|
+
const e = splitTokenEnd(text, stripped, kept, i);
|
|
354
|
+
if (e === -1)
|
|
355
|
+
continue;
|
|
356
|
+
spans.push([kept[i], kept[e - 1] + 1, stripped.slice(i, e)]);
|
|
357
|
+
scanned = e;
|
|
358
|
+
}
|
|
359
|
+
let walked = 0;
|
|
360
|
+
for (const i of prefixStarts.sort((a, b) => a - b)) {
|
|
361
|
+
if (kept[i] < walked)
|
|
362
|
+
continue;
|
|
363
|
+
const [span, reached] = payloadRunSpan(text, kept[i]);
|
|
364
|
+
if (span)
|
|
365
|
+
spans.push(span);
|
|
366
|
+
walked = reached;
|
|
367
|
+
}
|
|
368
|
+
return spans;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Fail-closed for a token split across string payloads: from a credential prefix at `start`, the span through the run of
|
|
372
|
+
* token characters and whole escape sequences after it, when a string payload in the run holds token characters, and
|
|
373
|
+
* where the run stopped. Any subset of payloads can hold a token's pieces and the rest junk
|
|
374
|
+
* (`\x1b]0;ghp_ab\x1b]0;x_y\x07cd…`), so the run is masked whole rather than rebuilt.
|
|
375
|
+
*/
|
|
376
|
+
function payloadRunSpan(text, start) {
|
|
377
|
+
let token = '';
|
|
378
|
+
let end = start;
|
|
379
|
+
let hidden = false;
|
|
380
|
+
let k = start;
|
|
381
|
+
// ponytail: a run longer than PAYLOAD_RUN_CAP is masked in pieces; raise it if longer hidden tokens appear.
|
|
382
|
+
while (k < text.length && k - start < PAYLOAD_RUN_CAP) {
|
|
383
|
+
ANSI_GAP_AT_RE.lastIndex = k;
|
|
384
|
+
const gap = ANSI_GAP_AT_RE.exec(text)?.[0];
|
|
385
|
+
if (gap) {
|
|
386
|
+
const from = STRING_OPENER_RE.exec(gap)?.[0].length ?? gap.length;
|
|
387
|
+
for (let j = from; j < gap.length; j += 1) {
|
|
388
|
+
if (!TOKEN_CHAR_RE.test(gap[j]))
|
|
389
|
+
continue;
|
|
390
|
+
token += gap[j];
|
|
391
|
+
end = k + j + 1;
|
|
392
|
+
hidden = true;
|
|
393
|
+
}
|
|
394
|
+
k += gap.length;
|
|
395
|
+
}
|
|
396
|
+
else if (TOKEN_CHAR_RE.test(text[k])) {
|
|
397
|
+
token += text[k];
|
|
398
|
+
end = k += 1;
|
|
399
|
+
}
|
|
400
|
+
else
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
return [hidden ? [start, end, token] : null, k];
|
|
404
|
+
}
|
|
405
|
+
/** Masks each union of the split-token spans every view finds, so no view masks a piece of a token another one joins. */
|
|
406
|
+
function maskSplitTokens(text) {
|
|
407
|
+
const spans = SPLIT_VIEWS.flatMap((gaps) => splitTokenSpans(text, gaps)).sort((a, b) => a[0] - b[0]);
|
|
408
|
+
let out = '';
|
|
409
|
+
let last = 0;
|
|
410
|
+
for (let k = 0; k < spans.length;) {
|
|
411
|
+
const start = spans[k][0];
|
|
412
|
+
let [, end, token] = spans[k];
|
|
413
|
+
for (k += 1; k < spans.length && spans[k][0] < end; k += 1) {
|
|
414
|
+
const [, nextEnd, nextToken] = spans[k];
|
|
415
|
+
end = Math.max(end, nextEnd);
|
|
416
|
+
if (nextToken.length > token.length)
|
|
417
|
+
token = nextToken;
|
|
418
|
+
}
|
|
419
|
+
out += text.slice(last, start) + mask(token);
|
|
420
|
+
last = end;
|
|
421
|
+
}
|
|
422
|
+
return out + text.slice(last);
|
|
423
|
+
}
|
|
180
424
|
function mask(token) {
|
|
181
425
|
// By code point, so a partial mask never splits a surrogate pair into invalid JSON.
|
|
182
426
|
const chars = Array.from(token);
|
|
@@ -437,6 +681,38 @@ function maskShellWord(value) {
|
|
|
437
681
|
}
|
|
438
682
|
/** A shell word's content, for the "nothing to mask" checks: the inside of a single quoted piece, else the word. */
|
|
439
683
|
const shellWordInner = (value) => splitQuoted(value)?.inner ?? value;
|
|
684
|
+
/**
|
|
685
|
+
* `ENV_SUFFIX_RE` assignments whose name is a credential (`DB_PASS=`, `openai_key=`). A password-class name masks any value,
|
|
686
|
+
* punctuation-only included; a bare `KEY` masks an opaque one. A plain value keeps the Agent's partial mask; a quoted,
|
|
687
|
+
* escaped or composed one is masked whole. Env lookups (`os.getenv(…)`) and masked values stay.
|
|
688
|
+
*/
|
|
689
|
+
function redactEnvSuffixes(text) {
|
|
690
|
+
let out = '';
|
|
691
|
+
let last = 0;
|
|
692
|
+
const quoteAt = quoteTracker(text);
|
|
693
|
+
const closeOf = enclosingClose(text);
|
|
694
|
+
// A skipped value does not move the scan past its word: a later match inside that word reuses its end, and a value is
|
|
695
|
+
// judged by its head, so many assignments inside one long word (`a_key=x\ a_key=x\ …`) still scan it once.
|
|
696
|
+
let wordEnd = -1;
|
|
697
|
+
ENV_SUFFIX_RE.lastIndex = 0;
|
|
698
|
+
for (let m = ENV_SUFFIX_RE.exec(text); m; m = ENV_SUFFIX_RE.exec(text)) {
|
|
699
|
+
const key = m[1];
|
|
700
|
+
const valueStart = m.index + m[0].length;
|
|
701
|
+
wordEnd = valueStart < wordEnd ? wordEnd : shellWordEnd(text, valueStart, quoteAt(valueStart), closeOf);
|
|
702
|
+
// A URL query parameter's value ends at the next `&` or `#`, as in `redactCredentialParams`.
|
|
703
|
+
QUERY_VALUE_RE.lastIndex = valueStart;
|
|
704
|
+
const valueEnd = /[?&]/.test(text[m.index - 1] ?? '') ? Math.min(wordEnd, valueStart + QUERY_VALUE_RE.exec(text)[0].length) : wordEnd;
|
|
705
|
+
const value = text.slice(valueStart, valueEnd);
|
|
706
|
+
const head = value.slice(0, 128);
|
|
707
|
+
const inner = shellWordInner(head);
|
|
708
|
+
if (!inner.trim() || inner === '***' || !isEnvCredential(key, shellDequote(head)))
|
|
709
|
+
continue;
|
|
710
|
+
out += text.slice(last, valueStart) + (/^[^\s'"\\$`]+$/.test(value) ? mask(value) : maskShellWord(value));
|
|
711
|
+
last = valueEnd;
|
|
712
|
+
ENV_SUFFIX_RE.lastIndex = Math.max(valueEnd, ENV_SUFFIX_RE.lastIndex);
|
|
713
|
+
}
|
|
714
|
+
return out + text.slice(last);
|
|
715
|
+
}
|
|
440
716
|
/** Credential parameters (`CRED_PARAM_RE`) with their whole shell-word value fully masked. */
|
|
441
717
|
function redactCredentialParams(text) {
|
|
442
718
|
let out = '';
|
|
@@ -565,6 +841,198 @@ function redactUserFlags(text) {
|
|
|
565
841
|
}
|
|
566
842
|
return out + text.slice(last);
|
|
567
843
|
}
|
|
844
|
+
const REGISTRY_LOGIN = { separate: ['-p'], attached: ['-p'], login: true, switches: ['-D', '--debug', '--tls', '--tlsverify', '-r', '--remote', '--syslog'] };
|
|
845
|
+
const MYSQL = { attached: ['-p'] };
|
|
846
|
+
const COMMAND_FLAGS = {
|
|
847
|
+
docker: REGISTRY_LOGIN, podman: REGISTRY_LOGIN, buildah: REGISTRY_LOGIN, nerdctl: REGISTRY_LOGIN, skopeo: REGISTRY_LOGIN, oras: REGISTRY_LOGIN, helm: REGISTRY_LOGIN,
|
|
848
|
+
mysql: MYSQL, mysqladmin: MYSQL, mysqldump: MYSQL, mysqlimport: MYSQL, mysqlshow: MYSQL, mysqlcheck: MYSQL, mysqlslap: MYSQL, mysql_upgrade: MYSQL, mysqlbinlog: MYSQL, mysqlsh: MYSQL,
|
|
849
|
+
mariadb: MYSQL, 'mariadb-admin': MYSQL, 'mariadb-dump': MYSQL, 'mariadb-import': MYSQL, 'mariadb-show': MYSQL, 'mariadb-check': MYSQL, 'mariadb-slap': MYSQL, 'mariadb-upgrade': MYSQL, 'mariadb-binlog': MYSQL,
|
|
850
|
+
sshpass: { separate: ['-p'], attached: ['-p'], values: ['-f', '-d', '-P'], stop: true },
|
|
851
|
+
'redis-cli': { separate: ['-a', '--pass'] },
|
|
852
|
+
smbclient: { separate: ['-U', '--user'], attached: ['-U', '--user='], percent: true },
|
|
853
|
+
};
|
|
854
|
+
/**
|
|
855
|
+
* A known command's name as a word: bare, after a path, a listed argv element, or composed by quotes (`do"cker"`); or a
|
|
856
|
+
* word the shell computes in whole or part (`$CLIENT`, `$(echo $(which mysql))`, `` `printf docker` ``, `my${EMPTY}sql`),
|
|
857
|
+
* which may name any of them wherever it stands (`sudo -u root $CLIENT -ppw`). A computed word is matched whole when its
|
|
858
|
+
* pieces are flat, so a quoted one reads as a listed element; otherwise up to its first expansion, and read from there.
|
|
859
|
+
*/
|
|
860
|
+
/** A parameter, substitution or backtick piece of a word; a name is read whole, so a word splits into pieces one way only. */
|
|
861
|
+
const EXPANSION = String.raw `\$[A-Za-z_][A-Za-z0-9_]*(?![A-Za-z0-9_])|\$\{[^{}\n]*\}|\$\([^()\n]*\)|\x60[^\x60\n]*\x60`;
|
|
862
|
+
const COMMAND_FLAG_RE = new RegExp(String.raw `(?<![^\s;&|()\x60'"/,[\\])(?:(?:${Object.keys(COMMAND_FLAGS).map((name) => name.replaceAll(/(?<=.)(?=.)/g, String.raw `["'\\]*`)).join('|')}|[\w./-]*(?:${EXPANSION})(?:[\w./-]|${EXPANSION})*)(?=[\s;&|)'",\]]|$)|[\w./-]*(?=\$[({A-Za-z_]|\x60))`, 'g');
|
|
863
|
+
/** Every command's flags, for an executable the shell computes. */
|
|
864
|
+
const ALL_COMMAND_FLAGS = [...new Set(Object.values(COMMAND_FLAGS))];
|
|
865
|
+
const COMMAND_FLAG_TEST_RE = new RegExp(COMMAND_FLAG_RE.source);
|
|
866
|
+
/**
|
|
867
|
+
* For an argv (`words` as the programs receive them), the words holding a command's secret, each with the offset it starts
|
|
868
|
+
* at. Every known command named so far is read at once, so a wrapper's operand naming one (`sudo -u mysql docker login -p
|
|
869
|
+
* pw`) does not hide the command it runs.
|
|
870
|
+
*/
|
|
871
|
+
function commandFlagMasks(words) {
|
|
872
|
+
const masks = new Map();
|
|
873
|
+
const states = new Map();
|
|
874
|
+
const secret = (flags, index, value, from) => {
|
|
875
|
+
if (value === undefined)
|
|
876
|
+
return;
|
|
877
|
+
const at = flags.percent ? value.indexOf('%', from) + 1 : from;
|
|
878
|
+
if ((at || !flags.percent) && value.slice(at) && value.slice(at) !== '***')
|
|
879
|
+
masks.set(index, at);
|
|
880
|
+
};
|
|
881
|
+
for (let i = 0; i < words.length; i += 1) {
|
|
882
|
+
const word = words[i];
|
|
883
|
+
for (const [flags, state] of states) {
|
|
884
|
+
if (i < state.next)
|
|
885
|
+
continue;
|
|
886
|
+
if (!state.active) {
|
|
887
|
+
// The subcommand is the first operand past the options; one right after a bare option may be its value. One the shell
|
|
888
|
+
// computes (`docker "$ACTION" -p pw`) may be `login`.
|
|
889
|
+
if (word === 'login' || /[$`]/.test(word))
|
|
890
|
+
state.active = true;
|
|
891
|
+
else if (!word.startsWith('-') && word !== 'registry' && !(/^-[^=]*$/.test(words[i - 1]) && !flags.switches?.includes(words[i - 1])))
|
|
892
|
+
states.delete(flags);
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
if (flags.separate?.includes(word)) {
|
|
896
|
+
secret(flags, i + 1, words[i + 1], 0);
|
|
897
|
+
state.next = i + 2;
|
|
898
|
+
continue;
|
|
899
|
+
}
|
|
900
|
+
const prefix = flags.attached?.find((p) => word.length > p.length && word.startsWith(p));
|
|
901
|
+
if (prefix !== undefined) {
|
|
902
|
+
secret(flags, i, word, prefix.length + (word[prefix.length] === '=' ? 1 : 0));
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (flags.values?.includes(word)) {
|
|
906
|
+
state.next = i + 2;
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (flags.stop && !word.startsWith('-'))
|
|
910
|
+
states.delete(flags);
|
|
911
|
+
}
|
|
912
|
+
const name = word.slice(word.lastIndexOf('/') + 1);
|
|
913
|
+
// A command named again (`docker login docker -p pw`, a host) keeps the state it is in. A word the shell computes may
|
|
914
|
+
// be the executable (`sudo -u root $CLIENT -ppw`), so it may be any known command.
|
|
915
|
+
const named = Object.hasOwn(COMMAND_FLAGS, name) ? [COMMAND_FLAGS[name]] : !word.startsWith('-') && /[$`]/.test(word) ? ALL_COMMAND_FLAGS : [];
|
|
916
|
+
for (const flags of named)
|
|
917
|
+
if (!states.has(flags))
|
|
918
|
+
states.set(flags, { active: !flags.login, next: i + 1 });
|
|
919
|
+
}
|
|
920
|
+
return masks;
|
|
921
|
+
}
|
|
922
|
+
/** `raw`, a shell word reading `plain`, masked from `plain`'s offset `from`; a word whose kept part is not literal is masked whole. */
|
|
923
|
+
function maskWordFrom(raw, plain, from) {
|
|
924
|
+
if (!from)
|
|
925
|
+
return maskShellWord(raw);
|
|
926
|
+
const kept = plain.slice(0, from);
|
|
927
|
+
const quoted = splitQuoted(raw);
|
|
928
|
+
if (quoted?.inner.startsWith(kept))
|
|
929
|
+
return `${quoted.open}${kept}***${quoted.close}`;
|
|
930
|
+
return raw.startsWith(kept) ? kept + maskShellWord(raw.slice(from)) : maskShellWord(raw);
|
|
931
|
+
}
|
|
932
|
+
/** A redirection operator, with its file descriptor. */
|
|
933
|
+
const REDIRECT_RE = /[0-9]*(?:&>>?|<<<|<<-?|<>|>>|>&|<&|>\||[<>])/y;
|
|
934
|
+
/**
|
|
935
|
+
* The words of the simple command (or `list`ed argv) at `from`, up to a shell metacharacter, the line end, a list's close
|
|
936
|
+
* or the close of the `enclosing` quote, and where the scan stopped. An unterminated quote ends the command with its word.
|
|
937
|
+
* Only a list's elements end at `,`, `]` or `}`; in a shell word they are ordinary characters (`-phunter,2`).
|
|
938
|
+
*/
|
|
939
|
+
function commandWords(text, from, enclosing, list) {
|
|
940
|
+
const spans = [];
|
|
941
|
+
const [gap, close, stop] = list ? [/[ \t,<>]/, /[\n;&|()\]}]/, /[\s;&|()<>,\]}]/] : [/[ \t]/, /[\n;&|()]/, /[\s;&|()<>]/];
|
|
942
|
+
let i = from;
|
|
943
|
+
let redirect = false;
|
|
944
|
+
for (;;) {
|
|
945
|
+
while (i < text.length && gap.test(text[i]))
|
|
946
|
+
i += 1;
|
|
947
|
+
// A redirection and its target (`</dev/null`, `2> /tmp/e`, `2>&1`) are no argument: the shell removes them.
|
|
948
|
+
REDIRECT_RE.lastIndex = i;
|
|
949
|
+
if (!list && i < text.length && text[i] !== enclosing && REDIRECT_RE.test(text)) {
|
|
950
|
+
i = REDIRECT_RE.lastIndex;
|
|
951
|
+
redirect = true;
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (i >= text.length || text[i] === enclosing || close.test(text[i]))
|
|
955
|
+
return { spans, end: i };
|
|
956
|
+
const start = i;
|
|
957
|
+
while (i < text.length && text[i] !== enclosing && !stop.test(text[i])) {
|
|
958
|
+
const c = text[i];
|
|
959
|
+
if (c === '\\')
|
|
960
|
+
i += 2;
|
|
961
|
+
else if ((c === "'" || c === '"') && c !== enclosing) {
|
|
962
|
+
// A quote groups across newlines, and an inner quote of the other kind (`sh -c "mysql -p'a b'"`) groups for the shell
|
|
963
|
+
// that runs the command.
|
|
964
|
+
let k = i + 1;
|
|
965
|
+
while (k < text.length && text[k] !== c && text[k] !== enclosing)
|
|
966
|
+
k += c === '"' && text[k] === '\\' ? 2 : 1;
|
|
967
|
+
// An unterminated quote runs to the text end (or the enclosing close); its word is still read, so its secret is masked.
|
|
968
|
+
if (text[k] !== c) {
|
|
969
|
+
spans.push([start, Math.min(k, text.length)]);
|
|
970
|
+
return { spans, end: Math.min(k, text.length) };
|
|
971
|
+
}
|
|
972
|
+
i = k + 1;
|
|
973
|
+
}
|
|
974
|
+
else if (c === '`' || (c === '$' && (text[i + 1] === '(' || text[i + 1] === '{'))) {
|
|
975
|
+
// A substitution is part of its word (`-p$(printf pw)`), spaces and nested ones included.
|
|
976
|
+
const [open, close] = c === '`' ? ['', '`'] : [text[i + 1], text[i + 1] === '(' ? ')' : '}'];
|
|
977
|
+
let k = i + 1;
|
|
978
|
+
for (let depth = 1; k < text.length && text[k] !== enclosing; k += 1) {
|
|
979
|
+
if (text[k] === close && (depth -= 1) === 0)
|
|
980
|
+
break;
|
|
981
|
+
if (text[k] === open && k > i + 1)
|
|
982
|
+
depth += 1;
|
|
983
|
+
}
|
|
984
|
+
if (text[k] !== close) {
|
|
985
|
+
spans.push([start, Math.min(k, text.length)]);
|
|
986
|
+
return { spans, end: Math.min(k, text.length) };
|
|
987
|
+
}
|
|
988
|
+
i = k + 1;
|
|
989
|
+
}
|
|
990
|
+
else
|
|
991
|
+
i += 1;
|
|
992
|
+
}
|
|
993
|
+
if (!redirect)
|
|
994
|
+
spans.push([start, Math.min(i, text.length)]);
|
|
995
|
+
redirect = false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* `COMMAND_FLAGS` secrets in shell text and listed argv text (`['mysql', '-phunter2']`). A quoted word that holds a known
|
|
1000
|
+
* command (`sh -c 'mysql -phunter2'`) is read as a command of its own.
|
|
1001
|
+
*/
|
|
1002
|
+
function redactCommandFlags(text) {
|
|
1003
|
+
let out = '';
|
|
1004
|
+
let last = 0;
|
|
1005
|
+
const quoteAt = quoteTracker(text);
|
|
1006
|
+
const re = new RegExp(COMMAND_FLAG_RE);
|
|
1007
|
+
for (let m = re.exec(text); m; m = re.exec(text)) {
|
|
1008
|
+
const quote = quoteAt(m.index).slice(-1);
|
|
1009
|
+
// A listed element (`'mysql',`) is read from its opening quote; a command inside a quoted argument ends at its close.
|
|
1010
|
+
let listed = quote !== '' && text[m.index + m[0].length] === quote;
|
|
1011
|
+
// A computed word matched only to its first expansion (`"$(echo $(which docker))"`) is listed when it fills its quotes.
|
|
1012
|
+
if (!listed && quote && text[m.index - 1] === quote) {
|
|
1013
|
+
const probe = commandWords(text, m.index, quote, false);
|
|
1014
|
+
listed = probe.spans.length === 1 && text[probe.end] === quote;
|
|
1015
|
+
}
|
|
1016
|
+
const { spans, end } = commandWords(text, listed ? text.lastIndexOf(quote, m.index) : m.index, listed ? '' : quote, listed);
|
|
1017
|
+
const words = spans.map(([start, stop]) => shellDequote(text.slice(start, stop)));
|
|
1018
|
+
const masks = commandFlagMasks(words);
|
|
1019
|
+
for (const [k, [start, stop]] of spans.entries()) {
|
|
1020
|
+
if (start < last)
|
|
1021
|
+
continue;
|
|
1022
|
+
const raw = text.slice(start, stop);
|
|
1023
|
+
const from = masks.get(k);
|
|
1024
|
+
// A nested command is a quoted or substituted word with a space, strictly inside this text.
|
|
1025
|
+
const nested = (listed || !quote) && raw.length < text.length && /['"]|\$\(|\x60/.test(raw) && /\s/.test(raw) && COMMAND_FLAG_TEST_RE.test(raw);
|
|
1026
|
+
const replaced = from !== undefined ? maskWordFrom(raw, words[k], from) : nested ? redactCommandFlags(raw) : raw;
|
|
1027
|
+
if (replaced === raw)
|
|
1028
|
+
continue;
|
|
1029
|
+
out += text.slice(last, start) + replaced;
|
|
1030
|
+
last = stop;
|
|
1031
|
+
}
|
|
1032
|
+
re.lastIndex = Math.max(end, re.lastIndex, m.index + 1);
|
|
1033
|
+
}
|
|
1034
|
+
return out + text.slice(last);
|
|
1035
|
+
}
|
|
568
1036
|
/**
|
|
569
1037
|
* Header credentials (`AUTH_HDR_RE`, `BEARER_RE`: a head group, then the credential). A parameterized credential is masked
|
|
570
1038
|
* whole; a single credential is read to the end of its shell word, so an adjacent quoted piece (`Bearer foo'bar`, which
|
|
@@ -1315,7 +1783,7 @@ export function redactSensitive(text) {
|
|
|
1315
1783
|
function redactRules(text) {
|
|
1316
1784
|
if (!text)
|
|
1317
1785
|
return text;
|
|
1318
|
-
let out = text.replace(CRED_RE, (_, t) => mask(t));
|
|
1786
|
+
let out = maskSplitTokens(text).replace(CRED_RE, (_, t) => mask(t));
|
|
1319
1787
|
out = out.replace(EMBEDDED_AWS_RE, (t) => mask(t));
|
|
1320
1788
|
out = redactHeaderCredentials(out, AUTH_HDR_RE);
|
|
1321
1789
|
out = out.replace(JWT_RE, (t) => mask(t));
|
|
@@ -1323,7 +1791,8 @@ function redactRules(text) {
|
|
|
1323
1791
|
for (const re of [COOKIE_ANSI_RE, COOKIE_SQ_RE, COOKIE_DQ_RE, COOKIE_BARE_RE])
|
|
1324
1792
|
out = out.replace(re, (whole, head, value) => (/[A-Za-z0-9]/.test(value) ? `${head}***` : whole));
|
|
1325
1793
|
out = redactCredentialParams(out);
|
|
1326
|
-
out = out.replace(ENV_RE, (whole, key, quote, value) => (/[A-Za-z0-9]/.test(value) ? `${key}=${quote}${mask(value)}${quote}` : whole));
|
|
1794
|
+
out = out.replace(ENV_RE, (whole, key, quote, value) => (/[A-Za-z0-9]/.test(value) && isEnvCredential(key, value) ? `${key}=${quote}${mask(value)}${quote}` : whole));
|
|
1795
|
+
out = redactEnvSuffixes(out);
|
|
1327
1796
|
out = out.replace(LISTED_FLAG_RE, (whole, q, dash, key, gap, vq, quotedValue, bare) => {
|
|
1328
1797
|
// An unquoted value (a number, `True`, a nested list) is masked whole.
|
|
1329
1798
|
if (bare !== undefined)
|
|
@@ -1338,7 +1807,11 @@ function redactRules(text) {
|
|
|
1338
1807
|
out = out.replace(PERCENT_KEY_RE, (whole, head, value) => (value && value !== '***' && isCredentialKey(percentDecode(head.slice(0, -1))) ? `${head}***` : whole));
|
|
1339
1808
|
out = out.replace(BARE_USERINFO_RE, (_, head, secret) => head + mask(secret));
|
|
1340
1809
|
out = out.replace(URL_USERINFO_RE, (_, head, secret) => head + (/['"\\]/.test(secret) ? '***' : mask(secret)));
|
|
1810
|
+
out = out.replace(URL_BARE_TOKEN_RE, (_, head, token) => head + mask(token));
|
|
1811
|
+
out = out.replace(TELEGRAM_TOKEN_RE, (_, bot, id) => `${bot ?? ''}${id}:***`);
|
|
1812
|
+
out = out.replace(PHONE_RE, (phone) => { const keep = phone.length <= 8 ? 2 : 4; return `${phone.slice(0, keep)}****${phone.slice(-keep)}`; });
|
|
1341
1813
|
out = redactUserFlags(out);
|
|
1814
|
+
out = redactCommandFlags(out);
|
|
1342
1815
|
out = out.replace(QUERY_KEY_RE, (whole, head, value) => (/[A-Za-z0-9]/.test(value) ? head + mask(value) : whole));
|
|
1343
1816
|
out = out.replace(PRIVKEY_RE, '[REDACTED PRIVATE KEY]').replace(PRIVKEY_OPEN_RE, '[REDACTED PRIVATE KEY]');
|
|
1344
1817
|
return restoreCodeEnvKeyLiterals(text, out);
|
|
@@ -1346,20 +1819,24 @@ function redactRules(text) {
|
|
|
1346
1819
|
const CASE_MARKERS = [
|
|
1347
1820
|
'sk-', 'ghp_', 'github_pat_', 'gho_', 'ghu_', 'ghs_', 'ghr_', 'AKIA', 'ASIA', 'xoxb-', 'xoxa-', 'xoxp-', 'xoxr-', 'xoxs-', 'AIza', 'pplx-', 'fal_', 'fc-',
|
|
1348
1821
|
'bb_live_', 'gAAAA', 'sk_live_', 'sk_test_', 'rk_live_', 'SG.', 'hf_', 'r8_', 'npm_', 'pypi-', 'dop_v1_', 'doo_v1_', 'am_', 'sk_', 'tvly-', 'exa_',
|
|
1349
|
-
'gsk_', 'syt_', 'retaindb_', 'hsk-', 'mem0_', 'brv_', '
|
|
1822
|
+
'gsk_', 'syt_', 'retaindb_', 'hsk-', 'mem0_', 'brv_', 'xapp-', 'xai-', 'ntn_', 'fw-', 'fw_', 'fpk_', 'glpat-', 'gloas-', 'gldt-', 'glrt-', 'glrtr-',
|
|
1823
|
+
'glcbt-', 'glptt-', 'glft-', 'glimt-', 'glagent-', 'glsoat-', 'glffct-', 'glwt-', 'GR1348941', 'pk-lf-', 'eyJ', '-----BEGIN',
|
|
1350
1824
|
];
|
|
1351
1825
|
const LOWER_MARKERS = [
|
|
1352
1826
|
'authorization: bearer ', 'authorization: bot ', 'private key', 'postgres://', 'postgresql://', 'mysql://', 'mongodb://', 'redis://', 'amqp://', '://',
|
|
1353
1827
|
'access_token', 'refresh_token', 'id_token', 'api_key', 'apikey', 'client_secret', 'auth_token', 'raw_secret', 'secret_input', 'key_material',
|
|
1354
1828
|
'x-amz-signature', 'token=', 'secret=', 'password=', 'passwd', 'password', 'secret', 'token', 'api-key', 'apikey', 'clientsecret', 'private_key', 'credential', ' -u ', '--user ', 'authorization', 'signature', 'bearer ', 'cookie:', 'authorization=', 'key=', '"token"', '"secret"', '"password"', '"bearer"',
|
|
1355
1829
|
];
|
|
1356
|
-
const TELEGRAM_RE = /(?:bot)?\d{8,}:[-A-Za-z0-9_]{30,}/;
|
|
1357
1830
|
const DISCORD_RE = /<@!?\d{17,20}>/;
|
|
1358
|
-
const
|
|
1831
|
+
const TELEGRAM_TEST_RE = new RegExp(TELEGRAM_TOKEN_RE.source);
|
|
1832
|
+
const PHONE_TEST_RE = new RegExp(PHONE_RE.source);
|
|
1359
1833
|
export function mightContainSensitiveText(text) {
|
|
1360
1834
|
if (!text)
|
|
1361
1835
|
return false;
|
|
1362
|
-
|
|
1836
|
+
// A control or zero-width character or an ANSI escape sequence inside a prefix (`x\u200bai-…`) does not hide it: the
|
|
1837
|
+
// redactor joins split tokens.
|
|
1838
|
+
const views = SPLIT_VIEWS.map((gaps) => text.replace(gaps, ''));
|
|
1839
|
+
if (CASE_MARKERS.some((m) => views.some((view) => view.includes(m))))
|
|
1363
1840
|
return true;
|
|
1364
1841
|
const lower = text.toLowerCase();
|
|
1365
1842
|
if (LOWER_MARKERS.some((m) => lower.includes(m)))
|
|
@@ -1380,11 +1857,15 @@ export function mightContainSensitiveText(text) {
|
|
|
1380
1857
|
return true;
|
|
1381
1858
|
if (USER_FLAG_TEST_RE.test(text))
|
|
1382
1859
|
return true;
|
|
1383
|
-
if (
|
|
1860
|
+
if (COMMAND_FLAG_TEST_RE.test(text))
|
|
1861
|
+
return true;
|
|
1862
|
+
if (text.includes(':') && TELEGRAM_TEST_RE.test(text))
|
|
1384
1863
|
return true;
|
|
1385
1864
|
if (text.includes('<@') && DISCORD_RE.test(text))
|
|
1386
1865
|
return true;
|
|
1387
|
-
if (text.includes('+') &&
|
|
1866
|
+
if (text.includes('+') && PHONE_TEST_RE.test(text))
|
|
1867
|
+
return true;
|
|
1868
|
+
if (text.includes('=') && ENV_SUFFIX_TEST_RE.test(text))
|
|
1388
1869
|
return true;
|
|
1389
1870
|
return false;
|
|
1390
1871
|
}
|
|
@@ -1424,7 +1905,7 @@ export function redactValue(v, enabled) {
|
|
|
1424
1905
|
return Object.fromEntries(Object.entries(v).map(([k, val]) => [k, redactValue(val, enabled)]));
|
|
1425
1906
|
return v;
|
|
1426
1907
|
}
|
|
1427
|
-
export const PUBLIC_MESSAGE_INTERNAL_FIELDS = new Set(['api_content', '_row_id', '_state_db_row_id', '_db_row_id', 'state_db_row_id', '_active_turn_token', '_active_turn_user', '_fork_child_turn']);
|
|
1908
|
+
export const PUBLIC_MESSAGE_INTERNAL_FIELDS = new Set(['api_content', '_row_id', '_state_db_row_id', '_state_db_generation', '_db_row_id', 'state_db_row_id', '_active_turn_token', '_active_turn_user', '_fork_child_turn']);
|
|
1428
1909
|
export function copyJson(value) {
|
|
1429
1910
|
if (Array.isArray(value))
|
|
1430
1911
|
return value.map((v) => copyJson(v));
|
|
@@ -1531,6 +2012,19 @@ export function buildActiveTurnToken(streamId, pendingStartedAt) {
|
|
|
1531
2012
|
return null;
|
|
1532
2013
|
return `${str(streamId).trim()}:${formatG17(started)}`;
|
|
1533
2014
|
}
|
|
2015
|
+
/** TAL-452: a user message longer than either limit ships `_collapsible`, so clients fold it behind "Show more". */
|
|
2016
|
+
const COLLAPSIBLE_USER_MESSAGE_LINES = 20;
|
|
2017
|
+
const COLLAPSIBLE_USER_MESSAGE_CHARS = 2000;
|
|
2018
|
+
/**
|
|
2019
|
+
* Whether a public (redacted) row is a typed user message (no steer, wakeup or marker) longer than either limit, measured
|
|
2020
|
+
* without the `[Attached files: ...]` line clients hide.
|
|
2021
|
+
*/
|
|
2022
|
+
function isCollapsibleUserMessage(item) {
|
|
2023
|
+
if (item.role !== 'user' || item._steer !== undefined || item._background_update !== undefined || item._marker_kind !== undefined)
|
|
2024
|
+
return false;
|
|
2025
|
+
const text = stripAttachedFilesMarker(messageText(item.content));
|
|
2026
|
+
return text.length > COLLAPSIBLE_USER_MESSAGE_CHARS || text.split('\n').length > COLLAPSIBLE_USER_MESSAGE_LINES;
|
|
2027
|
+
}
|
|
1534
2028
|
function publicMessageProjection(message, enabled, activeTurnToken) {
|
|
1535
2029
|
const isActive = Boolean(message && typeof message === 'object' && message.role === 'user' && activeTurnToken !== null && message._active_turn_token === activeTurnToken);
|
|
1536
2030
|
const scrubbed = scrubInternalReplayFields([message], { messageRecords: true })[0];
|
|
@@ -1544,6 +2038,11 @@ function publicMessageProjection(message, enabled, activeTurnToken) {
|
|
|
1544
2038
|
}
|
|
1545
2039
|
if (isActive)
|
|
1546
2040
|
item._active_turn_user = true;
|
|
2041
|
+
// Server-owned: a `_collapsible` the stored row carries never survives the projection's own decision.
|
|
2042
|
+
if (isCollapsibleUserMessage(item))
|
|
2043
|
+
item._collapsible = true;
|
|
2044
|
+
else
|
|
2045
|
+
delete item._collapsible;
|
|
1547
2046
|
return withMessageToolDisplay(scrubbed, item, enabled);
|
|
1548
2047
|
}
|
|
1549
2048
|
/**
|
|
@@ -1599,7 +2098,11 @@ function redactArgs(value, enabled) {
|
|
|
1599
2098
|
if (value.length === 2 && typeof value[0] === 'string' && isCredentialKey(value[0]))
|
|
1600
2099
|
return [value[0], maskLeaves(value[1])];
|
|
1601
2100
|
// An argv array (`['login', '--password', 'hunter2']`): a credential flag's value is the next element.
|
|
2101
|
+
const commandMasks = commandFlagMasks(value.map((item) => (typeof item === 'string' || typeof item === 'number' ? String(item) : '')));
|
|
1602
2102
|
return value.map((item, i) => {
|
|
2103
|
+
const from = commandMasks.get(i);
|
|
2104
|
+
if (from !== undefined)
|
|
2105
|
+
return typeof item === 'string' ? maskWordFrom(item, item, from) : maskLeaves(item);
|
|
1603
2106
|
const flag = value[i - 1];
|
|
1604
2107
|
if (typeof flag === 'string' && item !== '' && item !== null && item !== undefined) {
|
|
1605
2108
|
if (ARGV_USER_FLAG_RE.test(flag))
|
|
@@ -1680,7 +2183,7 @@ export function withToolId(data, id) {
|
|
|
1680
2183
|
delete frame.tid;
|
|
1681
2184
|
return frame;
|
|
1682
2185
|
}
|
|
1683
|
-
function redactNestedMessageContainers(value, enabled) {
|
|
2186
|
+
export function redactNestedMessageContainers(value, enabled) {
|
|
1684
2187
|
const scrubbed = scrubInternalReplayFields(value);
|
|
1685
2188
|
if (!scrubbed || typeof scrubbed !== 'object' || Array.isArray(scrubbed))
|
|
1686
2189
|
return redactValue(scrubbed, enabled);
|