cli-jaw 2.2.8 → 2.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/bin/agent-driven.js +33 -0
- package/dist/bin/agent-driven.js.map +1 -0
- package/dist/bin/commands/doctor.js +66 -1
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +107 -8
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/interactive-confirm.js +110 -0
- package/dist/bin/interactive-confirm.js.map +1 -0
- package/dist/bin/star-prompt.js +46 -15
- package/dist/bin/star-prompt.js.map +1 -1
- package/dist/lib/upload.js +5 -1
- package/dist/lib/upload.js.map +1 -1
- package/dist/server.js +1 -0
- package/dist/server.js.map +1 -1
- package/dist/src/agent/alert-escalation.js +1 -1
- package/dist/src/agent/alert-escalation.js.map +1 -1
- package/dist/src/agent/codex-app-catalog.js +57 -0
- package/dist/src/agent/codex-app-catalog.js.map +1 -0
- package/dist/src/agent/codex-app-client.js +89 -7
- package/dist/src/agent/codex-app-client.js.map +1 -1
- package/dist/src/agent/pi-rpc-verdict.js +22 -0
- package/dist/src/agent/pi-rpc-verdict.js.map +1 -0
- package/dist/src/agent/pi-runtime.js +240 -0
- package/dist/src/agent/pi-runtime.js.map +1 -1
- package/dist/src/agent/runtime-pool.js +445 -0
- package/dist/src/agent/runtime-pool.js.map +1 -0
- package/dist/src/agent/spawn.js +395 -240
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/cli/command-context.js +5 -2
- package/dist/src/cli/command-context.js.map +1 -1
- package/dist/src/cli/commands.js +43 -39
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/handlers-runtime.js +15 -7
- package/dist/src/cli/handlers-runtime.js.map +1 -1
- package/dist/src/cli/handlers-search.js +1 -1
- package/dist/src/cli/handlers-search.js.map +1 -1
- package/dist/src/cli/handlers-skill-invoke.js +1 -1
- package/dist/src/cli/handlers-skill-invoke.js.map +1 -1
- package/dist/src/cli/handlers-workflows.js +1 -1
- package/dist/src/cli/handlers-workflows.js.map +1 -1
- package/dist/src/cli/handlers.js +1 -1
- package/dist/src/cli/handlers.js.map +1 -1
- package/dist/src/cli/opencodex-models.js +40 -7
- package/dist/src/cli/opencodex-models.js.map +1 -1
- package/dist/src/cli/registry-live.js +6 -5
- package/dist/src/cli/registry-live.js.map +1 -1
- package/dist/src/command-contract/catalog.js +4 -0
- package/dist/src/command-contract/catalog.js.map +1 -1
- package/dist/src/core/config.js +69 -4
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/runtime-settings.js +17 -0
- package/dist/src/core/runtime-settings.js.map +1 -1
- package/dist/src/core/settings-merge.js +1 -1
- package/dist/src/core/settings-merge.js.map +1 -1
- package/dist/src/discord/bot.js +29 -14
- package/dist/src/discord/bot.js.map +1 -1
- package/dist/src/discord/commands.js +5 -4
- package/dist/src/discord/commands.js.map +1 -1
- package/dist/src/discord/discord-file.js +8 -2
- package/dist/src/discord/discord-file.js.map +1 -1
- package/dist/src/discord/forwarder.js +17 -19
- package/dist/src/discord/forwarder.js.map +1 -1
- package/dist/src/discord/send-only-client.js +9 -1
- package/dist/src/discord/send-only-client.js.map +1 -1
- package/dist/src/manager/dashboard-service.js +38 -1
- package/dist/src/manager/dashboard-service.js.map +1 -1
- package/dist/src/manager/telegram-hub/hub-bot.js +47 -29
- package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
- package/dist/src/messaging/channel-health.js +32 -0
- package/dist/src/messaging/channel-health.js.map +1 -1
- package/dist/src/messaging/chunk.js +376 -0
- package/dist/src/messaging/chunk.js.map +1 -0
- package/dist/src/messaging/dedupe.js +104 -0
- package/dist/src/messaging/dedupe.js.map +1 -0
- package/dist/src/messaging/fold.js +210 -0
- package/dist/src/messaging/fold.js.map +1 -0
- package/dist/src/messaging/redact.js +577 -0
- package/dist/src/messaging/redact.js.map +1 -0
- package/dist/src/messaging/retry.js +76 -0
- package/dist/src/messaging/retry.js.map +1 -0
- package/dist/src/messaging/runtime.js +6 -3
- package/dist/src/messaging/runtime.js.map +1 -1
- package/dist/src/messaging/send.js +45 -15
- package/dist/src/messaging/send.js.map +1 -1
- package/dist/src/messaging/slack-target.js +50 -0
- package/dist/src/messaging/slack-target.js.map +1 -0
- package/dist/src/messaging/types.js.map +1 -1
- package/dist/src/orchestrator/pipeline.js +4 -4
- package/dist/src/orchestrator/pipeline.js.map +1 -1
- package/dist/src/routes/command.js +11 -7
- package/dist/src/routes/command.js.map +1 -1
- package/dist/src/routes/messaging.js +28 -12
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/settings.js +22 -6
- package/dist/src/routes/settings.js.map +1 -1
- package/dist/src/slack/api.js +116 -0
- package/dist/src/slack/api.js.map +1 -0
- package/dist/src/slack/bot.js +262 -0
- package/dist/src/slack/bot.js.map +1 -0
- package/dist/src/slack/commands.js +98 -0
- package/dist/src/slack/commands.js.map +1 -0
- package/dist/src/slack/events.js +116 -0
- package/dist/src/slack/events.js.map +1 -0
- package/dist/src/slack/format.js +58 -0
- package/dist/src/slack/format.js.map +1 -0
- package/dist/src/slack/forwarder.js +57 -0
- package/dist/src/slack/forwarder.js.map +1 -0
- package/dist/src/slack/register.js +13 -0
- package/dist/src/slack/register.js.map +1 -0
- package/dist/src/slack/send-handler.js +41 -0
- package/dist/src/slack/send-handler.js.map +1 -0
- package/dist/src/slack/send-only-client.js +46 -0
- package/dist/src/slack/send-only-client.js.map +1 -0
- package/dist/src/slack/slack-file.js +72 -0
- package/dist/src/slack/slack-file.js.map +1 -0
- package/dist/src/slack/socket.js +341 -0
- package/dist/src/slack/socket.js.map +1 -0
- package/dist/src/telegram/bot.js +117 -37
- package/dist/src/telegram/bot.js.map +1 -1
- package/dist/src/telegram/elicitation-buttons.js +7 -2
- package/dist/src/telegram/elicitation-buttons.js.map +1 -1
- package/dist/src/telegram/forwarder.js +15 -7
- package/dist/src/telegram/forwarder.js.map +1 -1
- package/dist/src/telegram/rich-message.js +209 -24
- package/dist/src/telegram/rich-message.js.map +1 -1
- package/dist/src/telegram/telegram-file.js +35 -4
- package/dist/src/telegram/telegram-file.js.map +1 -1
- package/dist/src/telegram/voice.js +4 -3
- package/dist/src/telegram/voice.js.map +1 -1
- package/package.json +5 -1
- package/public/assets/providers/slack.svg +1 -0
- package/public/dist/assets/ChannelsDiscord-D9FLnZso.js +1 -0
- package/public/dist/assets/ChannelsSlack-ybvupgdK.js +1 -0
- package/public/dist/assets/ChannelsTelegram-BOCOPrBc.js +1 -0
- package/public/dist/assets/HealthBadge-megTlF4n.js +1 -0
- package/public/dist/assets/TransportStatusChips-CjEySMis.js +1 -0
- package/public/dist/assets/app-BGIAy4he.js +55 -0
- package/public/dist/assets/{bgtask-badge-CdBqBy1L.js → bgtask-badge-DXca8E7c.js} +1 -1
- package/public/dist/assets/bgtask-badge-GRSiNILQ.js +1 -0
- package/public/dist/assets/{employees-C8qufeJl.js → employees-BL4qMxJj.js} +1 -1
- package/public/dist/assets/iframe-renderer-BUSk0Rw9.js +1 -0
- package/public/dist/assets/{iframe-renderer-CZ67aeTO.js → iframe-renderer-DUI0LYBy.js} +2 -2
- package/public/dist/assets/{manager-CLXT60M5.js → manager-B-gpf-3j.js} +3 -3
- package/public/dist/assets/{memory-lWramLVw.js → memory-B_Y0L0KD.js} +1 -1
- package/public/dist/assets/memory-CX6YahTc.js +1 -0
- package/public/dist/assets/message-history-Det7u76S.js +1 -0
- package/public/dist/assets/{message-history-2NUN3UxM.js → message-history-Dl8dJzez.js} +1 -1
- package/public/dist/assets/{provider-icons-stsNmQKx.js → provider-icons-VDfahiUp.js} +3 -2
- package/public/dist/assets/{render-CsF-ojLe.js → render-BI1aqKoc.js} +1 -1
- package/public/dist/assets/{settings-BMLowRCh.js → settings-B0Sj4sUH.js} +1 -1
- package/public/dist/assets/settings-C7NutP6X.js +1 -0
- package/public/dist/assets/settings-core-CyX-px6_.js +100 -0
- package/public/dist/assets/settings-core-DVpFmrhY.js +1 -0
- package/public/dist/assets/{sidebar-gKjYLv49.js → sidebar-DOR9fTgN.js} +3 -3
- package/public/dist/assets/skills-BWhKreQ2.js +1 -0
- package/public/dist/assets/{skills-CKTbomdm.js → skills-wt0fkV9Y.js} +1 -1
- package/public/dist/assets/slash-commands-BZbqTg3v.js +20 -0
- package/public/dist/assets/slash-commands-Di4NDA4w.js +1 -0
- package/public/dist/assets/{trace-drawer-C3G4mmB1.js → trace-drawer-kMYzaVFW.js} +1 -1
- package/public/dist/assets/ui-BghexWrB.js +1 -0
- package/public/dist/assets/{ui-CVVC_flX.js → ui-D4D5HUfo.js} +1 -1
- package/public/dist/index.html +62 -1
- package/public/dist/manager/index.html +1 -1
- package/public/index.html +61 -0
- package/public/js/features/command-info.ts +1 -0
- package/public/js/features/help-content.ts +8 -0
- package/public/js/features/settings-channel.ts +5 -1
- package/public/js/features/settings-core.ts +2 -0
- package/public/js/features/settings-slack.ts +74 -0
- package/public/js/features/settings-types.ts +5 -1
- package/public/js/features/settings.ts +1 -0
- package/public/js/features/transport-status-row.ts +15 -3
- package/public/js/main.ts +19 -0
- package/public/js/provider-icons.ts +4 -1
- package/public/js/ws.ts +1 -1
- package/public/locales/en.json +21 -0
- package/public/locales/ja.json +21 -0
- package/public/locales/ko.json +21 -0
- package/public/locales/zh.json +21 -0
- package/public/manager/src/settings/SettingsShell.tsx +1 -0
- package/public/manager/src/settings/SettingsSidebar.tsx +1 -0
- package/public/manager/src/settings/pages/ChannelsSlack.tsx +323 -0
- package/public/manager/src/settings/pages/components/ActiveChannelToggle.tsx +2 -1
- package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +13 -4
- package/public/manager/src/settings/types.ts +1 -0
- package/scripts/check-redaction-sinks.mjs +276 -0
- package/scripts/pi-rpc-probe.mts +361 -0
- package/scripts/release-gates.mjs +83 -0
- package/scripts/release-preview.sh +8 -1
- package/scripts/release.sh +13 -2
- package/scripts/sync-electron-version.cjs +93 -0
- package/public/dist/assets/ChannelsDiscord-Bt1_rtNC.js +0 -1
- package/public/dist/assets/ChannelsTelegram-BLV0m1oq.js +0 -1
- package/public/dist/assets/TransportStatusChips-Bld0yXi7.js +0 -1
- package/public/dist/assets/app-iSJhsP20.js +0 -55
- package/public/dist/assets/bgtask-badge-CSkSsoB6.js +0 -1
- package/public/dist/assets/iframe-renderer-DXdo0YXN.js +0 -1
- package/public/dist/assets/memory-DeKUlD2M.js +0 -1
- package/public/dist/assets/message-history-CWTQ2woA.js +0 -1
- package/public/dist/assets/settings-CDjs7hTT.js +0 -1
- package/public/dist/assets/settings-core-Cs_oq-b3.js +0 -99
- package/public/dist/assets/settings-core-D-TOh1a6.js +0 -1
- package/public/dist/assets/skills-GuIfNASs.js +0 -1
- package/public/dist/assets/slash-commands-BtZa093n.js +0 -20
- package/public/dist/assets/slash-commands-DGJKfpMT.js +0 -1
- package/public/dist/assets/ui-DJg5Q_NJ.js +0 -1
- /package/public/dist/assets/{DashboardMeta-B7CVqXcU.js → DashboardMeta-DennaaA_.js} +0 -0
- /package/public/dist/assets/{Display-BF-rACK6.js → Display-hGuSn_W5.js} +0 -0
- /package/public/dist/assets/{Employees-D3hnciP0.js → Employees-B5YXk5ma.js} +0 -0
- /package/public/dist/assets/{Heartbeat-D4Pn5O3O.js → Heartbeat-vDGhPfct.js} +0 -0
- /package/public/dist/assets/{Mcp-SfytKWGX.js → Mcp-BHWnJ6SP.js} +0 -0
- /package/public/dist/assets/{Memory-DNxBOQ7N.js → Memory-B_UZ-iCA.js} +0 -0
- /package/public/dist/assets/{ModelProvider-CXwtlG7l.js → ModelProvider-CgUvs6pE.js} +0 -0
- /package/public/dist/assets/{Network-DISUhige.js → Network-AWpSkALD.js} +0 -0
- /package/public/dist/assets/{Profile-B8yBO3Ju.js → Profile-DnIE2ios.js} +0 -0
- /package/public/dist/assets/{Prompts-5lNQ5qOm.js → Prompts-B9aKLall.js} +0 -0
- /package/public/dist/assets/{SpeechKeys-BepaIHBa.js → SpeechKeys-K4Be_o7z.js} +0 -0
- /package/public/dist/assets/{TelegramHub--DNjRAwW.js → TelegramHub-CFwSkzHP.js} +0 -0
- /package/public/dist/assets/{TerminalPanel-DQovYfwz.js → TerminalPanel-CdlFZHAK.js} +0 -0
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
// ─── Channel Credential Redaction ────────────────────
|
|
2
|
+
// One masker for every channel. Error strings cross channel boundaries — the
|
|
3
|
+
// unified send path collects results from all three — so per-channel maskers
|
|
4
|
+
// leak at the seams.
|
|
5
|
+
//
|
|
6
|
+
// Three sinks matter, in order of severity:
|
|
7
|
+
// 1. the chat room itself (Telegram/Discord reply text)
|
|
8
|
+
// 2. HTTP response bodies (`{ ok: false, error }` reaches /api callers)
|
|
9
|
+
// 3. log files
|
|
10
|
+
//
|
|
11
|
+
// Telegram is the sharp edge: its Bot API puts the token in the URL PATH
|
|
12
|
+
// (`https://api.telegram.org/bot<TOKEN>/method`), so the path is itself a
|
|
13
|
+
// credential, exactly like Slack's `upload_url`.
|
|
14
|
+
/** A language-tag-free guard: keep host, drop everything that identifies it. */
|
|
15
|
+
import { canonicalize } from './fold.js';
|
|
16
|
+
const REDACTED = '...redacted';
|
|
17
|
+
/**
|
|
18
|
+
* Characters copied while assembling redacted output, since the last reset.
|
|
19
|
+
*
|
|
20
|
+
* Exists for the complexity regression tests. Those used to assert a wall-clock
|
|
21
|
+
* budget, which measures the runner rather than the algorithm: the same fixture
|
|
22
|
+
* took 155 ms on a developer laptop and 777 ms on a CI runner, and running the
|
|
23
|
+
* suite in parallel moved it again. A counter of the work actually performed is
|
|
24
|
+
* the same number on every machine, so the test can state the real invariant —
|
|
25
|
+
* copy work stays proportional to input length — instead of a machine-specific
|
|
26
|
+
* millisecond bound.
|
|
27
|
+
*
|
|
28
|
+
* Incrementing one integer per emitted piece costs nothing measurable and is
|
|
29
|
+
* never read in production.
|
|
30
|
+
*/
|
|
31
|
+
let charsCopied = 0;
|
|
32
|
+
/** Read the copy-work counter; test-only introspection. */
|
|
33
|
+
export function redactionCopyWork() {
|
|
34
|
+
return charsCopied;
|
|
35
|
+
}
|
|
36
|
+
/** Reset the copy-work counter; test-only introspection. */
|
|
37
|
+
export function resetRedactionCopyWork() {
|
|
38
|
+
charsCopied = 0;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mask a Telegram bot token embedded in a URL path, whatever the host.
|
|
42
|
+
*
|
|
43
|
+
* Host and credential are separate concerns. A lookalike host such as
|
|
44
|
+
* `evil.telegram.org.attacker.dev` is deliberately left readable — hiding it
|
|
45
|
+
* would conceal a suspicious destination — but a real token sitting in its
|
|
46
|
+
* path must still go. Anchoring on the literal `/bot` rather than a word
|
|
47
|
+
* boundary matters: in `.../bot123456:SECRET` the character before the digits
|
|
48
|
+
* is `t`, so `\b` never matches.
|
|
49
|
+
*/
|
|
50
|
+
function stripBotTokenFromPath(raw) {
|
|
51
|
+
// The separators may themselves be percent-encoded: a URL that travelled
|
|
52
|
+
// through a query parameter arrives as https%3A%2F%2F…%2Fbot<id>%3A<secret>.
|
|
53
|
+
return raw.replace(/(\/|%2F)(?:file(?:\/|%2F))?bots?(\d{6,12})(?::|%3A)[A-Za-z0-9_-]+/gi, (_m, slash, id) => `${slash}bot${id}:${REDACTED}`);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A `bot` prefix is proof of provenance: nothing but a Telegram credential is
|
|
57
|
+
* spelled `bot<digits>:<run>`. Because provenance is established, the secret's
|
|
58
|
+
* shape is not consulted — requiring a character mix would leak an
|
|
59
|
+
* all-uppercase token in full.
|
|
60
|
+
*
|
|
61
|
+
* The prefix has to stand alone. Excluding only a preceding letter was not
|
|
62
|
+
* enough — `my_bot123…` and `v2bot123…` are identifiers, not credentials — so
|
|
63
|
+
* the guard covers every identifier character.
|
|
64
|
+
*/
|
|
65
|
+
const PREFIXED_TOKEN = new RegExp(`(?<![A-Za-z0-9_])bot(\\d{6,12})(?::|\uFF1A)[A-Za-z0-9_-]{20,}`, 'gi');
|
|
66
|
+
/**
|
|
67
|
+
* A Discord token's first segment is the bot's user id in base64url, so it
|
|
68
|
+
* decodes to a run of digits. An artifact id or checksum of the same dotted
|
|
69
|
+
* shape does not, which is what keeps ordinary diagnostics readable.
|
|
70
|
+
*/
|
|
71
|
+
function decodesToSnowflake(segment) {
|
|
72
|
+
// The regex has no way to know where the token starts, so ordinary text
|
|
73
|
+
// running into it ("logabcdefMTIz…") is captured as part of the first
|
|
74
|
+
// segment. Try the trailing windows: a real header is 24 characters for an
|
|
75
|
+
// 18-digit id, but ids vary in length, so scan a small range from the end.
|
|
76
|
+
//
|
|
77
|
+
// Padding needs no special handling here — Node's base64 decoder tolerates
|
|
78
|
+
// it, so a `…Njc4==` segment still yields the id. What DOES matter is that
|
|
79
|
+
// the pattern above accepts the `=` characters in the first place; without
|
|
80
|
+
// that the whole match never forms.
|
|
81
|
+
for (let width = 20; width <= Math.min(32, segment.length); width += 1) {
|
|
82
|
+
const candidate = segment.slice(segment.length - width);
|
|
83
|
+
try {
|
|
84
|
+
const decoded = Buffer.from(candidate.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
|
|
85
|
+
if (/^\d{15,}$/.test(decoded))
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Not valid base64 at this width; try the next.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Bot tokens anywhere in a string.
|
|
96
|
+
*
|
|
97
|
+
* Detection is anchored on the SECRET, not on what precedes it. Every earlier
|
|
98
|
+
* attempt anchored on the left — a word boundary, then a delimiter set, then a
|
|
99
|
+
* lookbehind — and each one was defeated by a spelling nobody had listed:
|
|
100
|
+
* `Bot`, `BOT`, a bare `/`, an ordinary letter, `%3A`. What actually identifies
|
|
101
|
+
* a Telegram credential is its shape: a bot id, a separator, and a long
|
|
102
|
+
* high-entropy run. Match that, and the left context stops mattering.
|
|
103
|
+
*
|
|
104
|
+
* Separators cover the literal colon, its percent-encoding at any nesting
|
|
105
|
+
* depth (`%3A`, `%253A`, …), the fullwidth colon that NFKC folds to `:`, and
|
|
106
|
+
* an intervening line break — a token wrapped across lines in a log is still a
|
|
107
|
+
* token once someone joins it back up.
|
|
108
|
+
*/
|
|
109
|
+
// `%3A` encoded once is `%253A`, twice is `%25253A`: each round replaces the
|
|
110
|
+
// leading `%` with `%25`, so the nesting shows up as repeated `25` AFTER the
|
|
111
|
+
// first percent, not as repeated `%25` before it.
|
|
112
|
+
const TOKEN_SEPARATOR = '(?::|\uFF1A|%(?:25)*3A)';
|
|
113
|
+
// Zero-width formatting characters can sit between the id and the separator.
|
|
114
|
+
// They are invisible in a log yet vanish on paste, so a token split by one is
|
|
115
|
+
// still a working token.
|
|
116
|
+
const INVISIBLE = '[\\s\\u200B-\\u200D\\uFEFF]*';
|
|
117
|
+
// The id runs to the END of a digit run: with a longer run, take its last 6-12
|
|
118
|
+
// digits rather than refusing to match. A lookbehind that demands a non-digit
|
|
119
|
+
// lets `9` + a 12-digit id slip past as a 13-digit run.
|
|
120
|
+
const TOKEN_ANYWHERE = new RegExp(`(\\d{6,12})${INVISIBLE}${TOKEN_SEPARATOR}${INVISIBLE}([A-Za-z0-9_-]{30,})`, 'gi');
|
|
121
|
+
/**
|
|
122
|
+
* Provenance, not shape, decides how aggressive masking may be.
|
|
123
|
+
*
|
|
124
|
+
* Telegram documents its token only as opaque, so a rule like "the secret
|
|
125
|
+
* mixes upper case, lower case and digits" is an unwritten assumption: an
|
|
126
|
+
* all-uppercase token would ship in full. Every path where the surrounding
|
|
127
|
+
* text PROVES a credential — a Bot API URL, a `bot` prefix, an Authorization
|
|
128
|
+
* header — therefore masks on structure alone, without consulting the shape.
|
|
129
|
+
*
|
|
130
|
+
* The character mix survives in exactly one place: a bare `<digits>:<run>`
|
|
131
|
+
* pair in arbitrary prose, where there is no provenance and a build id or a
|
|
132
|
+
* checksum would otherwise be blanked. A false negative there costs nothing,
|
|
133
|
+
* because the same token inside any real Telegram context is caught above.
|
|
134
|
+
*/
|
|
135
|
+
function mixesCharacterClasses(candidate) {
|
|
136
|
+
return /[A-Z]/.test(candidate) && /[a-z]/.test(candidate) && /\d/.test(candidate);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Find secrets in the CANONICAL form of the text, then remove those literal
|
|
140
|
+
* runs from the original.
|
|
141
|
+
*
|
|
142
|
+
* This is what stops the spelling arms race. A pattern matched against the raw
|
|
143
|
+
* string has to anticipate every encoding and every invisible character; a
|
|
144
|
+
* pattern matched against the folded string sees one shape. The secret run
|
|
145
|
+
* survives folding intact (its characters are base64url, which percent-decoding
|
|
146
|
+
* and control-stripping leave alone), so it can be located in the original by
|
|
147
|
+
* substring and blanked there.
|
|
148
|
+
*/
|
|
149
|
+
function stripSecretsFoundInCanonicalForm(input) {
|
|
150
|
+
// Scan the folded form ALWAYS, not only when folding changed something. A
|
|
151
|
+
// string that is already canonical still needs this pass: the raw matchers
|
|
152
|
+
// downstream stop at the first prefixed token, so a secret repeated later
|
|
153
|
+
// in the same error string survived.
|
|
154
|
+
const folded = canonicalize(input);
|
|
155
|
+
// The fold gave up with escapes still unresolved. Something is nested
|
|
156
|
+
// deeply enough to outrun the budget, which nothing legitimate does, so
|
|
157
|
+
// blank whatever could be hiding a credential rather than pass the text
|
|
158
|
+
// through unread.
|
|
159
|
+
//
|
|
160
|
+
// Blanking only the runs that CONTAIN an escape is not enough: the fold
|
|
161
|
+
// also removes control characters, so an encoded prefix and its secret can
|
|
162
|
+
// sit on either side of a newline. The bot id is public, so leaving the
|
|
163
|
+
// secret half behind still hands over the credential. Any long run that
|
|
164
|
+
// either carries an escape or could BE a secret goes.
|
|
165
|
+
if (folded.exhausted) {
|
|
166
|
+
// Scope it: the escaped run itself, and any credential-shaped run near
|
|
167
|
+
// enough to be its other half. Blanking every long alphanumeric run
|
|
168
|
+
// erased ordinary identifiers — `release-build-20260802-darwin-arm64`
|
|
169
|
+
// among them — which hands an attacker a way to strip diagnostics by
|
|
170
|
+
// burying one deeply encoded string in the same message.
|
|
171
|
+
return input.replace(/\S{20,}/g, (run, offset) => {
|
|
172
|
+
if (/%|\\u|&#/.test(run))
|
|
173
|
+
return REDACTED;
|
|
174
|
+
// A Telegram secret is an unbroken base64url run. An ordinary long
|
|
175
|
+
// identifier is punctuated — `release-build-20260802-darwin-arm64`
|
|
176
|
+
// has hyphens — so requiring no separator keeps diagnostics while
|
|
177
|
+
// still covering the shape a credential takes.
|
|
178
|
+
if (!/^[A-Za-z0-9]{20,}$/.test(run))
|
|
179
|
+
return run;
|
|
180
|
+
return hasEscapeWithin(input, offset, run.length) ? REDACTED : run;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
// Collect the SOURCE ranges each match came from. Deleting the matched
|
|
184
|
+
// literal instead fails in both directions: an escape spliced inside the
|
|
185
|
+
// secret means the literal is not in the source at all, and a run that
|
|
186
|
+
// legitimately appears elsewhere gets destroyed with it.
|
|
187
|
+
const ranges = [];
|
|
188
|
+
// The folded coordinates of each hit, kept alongside. Recovering them
|
|
189
|
+
// afterwards meant scanning the whole map per range, which is the other
|
|
190
|
+
// half of the quadratic cost.
|
|
191
|
+
const foldedRanges = [];
|
|
192
|
+
const record = (start, stop) => {
|
|
193
|
+
if (start >= stop)
|
|
194
|
+
return;
|
|
195
|
+
const from = folded.origin[start];
|
|
196
|
+
const to = folded.end[stop - 1];
|
|
197
|
+
if (from === undefined || to === undefined)
|
|
198
|
+
return;
|
|
199
|
+
ranges.push({ from, to });
|
|
200
|
+
foldedRanges.push({ foldedFrom: start, foldedTo: stop });
|
|
201
|
+
};
|
|
202
|
+
for (const match of folded.text.matchAll(PREFIXED_TOKEN)) {
|
|
203
|
+
const at = match.index ?? 0;
|
|
204
|
+
const separator = match[0].search(/[:\uFF1A]/);
|
|
205
|
+
if (separator < 0)
|
|
206
|
+
continue;
|
|
207
|
+
record(at + separator + 1, at + match[0].length);
|
|
208
|
+
}
|
|
209
|
+
for (const match of folded.text.matchAll(CANONICAL_URL_TOKEN)) {
|
|
210
|
+
const at = match.index ?? 0;
|
|
211
|
+
const secret = match[2];
|
|
212
|
+
if (!secret)
|
|
213
|
+
continue;
|
|
214
|
+
record(at + match[0].length - secret.length, at + match[0].length);
|
|
215
|
+
}
|
|
216
|
+
if (ranges.length === 0)
|
|
217
|
+
return input;
|
|
218
|
+
// A credential proven once in a message is a credential everywhere in it.
|
|
219
|
+
// The repeat search runs over the FOLDED text, so a second mention wrapped
|
|
220
|
+
// in a different encoding is caught too, and each hit is carried back by
|
|
221
|
+
// the same offset map.
|
|
222
|
+
//
|
|
223
|
+
// Unlike the first pass this ignores whether the run is glued to
|
|
224
|
+
// surrounding characters. The earlier "standalone only" rule existed to
|
|
225
|
+
// protect unrelated values that happened to share the run, but once the
|
|
226
|
+
// very same run has been proven a credential in this message, treating a
|
|
227
|
+
// later copy as coincidence is the wrong way to be wrong.
|
|
228
|
+
//
|
|
229
|
+
// Bounded work: scanning the whole text once per distinct secret is
|
|
230
|
+
// quadratic, and an attacker controls how many distinct token-shaped runs
|
|
231
|
+
// an error string contains. Both the number of secrets and the total
|
|
232
|
+
// search effort are capped; the first pass has already masked each
|
|
233
|
+
// credential where it was proven, so the cap only trims the extra sweep
|
|
234
|
+
// for repeats.
|
|
235
|
+
// Every proven secret, not the first N. Capping this was the same mistake
|
|
236
|
+
// as capping decode rounds: the 33rd credential's repeat went out in the
|
|
237
|
+
// clear. Cost is controlled by making the sweep a single pass over the
|
|
238
|
+
// text rather than one pass per secret.
|
|
239
|
+
const known = new Set();
|
|
240
|
+
let shortest = Number.POSITIVE_INFINITY;
|
|
241
|
+
for (const { foldedFrom, foldedTo } of foldedRanges) {
|
|
242
|
+
const run = folded.text.slice(foldedFrom, foldedTo);
|
|
243
|
+
if (run.length < 20)
|
|
244
|
+
continue;
|
|
245
|
+
known.add(run);
|
|
246
|
+
shortest = Math.min(shortest, run.length);
|
|
247
|
+
}
|
|
248
|
+
if (known.size > 0) {
|
|
249
|
+
// Group by prefix AND length, so a position tests at most one string
|
|
250
|
+
// per length rather than every secret sharing that prefix.
|
|
251
|
+
//
|
|
252
|
+
// Bucketing on the prefix alone still lets an attacker pile hundreds of
|
|
253
|
+
// secrets into one bucket and follow it with near-miss tails: 400 of
|
|
254
|
+
// them took sixteen seconds. Any structure whose per-position work
|
|
255
|
+
// grows with attacker-chosen input is not a security boundary.
|
|
256
|
+
const byPrefixAndLength = new Map();
|
|
257
|
+
const lengthsByPrefix = new Map();
|
|
258
|
+
for (const secret of known) {
|
|
259
|
+
const prefix = secret.slice(0, PROBE_WIDTH);
|
|
260
|
+
byPrefixAndLength.set(`${prefix}\u0000${secret.length}`, secret);
|
|
261
|
+
const lengths = lengthsByPrefix.get(prefix);
|
|
262
|
+
if (lengths) {
|
|
263
|
+
if (!lengths.includes(secret.length))
|
|
264
|
+
lengths.push(secret.length);
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
lengthsByPrefix.set(prefix, [secret.length]);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (let at = 0; at + shortest <= folded.text.length; at += 1) {
|
|
271
|
+
const prefix = folded.text.slice(at, at + PROBE_WIDTH);
|
|
272
|
+
const lengths = lengthsByPrefix.get(prefix);
|
|
273
|
+
if (!lengths)
|
|
274
|
+
continue;
|
|
275
|
+
for (const length of lengths) {
|
|
276
|
+
const candidate = byPrefixAndLength.get(`${prefix}\u0000${length}`);
|
|
277
|
+
if (candidate === undefined)
|
|
278
|
+
continue;
|
|
279
|
+
if (folded.text.slice(at, at + length) !== candidate)
|
|
280
|
+
continue;
|
|
281
|
+
record(at, at + length);
|
|
282
|
+
at += length - 1;
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Build the output once, front to back.
|
|
288
|
+
//
|
|
289
|
+
// Splicing the string per range re-copied the whole text for every match,
|
|
290
|
+
// which is quadratic in the number of credentials — and an attacker picks
|
|
291
|
+
// that number. Measured on this fixture family: 4k credentials took 167 ms
|
|
292
|
+
// and 8k took 555 ms (3.3x for 2x input) while the scan itself stayed
|
|
293
|
+
// linear (43 ms → 79 ms). Collecting the pieces and joining once keeps the
|
|
294
|
+
// total copy work proportional to the text length.
|
|
295
|
+
//
|
|
296
|
+
// Overlapping ranges are MERGED rather than one being dropped. The old
|
|
297
|
+
// back-to-front loop kept the rightmost of two overlapping matches and
|
|
298
|
+
// discarded the other, which left the non-shared head of the discarded
|
|
299
|
+
// range in the clear — for a redactor, masking the union is the only
|
|
300
|
+
// direction that cannot leak. The union is bounded by the matches
|
|
301
|
+
// themselves, so ordinary text around them is untouched.
|
|
302
|
+
ranges.sort((a, b) => a.from - b.from || a.to - b.to);
|
|
303
|
+
const pieces = [];
|
|
304
|
+
let cursor = 0;
|
|
305
|
+
let openFrom = -1;
|
|
306
|
+
let openTo = -1;
|
|
307
|
+
const flush = () => {
|
|
308
|
+
if (openFrom < 0)
|
|
309
|
+
return;
|
|
310
|
+
pieces.push(input.slice(cursor, openFrom), REDACTED);
|
|
311
|
+
charsCopied += (openFrom - cursor) + REDACTED.length;
|
|
312
|
+
cursor = openTo;
|
|
313
|
+
openFrom = -1;
|
|
314
|
+
};
|
|
315
|
+
for (const { from, to } of ranges) {
|
|
316
|
+
if (openFrom >= 0 && from <= openTo) {
|
|
317
|
+
if (to > openTo)
|
|
318
|
+
openTo = to; // extend the merged run
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
flush();
|
|
322
|
+
openFrom = from;
|
|
323
|
+
openTo = to;
|
|
324
|
+
}
|
|
325
|
+
flush();
|
|
326
|
+
pieces.push(input.slice(cursor));
|
|
327
|
+
charsCopied += input.length - cursor;
|
|
328
|
+
return pieces.join('');
|
|
329
|
+
}
|
|
330
|
+
/** `/bot<id>:<secret>` inside a path, after folding. */
|
|
331
|
+
const CANONICAL_URL_TOKEN = /\/(?:file\/)?bots?(\d{6,12}):([A-Za-z0-9_-]{20,})/gi;
|
|
332
|
+
/**
|
|
333
|
+
* Mask query VALUES while keeping the keys, in one linear pass.
|
|
334
|
+
*
|
|
335
|
+
* A regex of the shape /([^&=]+)=([^&]*)/g backtracks quadratically when the
|
|
336
|
+
* query holds a long run with no `=` in it — a 100 KB URL took four seconds,
|
|
337
|
+
* which turns any logged string into a denial of service. Splitting on `&` and
|
|
338
|
+
* taking the first `=` per pair cannot backtrack at all.
|
|
339
|
+
*/
|
|
340
|
+
function maskQueryValues(query) {
|
|
341
|
+
return query
|
|
342
|
+
.split('&')
|
|
343
|
+
.map((pair) => {
|
|
344
|
+
const eq = pair.indexOf('=');
|
|
345
|
+
return eq < 0 ? pair : `${pair.slice(0, eq)}=${REDACTED}`;
|
|
346
|
+
})
|
|
347
|
+
.join('&');
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Redact channel credentials from any string before it reaches a log sink, an
|
|
351
|
+
* API response, or a chat message.
|
|
352
|
+
*/
|
|
353
|
+
export function redactChannelSecrets(input) {
|
|
354
|
+
return stripSecretsFoundInCanonicalForm(input)
|
|
355
|
+
// Percent-encoded URLs never reach the URL parser below, so peel their
|
|
356
|
+
// credentials off first.
|
|
357
|
+
.replace(/https?%3A%2F%2F[^\s]+/gi, stripBotTokenFromPath)
|
|
358
|
+
// Slack bearer tokens: xoxb-, xoxp-, xoxa-, xoxs-, xapp-.
|
|
359
|
+
.replace(/x(?:ox[bpas]|app)-[A-Za-z0-9-]+/g, (m) => `${m.slice(0, 9)}${REDACTED}`)
|
|
360
|
+
// Discord bot tokens are three base64url segments separated by dots.
|
|
361
|
+
// No word boundary and no fixed first-segment length: `\b` fails when
|
|
362
|
+
// an ordinary letter abuts the token, and pinning the first segment to
|
|
363
|
+
// 23-28 characters misses a token that follows other text.
|
|
364
|
+
//
|
|
365
|
+
// The first segment is base64url of a snowflake id, so it decodes to
|
|
366
|
+
// digits — that is what separates a real token from an artifact id or
|
|
367
|
+
// a checksum of the same shape. Structure alone is not enough:
|
|
368
|
+
// `aaaa….bbbbb.cccc…` matches any three-run dotted string.
|
|
369
|
+
//
|
|
370
|
+
// KNOWN OVER-MASKING. A non-token whose first segment happens to be a
|
|
371
|
+
// base64url snowflake is redacted too. Nothing in the remaining
|
|
372
|
+
// segments distinguishes it: a real token's second segment is six
|
|
373
|
+
// base64url characters and its third is an opaque HMAC, both of which
|
|
374
|
+
// an artifact id can match exactly. Since a miss here publishes a bot
|
|
375
|
+
// token while a false hit costs one unreadable diagnostic, the tie goes
|
|
376
|
+
// to masking. The snowflake test already spares ordinary versions,
|
|
377
|
+
// checksums and build ids, which is where the real risk of over-masking
|
|
378
|
+
// was.
|
|
379
|
+
//
|
|
380
|
+
// Anchoring the whole match keeps it linear. Open-ended runs made the
|
|
381
|
+
// engine try every split of a long dotless string; 50 KB took two
|
|
382
|
+
// seconds, which the timing test caught.
|
|
383
|
+
.replace(
|
|
384
|
+
// The first segment may carry base64 padding when a log renders it
|
|
385
|
+
// that way; rejecting `=` would leave the token in the clear.
|
|
386
|
+
// `=` is padding, so it is meaningful only AFTER a segment. The
|
|
387
|
+
// leading guard must still allow it: `token=<token>` puts one
|
|
388
|
+
// immediately in front, and excluding it there dropped the match.
|
|
389
|
+
/(?<![A-Za-z0-9_.-])([A-Za-z0-9_-]{20,}={0,2})\.([A-Za-z0-9_-]{5,}={0,2})\.([A-Za-z0-9_-]{25,}={0,2})(?![A-Za-z0-9_.-])/g, (whole, header) => (decodesToSnowflake(header) ? REDACTED : whole))
|
|
390
|
+
// Prefixed tokens: provenance is certain, so shape does not matter.
|
|
391
|
+
.replace(PREFIXED_TOKEN, (_m, id) => `bot${id}:${REDACTED}`)
|
|
392
|
+
// Authorization header values, whichever scheme.
|
|
393
|
+
.replace(/\b(Bot|Bearer)\s+\S+/gi, (_m, scheme) => `${scheme} ${REDACTED}`)
|
|
394
|
+
// URLs. ONE pass: running two URL regexes would let the second parse a
|
|
395
|
+
// string the first already rewrote.
|
|
396
|
+
//
|
|
397
|
+
// Host matching goes through the URL parser rather than a regex on the
|
|
398
|
+
// raw text, so canonical-equivalent spellings cannot slip past:
|
|
399
|
+
// uppercase hosts, an explicit :443, a trailing dot, and userinfo
|
|
400
|
+
// (user:pass@) all normalize before the suffix check.
|
|
401
|
+
.replace(/https?:\/\/[^\s]+/gi, (raw) => {
|
|
402
|
+
let parsed;
|
|
403
|
+
try {
|
|
404
|
+
parsed = new URL(raw);
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return stripBotTokenFromPath(raw);
|
|
408
|
+
}
|
|
409
|
+
const host = parsed.hostname.toLowerCase().replace(/\.$/, '');
|
|
410
|
+
const isSlack = host === 'slack.com' || host.endsWith('.slack.com');
|
|
411
|
+
const isTelegram = host === 'telegram.org' || host.endsWith('.telegram.org');
|
|
412
|
+
const isDiscord = host === 'discord.com' || host.endsWith('.discord.com')
|
|
413
|
+
|| host === 'discordapp.com' || host.endsWith('.discordapp.com');
|
|
414
|
+
// For our own hosts the whole path is capability-bearing: Slack's
|
|
415
|
+
// upload_url is an opaque path, Telegram's carries the bot token,
|
|
416
|
+
// and Discord webhook URLs carry the webhook secret.
|
|
417
|
+
if (isSlack || isTelegram || isDiscord)
|
|
418
|
+
return `https://${parsed.host}/${REDACTED}`;
|
|
419
|
+
// Untrusted host: keep the URL readable, but never leave a
|
|
420
|
+
// credential in it.
|
|
421
|
+
//
|
|
422
|
+
// Being a URL is NOT provenance on its own — that reading blanked
|
|
423
|
+
// ordinary artifact paths like /build/<id>:<hash>/artifact. Only a
|
|
424
|
+
// `/bot<id>:<secret>` segment proves a Telegram credential here;
|
|
425
|
+
// anything else falls back to the shape heuristic, which spares a
|
|
426
|
+
// build id while still catching a real token.
|
|
427
|
+
const withoutToken = stripBotTokenFromPath(raw)
|
|
428
|
+
.replace(TOKEN_ANYWHERE, (whole, id, secret) => mixesCharacterClasses(secret) ? `${id}:${REDACTED}` : whole);
|
|
429
|
+
// Query VALUES go, keys stay: a diagnostic URL stays diagnosable
|
|
430
|
+
// while a signature does not survive.
|
|
431
|
+
const queryAt = withoutToken.indexOf('?');
|
|
432
|
+
if (queryAt < 0)
|
|
433
|
+
return withoutToken;
|
|
434
|
+
return withoutToken.slice(0, queryAt + 1) + maskQueryValues(withoutToken.slice(queryAt + 1));
|
|
435
|
+
})
|
|
436
|
+
// Bot tokens outside any URL, including percent-encoded separators and
|
|
437
|
+
// a Bot/BOT/bot prefix in any casing.
|
|
438
|
+
// Bare `<digits>:<run>` in arbitrary prose: no provenance, so the
|
|
439
|
+
// heuristic applies and an ordinary diagnostic stays readable.
|
|
440
|
+
.replace(TOKEN_ANYWHERE, (whole, id, secret) => mixesCharacterClasses(secret) ? `${id}:${REDACTED}` : whole);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Stringify anything that was thrown, without ever throwing.
|
|
444
|
+
*
|
|
445
|
+
* `JSON.stringify` rejects BigInt ("Do not know how to serialize a BigInt")
|
|
446
|
+
* and circular references; `String()` itself throws on a Symbol or an object
|
|
447
|
+
* whose `toString` throws. An error sink that can throw defeats the error
|
|
448
|
+
* handling it was added to protect.
|
|
449
|
+
*/
|
|
450
|
+
function stringifyThrown(err, withStack) {
|
|
451
|
+
if (err instanceof Error) {
|
|
452
|
+
return withStack ? (err.stack ?? err.message) : err.message;
|
|
453
|
+
}
|
|
454
|
+
if (typeof err === 'string')
|
|
455
|
+
return err;
|
|
456
|
+
try {
|
|
457
|
+
return JSON.stringify(err) ?? String(err);
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
try {
|
|
461
|
+
return String(err);
|
|
462
|
+
}
|
|
463
|
+
catch {
|
|
464
|
+
return '[unrepresentable error]';
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* For text a USER will see: message only, credentials masked.
|
|
470
|
+
*
|
|
471
|
+
* Never includes the stack. A stack frame carries absolute source paths, and
|
|
472
|
+
* publishing the operator's directory layout to a chat room is its own leak.
|
|
473
|
+
*/
|
|
474
|
+
export function userErrorText(err) {
|
|
475
|
+
return redactChannelSecrets(stringifyThrown(err, false));
|
|
476
|
+
}
|
|
477
|
+
/** For server logs: full stack, credentials masked. */
|
|
478
|
+
export function logErrorText(err) {
|
|
479
|
+
return redactChannelSecrets(stringifyThrown(err, true));
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Last mile: mask the BODY of an outbound message, whatever produced it.
|
|
483
|
+
*
|
|
484
|
+
* Error paths were hardened first, but a credential does not only travel in an
|
|
485
|
+
* error. An agent that reads a config file, a command that echoes a URL, a
|
|
486
|
+
* forwarder relaying tool output — each puts ordinary text on the wire, and
|
|
487
|
+
* that text goes to a chat room where anyone in the channel can read it.
|
|
488
|
+
*
|
|
489
|
+
* Every transport calls this immediately before handing text to the vendor
|
|
490
|
+
* SDK, so there is one place to audit rather than one per call site.
|
|
491
|
+
*/
|
|
492
|
+
export function redactOutboundText(text) {
|
|
493
|
+
return redactChannelSecrets(text);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Mask every string inside a structured outbound payload.
|
|
497
|
+
*
|
|
498
|
+
* Inline keyboards carry user-visible text in button labels and URLs, and
|
|
499
|
+
* those reach the room exactly like a message body. Walking the object beats
|
|
500
|
+
* enumerating fields, because the vendor's shape changes and a new field would
|
|
501
|
+
* silently go unmasked.
|
|
502
|
+
*
|
|
503
|
+
* Depth- and breadth-bounded: the input is attacker-influenced, and a
|
|
504
|
+
* recursive walk over it is a stack-overflow waiting to happen. Cycles are
|
|
505
|
+
* tracked so a self-referencing object cannot spin.
|
|
506
|
+
*/
|
|
507
|
+
export function redactOutboundPayload(value, depth = 0, seen = new WeakMap()) {
|
|
508
|
+
if (typeof value === 'string')
|
|
509
|
+
return redactChannelSecrets(value);
|
|
510
|
+
if (value === null || typeof value !== 'object')
|
|
511
|
+
return value;
|
|
512
|
+
// A cache, not a visited-set. Returning the ORIGINAL for an object seen
|
|
513
|
+
// before means the second alias of a shared button object ships its secret
|
|
514
|
+
// untouched — the first reference was sanitized and the second was not.
|
|
515
|
+
const cached = seen.get(value);
|
|
516
|
+
if (cached !== undefined)
|
|
517
|
+
return cached;
|
|
518
|
+
// Past the depth limit, drop the subtree instead of returning it. Handing
|
|
519
|
+
// back the raw object is a leak; anything this deep in an outbound payload
|
|
520
|
+
// is not something a channel renders.
|
|
521
|
+
if (depth >= MAX_PAYLOAD_DEPTH)
|
|
522
|
+
return (Array.isArray(value) ? [] : {});
|
|
523
|
+
// Register the container BEFORE walking it, so a cycle resolves to the
|
|
524
|
+
// same sanitized object rather than recursing forever.
|
|
525
|
+
const out = Array.isArray(value) ? [] : {};
|
|
526
|
+
seen.set(value, out);
|
|
527
|
+
if (Array.isArray(value)) {
|
|
528
|
+
for (const item of value) {
|
|
529
|
+
out.push(redactOutboundPayload(item, depth + 1, seen));
|
|
530
|
+
}
|
|
531
|
+
return out;
|
|
532
|
+
}
|
|
533
|
+
for (const [key, item] of Object.entries(value)) {
|
|
534
|
+
out[key] = redactOutboundPayload(item, depth + 1, seen);
|
|
535
|
+
}
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
/** Deeper than any keyboard or media payload a channel actually renders. */
|
|
539
|
+
const MAX_PAYLOAD_DEPTH = 12;
|
|
540
|
+
/**
|
|
541
|
+
* True when an escaped run sits close enough to be the other half of a
|
|
542
|
+
* credential the fold could not finish resolving.
|
|
543
|
+
*
|
|
544
|
+
* Distance is measured to the escaped RUN, not to an escape character. A long
|
|
545
|
+
* `%2525…` chain is mostly digits, so a window around the neighbour can sit
|
|
546
|
+
* entirely inside it and still contain no `%` — which read as "no escape
|
|
547
|
+
* nearby" and let the secret through.
|
|
548
|
+
*/
|
|
549
|
+
function hasEscapeWithin(input, offset, length) {
|
|
550
|
+
for (const match of input.matchAll(/\S{20,}/g)) {
|
|
551
|
+
if (!/%|\\u|&#/.test(match[0]))
|
|
552
|
+
continue;
|
|
553
|
+
const start = match.index ?? 0;
|
|
554
|
+
const stop = start + match[0].length;
|
|
555
|
+
const gapBefore = offset - stop;
|
|
556
|
+
const gapAfter = start - (offset + length);
|
|
557
|
+
if (gapBefore >= 0 && gapBefore <= EXHAUSTED_NEIGHBOURHOOD)
|
|
558
|
+
return true;
|
|
559
|
+
if (gapAfter >= 0 && gapAfter <= EXHAUSTED_NEIGHBOURHOOD)
|
|
560
|
+
return true;
|
|
561
|
+
}
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* How far to look for the escaped half. The fold strips control characters and
|
|
566
|
+
* whitespace, so the two halves can be a line break and a little punctuation
|
|
567
|
+
* apart — but not paragraphs apart, which is what keeps this from swallowing
|
|
568
|
+
* an unrelated identifier elsewhere in the message.
|
|
569
|
+
*/
|
|
570
|
+
const EXHAUSTED_NEIGHBOURHOOD = 64;
|
|
571
|
+
/**
|
|
572
|
+
* Width of the prefix used to index known secrets. Every tracked run is at
|
|
573
|
+
* least 20 characters, so this always exists, and collisions across distinct
|
|
574
|
+
* credentials are rare enough that each position tests roughly one candidate.
|
|
575
|
+
*/
|
|
576
|
+
const PROBE_WIDTH = 16;
|
|
577
|
+
//# sourceMappingURL=redact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact.js","sourceRoot":"","sources":["../../../src/messaging/redact.ts"],"names":[],"mappings":"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"}
|