switchroom 0.19.35 → 0.19.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/cli/skill-validate-pretool.mjs +15 -2
  2. package/dist/cli/switchroom.js +672 -441
  3. package/dist/host-control/main.js +149 -3
  4. package/package.json +4 -2
  5. package/profiles/_base/start.sh.hbs +37 -12
  6. package/telegram-plugin/dist/gateway/gateway.js +311 -93
  7. package/telegram-plugin/format.ts +70 -15
  8. package/telegram-plugin/gateway/gateway.ts +22 -29
  9. package/telegram-plugin/gateway/ipc-server.ts +18 -15
  10. package/telegram-plugin/gateway/model-command.ts +34 -0
  11. package/telegram-plugin/gateway/outbound-send-path.ts +12 -1
  12. package/telegram-plugin/operator-events.ts +40 -16
  13. package/telegram-plugin/secret-detect/db-uri.ts +90 -0
  14. package/telegram-plugin/secret-detect/index.ts +24 -1
  15. package/telegram-plugin/secret-detect/inert-values.ts +147 -0
  16. package/telegram-plugin/secret-detect/kv-scanner.ts +108 -0
  17. package/telegram-plugin/secret-detect/patterns.ts +24 -4
  18. package/telegram-plugin/tests/format-consistency.test.ts +93 -0
  19. package/telegram-plugin/tests/gateway-session-model-relaunch.test.ts +40 -2
  20. package/telegram-plugin/tests/ipc-server-validate-operator.test.ts +20 -11
  21. package/telegram-plugin/tests/outbound-send-path.test.ts +1 -1
  22. package/telegram-plugin/tests/secret-detect-cross-engine.test.ts +263 -0
  23. package/telegram-plugin/tests/secret-detect-write-path.test.ts +403 -0
  24. package/telegram-plugin/tests/turn-flush-safety.test.ts +2 -2
  25. package/vendor/hindsight-memory/scripts/lib/client.py +58 -0
  26. package/vendor/hindsight-memory/scripts/lib/secret_patterns.json +431 -0
  27. package/vendor/hindsight-memory/scripts/lib/secret_redact.py +563 -0
  28. package/vendor/hindsight-memory/scripts/lib/secret_redaction_vectors.json +397 -0
  29. package/vendor/hindsight-memory/scripts/tests/test_secret_redact.py +522 -0
@@ -2120,7 +2120,7 @@ var init_esm = __esm(() => {
2120
2120
  });
2121
2121
 
2122
2122
  // src/build-info.ts
2123
- var VERSION = "0.19.35", COMMIT_SHA = "76ae22ca";
2123
+ var VERSION = "0.19.36", COMMIT_SHA = "ba505b15";
2124
2124
 
2125
2125
  // src/cli/resolve-version.ts
2126
2126
  import { existsSync, readFileSync } from "node:fs";
@@ -35532,7 +35532,7 @@ class AuthBrokerClient {
35532
35532
  sock.once("error", onError);
35533
35533
  sock.once("connect", () => {
35534
35534
  sock.removeListener("error", onError);
35535
- sock.on("data", (chunk) => this.onData(chunk));
35535
+ sock.on("data", (chunk2) => this.onData(chunk2));
35536
35536
  sock.on("error", (err) => this.onSocketError(err));
35537
35537
  sock.on("close", () => this.onSocketClose());
35538
35538
  this.socket = sock;
@@ -35546,8 +35546,8 @@ class AuthBrokerClient {
35546
35546
  this.connecting = null;
35547
35547
  }
35548
35548
  }
35549
- onData(chunk) {
35550
- this.buffer += chunk.toString("utf8");
35549
+ onData(chunk2) {
35550
+ this.buffer += chunk2.toString("utf8");
35551
35551
  let idx;
35552
35552
  while ((idx = this.buffer.indexOf(`
35553
35553
  `)) !== -1) {
@@ -39525,11 +39525,11 @@ function readAuditRaw(logPath, opts = {}) {
39525
39525
  const gen = `${logPath}.${n}`;
39526
39526
  if (!existsSync51(gen))
39527
39527
  break;
39528
- const chunk = tailBytes(gen, budget, strict);
39529
- if (chunk.length === 0)
39528
+ const chunk2 = tailBytes(gen, budget, strict);
39529
+ if (chunk2.length === 0)
39530
39530
  continue;
39531
- chunks.push(chunk);
39532
- budget -= Buffer.byteLength(chunk, "utf-8");
39531
+ chunks.push(chunk2);
39532
+ budget -= Buffer.byteLength(chunk2, "utf-8");
39533
39533
  }
39534
39534
  return chunks.reverse().filter((c) => c.length > 0).map((c) => c.endsWith(`
39535
39535
  `) ? c : c + `
@@ -44451,8 +44451,8 @@ async function hostdRequest(opts, req) {
44451
44451
  reject(err);
44452
44452
  }
44453
44453
  });
44454
- socket.on("data", (chunk) => {
44455
- buf += chunk.toString("utf8");
44454
+ socket.on("data", (chunk2) => {
44455
+ buf += chunk2.toString("utf8");
44456
44456
  if (Buffer.byteLength(buf, "utf8") > MAX_FRAME_BYTES3 * 2) {
44457
44457
  if (settled)
44458
44458
  return;
@@ -71143,9 +71143,600 @@ import { execFileSync as execFileSync11 } from "node:child_process";
71143
71143
 
71144
71144
  // src/agents/handoff-summarizer.ts
71145
71145
  init_atomic();
71146
- init_observation_scopes();
71147
71146
  import { readFileSync as readFileSync19, writeFileSync as writeFileSync9, renameSync as renameSync7, mkdirSync as mkdirSync16, existsSync as existsSync23, statSync as statSync11, readdirSync as readdirSync10 } from "node:fs";
71148
71147
  import { join as join18 } from "node:path";
71148
+
71149
+ // telegram-plugin/secret-detect/patterns.ts
71150
+ var ANCHORED_PATTERNS = [
71151
+ { rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
71152
+ { rule_id: "anthropic_oauth_code", regex: /(?:^|\s)([A-Za-z0-9_-]{20,}#[A-Za-z0-9_-]{20,})(?=\s|$)/gm, captureIndex: 1, slugHint: "anthropic_oauth_code" },
71153
+ { rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
71154
+ { rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
71155
+ { rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
71156
+ { rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
71157
+ { rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
71158
+ { rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
71159
+ { rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
71160
+ { rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
71161
+ { rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
71162
+ { rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
71163
+ { rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
71164
+ { rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
71165
+ { rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
71166
+ { rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
71167
+ ];
71168
+ var STRUCTURED_PATTERNS = [
71169
+ {
71170
+ rule_id: "env_key_value",
71171
+ regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
71172
+ captureIndex: 3,
71173
+ slugHint: "env"
71174
+ },
71175
+ {
71176
+ rule_id: "json_secret_field",
71177
+ regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
71178
+ captureIndex: 1,
71179
+ slugHint: "json_secret"
71180
+ },
71181
+ {
71182
+ rule_id: "cli_flag",
71183
+ regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
71184
+ captureIndex: 2,
71185
+ slugHint: "cli_flag"
71186
+ },
71187
+ {
71188
+ rule_id: "bearer_auth_header",
71189
+ regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/gi,
71190
+ captureIndex: 1,
71191
+ slugHint: "bearer_token"
71192
+ },
71193
+ {
71194
+ rule_id: "bearer_loose",
71195
+ regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/gi,
71196
+ captureIndex: 1,
71197
+ slugHint: "bearer_token"
71198
+ },
71199
+ {
71200
+ rule_id: "basic_auth_header",
71201
+ regex: /Authorization\s*[:=]\s*Basic\s+([A-Za-z0-9+/=]{8,})/gi,
71202
+ captureIndex: 1,
71203
+ slugHint: "basic_auth"
71204
+ },
71205
+ {
71206
+ rule_id: "pem_private_key",
71207
+ regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
71208
+ captureIndex: 0,
71209
+ slugHint: "pem_private_key"
71210
+ }
71211
+ ];
71212
+ var PROVIDER_PATTERNS = [
71213
+ { rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
71214
+ { rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
71215
+ { rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
71216
+ { rule_id: "sendgrid_api_key", regex: /\b(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "sendgrid_key" },
71217
+ { rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
71218
+ { rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
71219
+ { rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
71220
+ { rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
71221
+ { rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
71222
+ { rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
71223
+ { rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
71224
+ { rule_id: "doppler_token", regex: /\b(dp\.(?:pt|st|ct|sa|scim|audit)\.[A-Za-z0-9]{40,44})\b/g, captureIndex: 1, slugHint: "doppler_token" },
71225
+ { rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
71226
+ { rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
71227
+ { rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
71228
+ { rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
71229
+ { rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
71230
+ { rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
71231
+ { rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
71232
+ { rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
71233
+ { rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
71234
+ { rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
71235
+ { rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
71236
+ { rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
71237
+ { rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
71238
+ { rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
71239
+ { rule_id: "grafana_service_account", regex: /\b(glsa_[A-Za-z0-9]{32}_[a-fA-F0-9]{8})\b/g, captureIndex: 1, slugHint: "grafana_token" },
71240
+ { rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
71241
+ { rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
71242
+ { rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
71243
+ ];
71244
+ var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
71245
+
71246
+ // telegram-plugin/secret-detect/entropy.ts
71247
+ function shannonEntropy(s) {
71248
+ if (s.length === 0)
71249
+ return 0;
71250
+ const counts = new Map;
71251
+ for (const ch of s) {
71252
+ counts.set(ch, (counts.get(ch) ?? 0) + 1);
71253
+ }
71254
+ let h = 0;
71255
+ const len = s.length;
71256
+ for (const c of counts.values()) {
71257
+ const p = c / len;
71258
+ h -= p * Math.log2(p);
71259
+ }
71260
+ return h;
71261
+ }
71262
+
71263
+ // telegram-plugin/secret-detect/inert-values.ts
71264
+ var INERT_VALUE_RE = [
71265
+ /^\[REDACTED(?::[A-Za-z0-9_]+)?\]$/i,
71266
+ /^\$\{?[A-Za-z_][A-Za-z0-9_]*\}?$/,
71267
+ /^\{\{[^\n\r\u2028\u2029]*\}\}$/,
71268
+ /^<[^<>\n\r\u2028\u2029]*>$/,
71269
+ /^%[A-Za-z_][A-Za-z0-9_]*%$/,
71270
+ /^vault:[A-Za-z0-9_./-]*$/i,
71271
+ /^[*x\u2022.]+$/i,
71272
+ /^(?:process\.env|import\.meta\.env|os\.environ)(?:\.[A-Za-z_$][A-Za-z0-9_$]*|\[["']?[A-Za-z_$][A-Za-z0-9_$]*["']?\])*$/,
71273
+ /^(?:changeme|change-me|replaceme|replace-me|placeholder|todo|tbd|yourkey|your-key|yourpassword|your-password|yoursecret|your-secret|yourtoken|your-token)(?:[-_][A-Za-z0-9-]+)?$/i,
71274
+ /^[a-z]+(?: [a-z]+){2,}$/
71275
+ ];
71276
+ var CREDENTIAL_RUN_RE = /[A-Za-z0-9]{12,}/g;
71277
+ var MIXED_CLASS_RUN_MIN = 12;
71278
+ var SINGLE_CLASS_RUN_MIN = 16;
71279
+ function runIsCredentialShaped(run) {
71280
+ let classes = 0;
71281
+ if (/[a-z]/.test(run))
71282
+ classes++;
71283
+ if (/[A-Z]/.test(run))
71284
+ classes++;
71285
+ if (/[0-9]/.test(run))
71286
+ classes++;
71287
+ return run.length >= (classes >= 2 ? MIXED_CLASS_RUN_MIN : SINGLE_CLASS_RUN_MIN);
71288
+ }
71289
+ function hasCredentialShapedRun(value) {
71290
+ for (const run of value.match(CREDENTIAL_RUN_RE) ?? []) {
71291
+ if (runIsCredentialShaped(run))
71292
+ return true;
71293
+ }
71294
+ return false;
71295
+ }
71296
+ function isInertValue(value) {
71297
+ if (!INERT_VALUE_RE.some((re) => re.test(value)))
71298
+ return false;
71299
+ return !hasCredentialShapedRun(value);
71300
+ }
71301
+ var INERT_GATED_RULES = new Set([
71302
+ "env_key_value",
71303
+ "json_secret_field",
71304
+ "cli_flag"
71305
+ ]);
71306
+ var TRAILING_PUNCT_RE = /[.,;:!?)\]}]+$/;
71307
+ function stripTrailingPunctuation(value) {
71308
+ return value.replace(TRAILING_PUNCT_RE, "");
71309
+ }
71310
+
71311
+ // telegram-plugin/secret-detect/kv-scanner.ts
71312
+ var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
71313
+ var KV_ENTROPY_THRESHOLD = 4;
71314
+ var MEMORABLE_PW_RE = /\b([A-Za-z0-9_-]*(?:password|passwd|passphrase|pwd))\b\s*(?:[:=]\s*|\s+is\s+)(["']?)([^\s"']{8,64})\2/gi;
71315
+ var MEMORABLE_PW_RULE_ID = "memorable_password";
71316
+ var MEMORABLE_PW_MIN_CLASSES = 2;
71317
+ function charClassCount(value) {
71318
+ let n = 0;
71319
+ if (/[a-z]/.test(value))
71320
+ n++;
71321
+ if (/[A-Z]/.test(value))
71322
+ n++;
71323
+ if (/[0-9]/.test(value))
71324
+ n++;
71325
+ if (/[^A-Za-z0-9]/.test(value))
71326
+ n++;
71327
+ return n;
71328
+ }
71329
+ function looksLikeMemorablePassword(value) {
71330
+ if (isInertValue(value))
71331
+ return false;
71332
+ const core = stripTrailingPunctuation(value);
71333
+ if (core.length < 8 || core.length > 64)
71334
+ return false;
71335
+ if (isInertValue(core))
71336
+ return false;
71337
+ if (new Set(core).size < 4)
71338
+ return false;
71339
+ return charClassCount(core) >= MEMORABLE_PW_MIN_CLASSES;
71340
+ }
71341
+ function scanMemorablePasswords(text) {
71342
+ const hits = [];
71343
+ MEMORABLE_PW_RE.lastIndex = 0;
71344
+ let m;
71345
+ while ((m = MEMORABLE_PW_RE.exec(text)) !== null) {
71346
+ const keyName = m[1];
71347
+ const value = m[3];
71348
+ if (!value || !looksLikeMemorablePassword(value))
71349
+ continue;
71350
+ const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
71351
+ if (valueOffsetInMatch < 0)
71352
+ continue;
71353
+ const start = m.index + valueOffsetInMatch;
71354
+ hits.push({
71355
+ rule_id: MEMORABLE_PW_RULE_ID,
71356
+ start,
71357
+ end: start + value.length,
71358
+ matched_text: value,
71359
+ key_name: keyName,
71360
+ confidence: "ambiguous"
71361
+ });
71362
+ }
71363
+ return hits;
71364
+ }
71365
+ function scanKeyValue(text) {
71366
+ const hits = [];
71367
+ KV_RE.lastIndex = 0;
71368
+ let m;
71369
+ while ((m = KV_RE.exec(text)) !== null) {
71370
+ const [, keyName, value] = m;
71371
+ if (!value)
71372
+ continue;
71373
+ if (isInertValue(value))
71374
+ continue;
71375
+ const h = shannonEntropy(value);
71376
+ if (h < KV_ENTROPY_THRESHOLD)
71377
+ continue;
71378
+ const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
71379
+ if (valueOffsetInMatch < 0)
71380
+ continue;
71381
+ const start = m.index + valueOffsetInMatch;
71382
+ const end = start + value.length;
71383
+ hits.push({
71384
+ rule_id: "kv_entropy",
71385
+ start,
71386
+ end,
71387
+ matched_text: value,
71388
+ key_name: keyName,
71389
+ confidence: "ambiguous"
71390
+ });
71391
+ }
71392
+ return hits;
71393
+ }
71394
+
71395
+ // telegram-plugin/secret-detect/db-uri.ts
71396
+ var DB_URI_RE = /\b([a-zA-Z][a-zA-Z0-9+.-]+):\/\/([^\s:/@]+):([^\s/?#]+)@([^\s/@?#]+)/g;
71397
+ var DB_URI_RULE_ID = "db_uri_password";
71398
+ function isInertPassword(value) {
71399
+ if (value.startsWith("[REDACTED"))
71400
+ return true;
71401
+ return /^[*x]+$/i.test(value);
71402
+ }
71403
+ function scanDbUris(text) {
71404
+ const hits = [];
71405
+ DB_URI_RE.lastIndex = 0;
71406
+ let m;
71407
+ while ((m = DB_URI_RE.exec(text)) !== null) {
71408
+ const scheme = m[1];
71409
+ const user = m[2];
71410
+ const password = m[3];
71411
+ if (isInertPassword(password))
71412
+ continue;
71413
+ const start = m.index + scheme.length + 3 + user.length + 1;
71414
+ hits.push({
71415
+ rule_id: DB_URI_RULE_ID,
71416
+ start,
71417
+ end: start + password.length,
71418
+ matched_text: password,
71419
+ key_name: `${scheme}_password`,
71420
+ confidence: "ambiguous"
71421
+ });
71422
+ }
71423
+ return hits;
71424
+ }
71425
+
71426
+ // telegram-plugin/secret-detect/generic-entropy.ts
71427
+ var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
71428
+ var GENERIC_MIN_DISTINCT = 18;
71429
+ var MAX_GENERIC_HITS = 20;
71430
+ function hasDistinctChars(tok, n) {
71431
+ const seen = new Uint8Array(128);
71432
+ let distinct = 0;
71433
+ for (let i = 0;i < tok.length; i++) {
71434
+ const c = tok.charCodeAt(i);
71435
+ if (seen[c] === 0) {
71436
+ seen[c] = 1;
71437
+ if (++distinct >= n)
71438
+ return true;
71439
+ }
71440
+ }
71441
+ return false;
71442
+ }
71443
+ function hasDigit(tok) {
71444
+ for (let i = 0;i < tok.length; i++) {
71445
+ const c = tok.charCodeAt(i);
71446
+ if (c >= 48 && c <= 57)
71447
+ return true;
71448
+ }
71449
+ return false;
71450
+ }
71451
+ function scanGenericSecrets(text) {
71452
+ const hits = [];
71453
+ CANDIDATE_RE.lastIndex = 0;
71454
+ let m;
71455
+ while ((m = CANDIDATE_RE.exec(text)) !== null) {
71456
+ if (hits.length >= MAX_GENERIC_HITS)
71457
+ break;
71458
+ const tok = m[0];
71459
+ if (!hasDigit(tok))
71460
+ continue;
71461
+ if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
71462
+ continue;
71463
+ hits.push({
71464
+ rule_id: "generic_high_entropy",
71465
+ start: m.index,
71466
+ end: m.index + tok.length,
71467
+ matched_text: tok,
71468
+ confidence: "ambiguous"
71469
+ });
71470
+ }
71471
+ return hits;
71472
+ }
71473
+
71474
+ // telegram-plugin/secret-detect/chunker.ts
71475
+ var CHUNK_THRESHOLD = 32 * 1024;
71476
+ var WINDOW_SIZE = 16 * 1024;
71477
+ var OVERLAP = 8 * 1024;
71478
+ function chunk(text) {
71479
+ if (text.length <= CHUNK_THRESHOLD) {
71480
+ return [{ offset: 0, text }];
71481
+ }
71482
+ const out = [];
71483
+ let offset = 0;
71484
+ while (offset < text.length) {
71485
+ const end = Math.min(offset + WINDOW_SIZE, text.length);
71486
+ out.push({ offset, text: text.slice(offset, end) });
71487
+ if (end >= text.length)
71488
+ break;
71489
+ offset = end - OVERLAP;
71490
+ }
71491
+ return out;
71492
+ }
71493
+
71494
+ // telegram-plugin/secret-detect/suppressor.ts
71495
+ var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
71496
+ var WINDOW = 40;
71497
+ var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
71498
+ function isSuppressed(text, start, end) {
71499
+ const left = Math.max(0, start - WINDOW);
71500
+ const right = Math.min(text.length, end + WINDOW);
71501
+ const context = text.slice(left, start) + text.slice(end, right);
71502
+ return MARKER_RE.test(context);
71503
+ }
71504
+
71505
+ // telegram-plugin/secret-detect/slug.ts
71506
+ function sanitizeKeyName(raw) {
71507
+ const up = raw.toUpperCase();
71508
+ const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
71509
+ return cleaned.length > 0 ? cleaned : "SECRET";
71510
+ }
71511
+ function datePart(now = new Date) {
71512
+ const y = now.getUTCFullYear();
71513
+ const m = String(now.getUTCMonth() + 1).padStart(2, "0");
71514
+ const d = String(now.getUTCDate()).padStart(2, "0");
71515
+ return `${y}${m}${d}`;
71516
+ }
71517
+ function deriveSlug(inputs, existing) {
71518
+ let base;
71519
+ if (inputs.key_name && inputs.key_name.trim().length > 0) {
71520
+ base = sanitizeKeyName(inputs.key_name);
71521
+ } else {
71522
+ base = `${inputs.rule_id}_${datePart(inputs.now)}`;
71523
+ }
71524
+ if (!existing.has(base))
71525
+ return base;
71526
+ let n = 2;
71527
+ while (existing.has(`${base}_${n}`))
71528
+ n++;
71529
+ return `${base}_${n}`;
71530
+ }
71531
+ // telegram-plugin/secret-detect/url-redact.ts
71532
+ var SENSITIVE_PARAMS = new Set([
71533
+ "token",
71534
+ "key",
71535
+ "api_key",
71536
+ "apikey",
71537
+ "secret",
71538
+ "access_token",
71539
+ "password",
71540
+ "pass",
71541
+ "auth",
71542
+ "client_secret",
71543
+ "refresh_token",
71544
+ "signature"
71545
+ ]);
71546
+ var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
71547
+ function redactUrls(text) {
71548
+ return text.replace(URL_RE, (m) => {
71549
+ const redacted = redactOne(m);
71550
+ return redacted ?? m;
71551
+ });
71552
+ }
71553
+ function redactOne(raw) {
71554
+ let u;
71555
+ try {
71556
+ u = new URL(raw);
71557
+ } catch {
71558
+ return null;
71559
+ }
71560
+ let changed = false;
71561
+ if (u.username || u.password) {
71562
+ u.username = "***";
71563
+ u.password = "";
71564
+ changed = true;
71565
+ }
71566
+ for (const [key] of u.searchParams) {
71567
+ if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
71568
+ u.searchParams.set(key, "***");
71569
+ changed = true;
71570
+ }
71571
+ }
71572
+ return changed ? u.toString() : null;
71573
+ }
71574
+
71575
+ // telegram-plugin/secret-detect/index.ts
71576
+ function detectSecrets(text) {
71577
+ if (!text || text.length === 0)
71578
+ return [];
71579
+ const windows = chunk(text);
71580
+ const raw = [];
71581
+ for (const win of windows) {
71582
+ for (const p of ALL_PATTERNS) {
71583
+ const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
71584
+ let m;
71585
+ while ((m = re.exec(win.text)) !== null) {
71586
+ if (m[0].length === 0) {
71587
+ re.lastIndex++;
71588
+ continue;
71589
+ }
71590
+ const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
71591
+ if (!cap)
71592
+ continue;
71593
+ const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
71594
+ if (matchStart < 0)
71595
+ continue;
71596
+ const globalStart = win.offset + matchStart;
71597
+ const globalEnd = globalStart + cap.length;
71598
+ const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
71599
+ if (INERT_GATED_RULES.has(p.rule_id) && isInertValue(cap))
71600
+ continue;
71601
+ if (p.rule_id === "env_key_value") {
71602
+ const ENV_KV_MIN_LEN = 12;
71603
+ const ENV_KV_MIN_ENTROPY = 3.5;
71604
+ if (cap.length < ENV_KV_MIN_LEN)
71605
+ continue;
71606
+ if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
71607
+ continue;
71608
+ }
71609
+ raw.push({
71610
+ rule_id: p.rule_id,
71611
+ start: globalStart,
71612
+ end: globalEnd,
71613
+ matched_text: cap,
71614
+ key_name: keyName,
71615
+ confidence: "high"
71616
+ });
71617
+ }
71618
+ }
71619
+ const kvHits = scanKeyValue(win.text);
71620
+ for (const h of kvHits) {
71621
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71622
+ }
71623
+ for (const h of scanDbUris(win.text)) {
71624
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71625
+ }
71626
+ for (const h of scanMemorablePasswords(win.text)) {
71627
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71628
+ }
71629
+ const genHits = scanGenericSecrets(win.text);
71630
+ for (const h of genHits) {
71631
+ raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
71632
+ }
71633
+ }
71634
+ const deduped = dedupeRaw(raw);
71635
+ const final = dropOverlaps(deduped);
71636
+ const existing = new Set;
71637
+ const out = [];
71638
+ for (const h of final) {
71639
+ const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
71640
+ existing.add(suggested_slug);
71641
+ out.push({
71642
+ rule_id: h.rule_id,
71643
+ matched_text: h.matched_text,
71644
+ start: h.start,
71645
+ end: h.end,
71646
+ confidence: h.confidence,
71647
+ suppressed: isSuppressed(text, h.start, h.end),
71648
+ suggested_slug,
71649
+ key_name: h.key_name
71650
+ });
71651
+ }
71652
+ out.sort((a, b) => a.start - b.start);
71653
+ return out;
71654
+ }
71655
+ function dedupeRaw(raw) {
71656
+ const seen = new Map;
71657
+ for (const h of raw) {
71658
+ const key = `${h.start}:${h.end}`;
71659
+ const existing = seen.get(key);
71660
+ if (!existing) {
71661
+ seen.set(key, h);
71662
+ continue;
71663
+ }
71664
+ if (existing.confidence === "ambiguous" && h.confidence === "high") {
71665
+ seen.set(key, h);
71666
+ }
71667
+ }
71668
+ return Array.from(seen.values());
71669
+ }
71670
+ function dropOverlaps(hits) {
71671
+ const out = hits.filter((h) => !(h.confidence === "ambiguous" && hits.some((o) => o !== h && o.start <= h.start && o.end >= h.end && !(o.start === h.start && o.end === h.end))));
71672
+ out.sort((a, b) => a.start - b.start || a.end - b.end);
71673
+ return out;
71674
+ }
71675
+
71676
+ // telegram-plugin/secret-detect/redact.ts
71677
+ var REDACTED_MARKER = "[REDACTED]";
71678
+ function redact(text) {
71679
+ if (!text || text.length === 0)
71680
+ return text;
71681
+ const urlScrubbed = redactUrls(text);
71682
+ const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
71683
+ if (hits.length === 0)
71684
+ return urlScrubbed;
71685
+ const sorted = [...hits].sort((a, b) => b.start - a.start);
71686
+ let out = urlScrubbed;
71687
+ for (const h of sorted) {
71688
+ out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
71689
+ }
71690
+ return out;
71691
+ }
71692
+ function redactedMarker(ruleId) {
71693
+ const trimmed = ruleId.replace(/^(kv|env)_/, "");
71694
+ if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
71695
+ return REDACTED_MARKER;
71696
+ }
71697
+ return `[REDACTED:${trimmed}]`;
71698
+ }
71699
+ // src/memory/hindsight-write-redaction.ts
71700
+ function redactJsonStrings(value) {
71701
+ if (typeof value === "string")
71702
+ return redact(value);
71703
+ if (Array.isArray(value))
71704
+ return value.map(redactJsonStrings);
71705
+ if (value && typeof value === "object") {
71706
+ const out = {};
71707
+ for (const [k, v] of Object.entries(value)) {
71708
+ out[k] = redactJsonStrings(v);
71709
+ }
71710
+ return out;
71711
+ }
71712
+ return value;
71713
+ }
71714
+ var REDACTED_ITEM_FIELDS = ["content", "context"];
71715
+ function redactMemoryWriteBody(body) {
71716
+ if (!body || typeof body !== "object" || Array.isArray(body))
71717
+ return body;
71718
+ const src = body;
71719
+ if (!Array.isArray(src.items))
71720
+ return body;
71721
+ const items = src.items.map((item) => {
71722
+ if (!item || typeof item !== "object" || Array.isArray(item))
71723
+ return item;
71724
+ const row = { ...item };
71725
+ for (const field of REDACTED_ITEM_FIELDS) {
71726
+ if (typeof row[field] === "string") {
71727
+ row[field] = redact(row[field]);
71728
+ }
71729
+ }
71730
+ if (row.metadata && typeof row.metadata === "object") {
71731
+ row.metadata = redactJsonStrings(row.metadata);
71732
+ }
71733
+ return row;
71734
+ });
71735
+ return { ...src, items };
71736
+ }
71737
+
71738
+ // src/agents/handoff-summarizer.ts
71739
+ init_observation_scopes();
71149
71740
  var HANDOFF_STATUS_MIRROR_SKIPPED = "mirror-skipped-invalid-scope";
71150
71741
  var DEFAULT_MAX_TURNS = 50;
71151
71742
  var TOPIC_MAX_CHARS = 117;
@@ -71332,7 +71923,7 @@ async function mirrorToHindsight(briefing, opts) {
71332
71923
  `);
71333
71924
  return false;
71334
71925
  }
71335
- const body = {
71926
+ const body = redactMemoryWriteBody({
71336
71927
  items: [
71337
71928
  {
71338
71929
  content: briefing,
@@ -71342,7 +71933,7 @@ async function mirrorToHindsight(briefing, opts) {
71342
71933
  }
71343
71934
  ],
71344
71935
  async: true
71345
- };
71936
+ });
71346
71937
  try {
71347
71938
  await fetchFn(endpoint, {
71348
71939
  method: "POST",
@@ -74348,8 +74939,8 @@ Bootstrapping agent: ${name}
74348
74939
  const code = await new Promise((resolve24) => {
74349
74940
  process.stdin.setEncoding("utf-8");
74350
74941
  let buf = "";
74351
- process.stdin.on("data", (chunk) => {
74352
- buf += chunk.toString();
74942
+ process.stdin.on("data", (chunk2) => {
74943
+ buf += chunk2.toString();
74353
74944
  const newlineIdx = buf.indexOf(`
74354
74945
  `);
74355
74946
  if (newlineIdx !== -1) {
@@ -74431,8 +75022,8 @@ switchroom agent add: ${name}
74431
75022
  return new Promise((resolveLine) => {
74432
75023
  process.stdin.setEncoding("utf-8");
74433
75024
  let buf = "";
74434
- const onData = (chunk) => {
74435
- buf += chunk.toString();
75025
+ const onData = (chunk2) => {
75026
+ buf += chunk2.toString();
74436
75027
  const newlineIdx = buf.indexOf(`
74437
75028
  `);
74438
75029
  if (newlineIdx !== -1) {
@@ -76463,8 +77054,8 @@ async function readHiddenLine2(prompt) {
76463
77054
  stdin.setRawMode(true);
76464
77055
  }
76465
77056
  let line = "";
76466
- const onData = (chunk) => {
76467
- const s = chunk.toString("utf8");
77057
+ const onData = (chunk2) => {
77058
+ const s = chunk2.toString("utf8");
76468
77059
  for (const ch of s) {
76469
77060
  if (ch === `
76470
77061
  ` || ch === "\r") {
@@ -77393,7 +77984,7 @@ function getVaultPath2(configPath) {
77393
77984
  return resolvePath("~/.switchroom/vault.enc");
77394
77985
  }
77395
77986
  }
77396
- function maskToken(s) {
77987
+ function maskToken2(s) {
77397
77988
  if (s.length >= 18)
77398
77989
  return `${s.slice(0, 6)}...${s.slice(-4)}`;
77399
77990
  return "***";
@@ -77681,7 +78272,7 @@ function registerVaultSweep(vault, program3) {
77681
78272
  console.log("");
77682
78273
  console.log(source_default.dim(`masked values for display only:`));
77683
78274
  for (const v of values) {
77684
- console.log(` vault:${v.key} \u2192 ${maskToken(v.value)}`);
78275
+ console.log(` vault:${v.key} \u2192 ${maskToken2(v.value)}`);
77685
78276
  }
77686
78277
  if (opts.dryRun) {
77687
78278
  console.log(source_default.yellow("dry-run \u2014 no files modified. Rerun without --dry-run to apply."));
@@ -80654,8 +81245,8 @@ class VaultBroker {
80654
81245
  peer = this.testOpts._testIdentify ? this.testOpts._testIdentify(listenerSocketPath, socket) : identify(listenerSocketPath, socket);
80655
81246
  }
80656
81247
  let buffer = "";
80657
- socket.on("data", (chunk) => {
80658
- buffer += chunk.toString("utf8");
81248
+ socket.on("data", (chunk2) => {
81249
+ buffer += chunk2.toString("utf8");
80659
81250
  if (Buffer.byteLength(buffer, "utf8") > MAX_FRAME_BYTES) {
80660
81251
  const resp = encodeResponse(errorResponse("BAD_REQUEST", "Frame exceeds 64 KiB limit"));
80661
81252
  socket.end(resp);
@@ -81764,8 +82355,8 @@ class VaultBroker {
81764
82355
  const auditCaller = isOperator ? "operator" : unlockPeer !== null ? callerFromPeer(unlockPeer) : `pid:${process.pid}`;
81765
82356
  const auditCgroup = isOperator ? undefined : unlockPeer?.systemdUnit ?? undefined;
81766
82357
  let buffer = "";
81767
- socket.on("data", (chunk) => {
81768
- buffer += chunk.toString("utf8");
82358
+ socket.on("data", (chunk2) => {
82359
+ buffer += chunk2.toString("utf8");
81769
82360
  const newlineIdx = buffer.indexOf(`
81770
82361
  `);
81771
82362
  if (newlineIdx === -1) {
@@ -83332,7 +83923,7 @@ function promptLine2(prompt, hidden = false) {
83332
83923
  function readStdinToEnd() {
83333
83924
  return new Promise((resolve30, reject) => {
83334
83925
  const chunks = [];
83335
- process.stdin.on("data", (chunk) => chunks.push(chunk));
83926
+ process.stdin.on("data", (chunk2) => chunks.push(chunk2));
83336
83927
  process.stdin.on("end", () => {
83337
83928
  resolve30(Buffer.concat(chunks).toString("utf8"));
83338
83929
  });
@@ -85484,9 +86075,9 @@ Repairing vector index coverage for ${target}${opts.dryRun ? source_default.yell
85484
86075
  `));
85485
86076
  const child = spawn5("docker", argv, { stdio: ["ignore", "pipe", "inherit"] });
85486
86077
  let captured = "";
85487
- child.stdout?.on("data", (chunk) => {
85488
- captured += chunk.toString();
85489
- process.stdout.write(chunk);
86078
+ child.stdout?.on("data", (chunk2) => {
86079
+ captured += chunk2.toString();
86080
+ process.stdout.write(chunk2);
85490
86081
  });
85491
86082
  const childExit = await new Promise((resolve30) => {
85492
86083
  child.on("error", () => resolve30(127));
@@ -85587,14 +86178,21 @@ Demoting memory ${source_default.cyan(memoryId)}`));
85587
86178
  const timeoutMs = Math.max(1000, parseInt(opts.timeout, 10) || 60000);
85588
86179
  const ctrl = new AbortController;
85589
86180
  const t = setTimeout(() => ctrl.abort(), timeoutMs);
86181
+ const payload = redactMemoryWriteBody({
86182
+ items: [{ content, tags: ["operator-authored", "profile"] }],
86183
+ async: false
86184
+ });
86185
+ const stored = payload.items[0].content;
86186
+ if (stored !== content) {
86187
+ console.error(source_default.yellow(`\u26a0 Credential-shaped text in this fact was replaced with a
86188
+ ` + ` [REDACTED] marker before it was stored. Put secrets in the
86189
+ ` + " vault (`switchroom vault set <key>`) and reference them as\n" + " `vault:<key>` instead."));
86190
+ }
85590
86191
  try {
85591
86192
  const res = await fetch(url, {
85592
86193
  method: "POST",
85593
86194
  headers: { "Content-Type": "application/json" },
85594
- body: JSON.stringify({
85595
- items: [{ content, tags: ["operator-authored", "profile"] }],
85596
- async: false
85597
- }),
86195
+ body: JSON.stringify(payload),
85598
86196
  signal: ctrl.signal
85599
86197
  });
85600
86198
  if (!res.ok) {
@@ -85602,7 +86200,7 @@ Demoting memory ${source_default.cyan(memoryId)}`));
85602
86200
  process.exit(1);
85603
86201
  }
85604
86202
  console.log(source_default.green(`\u2713 Added to profile bank "${bank}"`));
85605
- console.log(source_default.gray(` ${content}`));
86203
+ console.log(source_default.gray(` ${stored}`));
85606
86204
  console.log(source_default.gray(" (fact extraction runs in the background \u2014 `profile list` may lag a few seconds)"));
85607
86205
  console.log(source_default.gray(`
85608
86206
  Wire it into agents via memory.recall.additional_banks: ["${bank}"] in switchroom.yaml,
@@ -87895,8 +88493,8 @@ function forwardToGateway(socketPath, req, opts = {}) {
87895
88493
  conn.write(JSON.stringify(req) + `
87896
88494
  `);
87897
88495
  });
87898
- conn.on("data", (chunk) => {
87899
- buf += chunk;
88496
+ conn.on("data", (chunk2) => {
88497
+ buf += chunk2;
87900
88498
  const nl = buf.indexOf(`
87901
88499
  `);
87902
88500
  if (nl === -1)
@@ -94075,410 +94673,6 @@ function computeFingerprint(source, code) {
94075
94673
  return `${source}::${code}`;
94076
94674
  }
94077
94675
 
94078
- // telegram-plugin/secret-detect/patterns.ts
94079
- var ANCHORED_PATTERNS = [
94080
- { rule_id: "anthropic_api_key", regex: /\b(sk-ant-[A-Za-z0-9_-]{8,})\b/g, captureIndex: 1, slugHint: "anthropic_api_key" },
94081
- { rule_id: "anthropic_oauth_code", regex: /(?:^|\s)([A-Za-z0-9_-]{20,}#[A-Za-z0-9_-]{20,})(?=\s|$)/gm, captureIndex: 1, slugHint: "anthropic_oauth_code" },
94082
- { rule_id: "openai_api_key", regex: /\b(sk-[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "openai_api_key" },
94083
- { rule_id: "github_pat_classic", regex: /\b(ghp_[A-Za-z0-9]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
94084
- { rule_id: "github_pat_fine_grained", regex: /\b(github_pat_[A-Za-z0-9_]{20,})\b/g, captureIndex: 1, slugHint: "github_pat" },
94085
- { rule_id: "slack_token", regex: /\b(xox[baprs]-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_token" },
94086
- { rule_id: "slack_app_token", regex: /\b(xapp-[A-Za-z0-9-]{10,})\b/g, captureIndex: 1, slugHint: "slack_app_token" },
94087
- { rule_id: "groq_api_key", regex: /\b(gsk_[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "groq_api_key" },
94088
- { rule_id: "google_api_key", regex: /\b(AIza[0-9A-Za-z\-_]{20,})\b/g, captureIndex: 1, slugHint: "google_api_key" },
94089
- { rule_id: "perplexity_api_key", regex: /\b(pplx-[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "perplexity_api_key" },
94090
- { rule_id: "npm_token", regex: /\b(npm_[A-Za-z0-9]{10,})\b/g, captureIndex: 1, slugHint: "npm_token" },
94091
- { rule_id: "telegram_bot_token_prefixed", regex: /\bbot(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
94092
- { rule_id: "telegram_bot_token", regex: /\b(\d{6,}:[A-Za-z0-9_-]{20,})\b/g, captureIndex: 1, slugHint: "telegram_bot_token" },
94093
- { rule_id: "laravel_sanctum_token", regex: /\b(\d+\|[A-Za-z0-9]{40,})\b/g, captureIndex: 1, slugHint: "api_token" },
94094
- { rule_id: "aws_access_key", regex: /\b(AKIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
94095
- { rule_id: "jwt", regex: /\b(eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g, captureIndex: 1, slugHint: "jwt" }
94096
- ];
94097
- var STRUCTURED_PATTERNS = [
94098
- {
94099
- rule_id: "env_key_value",
94100
- regex: /\b([A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD))\b\s*[=:]\s*(["']?)([^\s"'\\]+)\2/g,
94101
- captureIndex: 3,
94102
- slugHint: "env"
94103
- },
94104
- {
94105
- rule_id: "json_secret_field",
94106
- regex: /"(?:apiKey|token|secret|password|passwd|accessToken|refreshToken)"\s*:\s*"([^"]+)"/g,
94107
- captureIndex: 1,
94108
- slugHint: "json_secret"
94109
- },
94110
- {
94111
- rule_id: "cli_flag",
94112
- regex: /--(?:api[-_]?key|hook[-_]?token|token|secret|password|passwd)\s+(["']?)([^\s"']+)\1/g,
94113
- captureIndex: 2,
94114
- slugHint: "cli_flag"
94115
- },
94116
- {
94117
- rule_id: "bearer_auth_header",
94118
- regex: /Authorization\s*[:=]\s*Bearer\s+([A-Za-z0-9._\-+=]+)/g,
94119
- captureIndex: 1,
94120
- slugHint: "bearer_token"
94121
- },
94122
- {
94123
- rule_id: "bearer_loose",
94124
- regex: /\bBearer\s+([A-Za-z0-9._\-+=]{18,})\b/g,
94125
- captureIndex: 1,
94126
- slugHint: "bearer_token"
94127
- },
94128
- {
94129
- rule_id: "pem_private_key",
94130
- regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z ]*PRIVATE KEY-----/g,
94131
- captureIndex: 0,
94132
- slugHint: "pem_private_key"
94133
- }
94134
- ];
94135
- var PROVIDER_PATTERNS = [
94136
- { rule_id: "slack_webhook", regex: /(https:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9_/]+)/g, captureIndex: 1, slugHint: "slack_webhook" },
94137
- { rule_id: "stripe_live_secret", regex: /\b(sk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
94138
- { rule_id: "stripe_restricted", regex: /\b(rk_live_[A-Za-z0-9]{24,})\b/g, captureIndex: 1, slugHint: "stripe_key" },
94139
- { rule_id: "sendgrid_api_key", regex: /\b(SG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "sendgrid_key" },
94140
- { rule_id: "gitlab_pat", regex: /\b(glpat-[A-Za-z0-9_-]{20})\b/g, captureIndex: 1, slugHint: "gitlab_pat" },
94141
- { rule_id: "huggingface_token", regex: /\b(hf_[A-Za-z0-9]{34,})\b/g, captureIndex: 1, slugHint: "huggingface_token" },
94142
- { rule_id: "twilio_api_key", regex: /\b(SK[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "twilio_api_key" },
94143
- { rule_id: "mailgun_key", regex: /\b(key-[0-9a-f]{32})\b/g, captureIndex: 1, slugHint: "mailgun_key" },
94144
- { rule_id: "digitalocean_pat", regex: /\b(dop_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
94145
- { rule_id: "digitalocean_oauth", regex: /\b(doo_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
94146
- { rule_id: "digitalocean_refresh", regex: /\b(dor_v1_[a-f0-9]{64})\b/g, captureIndex: 1, slugHint: "digitalocean_token" },
94147
- { rule_id: "doppler_token", regex: /\b(dp\.(?:pt|st|ct|sa|scim|audit)\.[A-Za-z0-9]{40,44})\b/g, captureIndex: 1, slugHint: "doppler_token" },
94148
- { rule_id: "linear_api_key", regex: /\b(lin_api_[A-Za-z0-9]{40})\b/g, captureIndex: 1, slugHint: "linear_api_key" },
94149
- { rule_id: "shopify_access_token", regex: /\b(shpat_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
94150
- { rule_id: "shopify_shared_secret", regex: /\b(shpss_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
94151
- { rule_id: "shopify_private_app", regex: /\b(shppa_[a-fA-F0-9]{32})\b/g, captureIndex: 1, slugHint: "shopify_token" },
94152
- { rule_id: "square_access_token", regex: /\b(sq0atp-[A-Za-z0-9_-]{22})\b/g, captureIndex: 1, slugHint: "square_token" },
94153
- { rule_id: "square_oauth_secret", regex: /\b(sq0csp-[A-Za-z0-9_-]{43})\b/g, captureIndex: 1, slugHint: "square_token" },
94154
- { rule_id: "newrelic_key", regex: /\b(NRAK-[A-Z0-9]{27})\b/g, captureIndex: 1, slugHint: "newrelic_key" },
94155
- { rule_id: "notion_token", regex: /\b(ntn_[A-Za-z0-9]{46})\b/g, captureIndex: 1, slugHint: "notion_token" },
94156
- { rule_id: "planetscale_password", regex: /\b(pscale_pw_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
94157
- { rule_id: "planetscale_token", regex: /\b(pscale_tkn_[A-Za-z0-9_.-]{43})\b/g, captureIndex: 1, slugHint: "planetscale_token" },
94158
- { rule_id: "supabase_service_key", regex: /\b(sbp_[a-f0-9]{40})\b/g, captureIndex: 1, slugHint: "supabase_key" },
94159
- { rule_id: "atlassian_token", regex: /\b(ATATT[A-Za-z0-9_\-=]{20,})\b/g, captureIndex: 1, slugHint: "atlassian_token" },
94160
- { rule_id: "dropbox_token", regex: /\b(sl\.[A-Za-z0-9_-]{130,})/g, captureIndex: 1, slugHint: "dropbox_token" },
94161
- { rule_id: "databricks_token", regex: /\b(dapi[a-f0-9]{32})\b/g, captureIndex: 1, slugHint: "databricks_token" },
94162
- { rule_id: "grafana_service_account", regex: /\b(glsa_[A-Za-z0-9]{32}_[a-fA-F0-9]{8})\b/g, captureIndex: 1, slugHint: "grafana_token" },
94163
- { rule_id: "pypi_token", regex: /\b(pypi-AgEIcHlwaS[A-Za-z0-9_-]{50,})/g, captureIndex: 1, slugHint: "pypi_token" },
94164
- { rule_id: "aws_temp_access_key", regex: /\b(ASIA[0-9A-Z]{16})\b/g, captureIndex: 1, slugHint: "aws_access_key" },
94165
- { rule_id: "gcp_oauth_token", regex: /\b(ya29\.[A-Za-z0-9_-]{30,})/g, captureIndex: 1, slugHint: "gcp_oauth_token" }
94166
- ];
94167
- var ALL_PATTERNS = [...ANCHORED_PATTERNS, ...PROVIDER_PATTERNS, ...STRUCTURED_PATTERNS];
94168
-
94169
- // telegram-plugin/secret-detect/entropy.ts
94170
- function shannonEntropy(s) {
94171
- if (s.length === 0)
94172
- return 0;
94173
- const counts = new Map;
94174
- for (const ch of s) {
94175
- counts.set(ch, (counts.get(ch) ?? 0) + 1);
94176
- }
94177
- let h = 0;
94178
- const len = s.length;
94179
- for (const c of counts.values()) {
94180
- const p = c / len;
94181
- h -= p * Math.log2(p);
94182
- }
94183
- return h;
94184
- }
94185
-
94186
- // telegram-plugin/secret-detect/kv-scanner.ts
94187
- var KV_RE = /\b([A-Za-z_][A-Za-z0-9_-]*(?:password|passwd|token|secret|key|api[_-]?key))\s*[:=]\s*["']?([^\s"'\\]{8,})["']?/gi;
94188
- var KV_ENTROPY_THRESHOLD = 4;
94189
- function scanKeyValue(text) {
94190
- const hits = [];
94191
- KV_RE.lastIndex = 0;
94192
- let m;
94193
- while ((m = KV_RE.exec(text)) !== null) {
94194
- const [, keyName, value] = m;
94195
- if (!value)
94196
- continue;
94197
- const h = shannonEntropy(value);
94198
- if (h < KV_ENTROPY_THRESHOLD)
94199
- continue;
94200
- const valueOffsetInMatch = m[0].indexOf(value, keyName.length);
94201
- if (valueOffsetInMatch < 0)
94202
- continue;
94203
- const start = m.index + valueOffsetInMatch;
94204
- const end = start + value.length;
94205
- hits.push({
94206
- rule_id: "kv_entropy",
94207
- start,
94208
- end,
94209
- matched_text: value,
94210
- key_name: keyName,
94211
- confidence: "ambiguous"
94212
- });
94213
- }
94214
- return hits;
94215
- }
94216
-
94217
- // telegram-plugin/secret-detect/generic-entropy.ts
94218
- var CANDIDATE_RE = /[A-Za-z0-9]{28,}/g;
94219
- var GENERIC_MIN_DISTINCT = 18;
94220
- var MAX_GENERIC_HITS = 20;
94221
- function hasDistinctChars(tok, n) {
94222
- const seen = new Uint8Array(128);
94223
- let distinct = 0;
94224
- for (let i = 0;i < tok.length; i++) {
94225
- const c = tok.charCodeAt(i);
94226
- if (seen[c] === 0) {
94227
- seen[c] = 1;
94228
- if (++distinct >= n)
94229
- return true;
94230
- }
94231
- }
94232
- return false;
94233
- }
94234
- function hasDigit(tok) {
94235
- for (let i = 0;i < tok.length; i++) {
94236
- const c = tok.charCodeAt(i);
94237
- if (c >= 48 && c <= 57)
94238
- return true;
94239
- }
94240
- return false;
94241
- }
94242
- function scanGenericSecrets(text) {
94243
- const hits = [];
94244
- CANDIDATE_RE.lastIndex = 0;
94245
- let m;
94246
- while ((m = CANDIDATE_RE.exec(text)) !== null) {
94247
- if (hits.length >= MAX_GENERIC_HITS)
94248
- break;
94249
- const tok = m[0];
94250
- if (!hasDigit(tok))
94251
- continue;
94252
- if (!hasDistinctChars(tok, GENERIC_MIN_DISTINCT))
94253
- continue;
94254
- hits.push({
94255
- rule_id: "generic_high_entropy",
94256
- start: m.index,
94257
- end: m.index + tok.length,
94258
- matched_text: tok,
94259
- confidence: "ambiguous"
94260
- });
94261
- }
94262
- return hits;
94263
- }
94264
-
94265
- // telegram-plugin/secret-detect/chunker.ts
94266
- var CHUNK_THRESHOLD = 32 * 1024;
94267
- var WINDOW_SIZE = 16 * 1024;
94268
- var OVERLAP = 8 * 1024;
94269
- function chunk(text) {
94270
- if (text.length <= CHUNK_THRESHOLD) {
94271
- return [{ offset: 0, text }];
94272
- }
94273
- const out = [];
94274
- let offset = 0;
94275
- while (offset < text.length) {
94276
- const end = Math.min(offset + WINDOW_SIZE, text.length);
94277
- out.push({ offset, text: text.slice(offset, end) });
94278
- if (end >= text.length)
94279
- break;
94280
- offset = end - OVERLAP;
94281
- }
94282
- return out;
94283
- }
94284
-
94285
- // telegram-plugin/secret-detect/suppressor.ts
94286
- var MARKERS = ["test", "mock", "example", "fixture", "dummy"];
94287
- var WINDOW = 40;
94288
- var MARKER_RE = new RegExp(`\\b(?:${MARKERS.join("|")})\\b`, "i");
94289
- function isSuppressed(text, start, end) {
94290
- const left = Math.max(0, start - WINDOW);
94291
- const right = Math.min(text.length, end + WINDOW);
94292
- const context = text.slice(left, start) + text.slice(end, right);
94293
- return MARKER_RE.test(context);
94294
- }
94295
-
94296
- // telegram-plugin/secret-detect/slug.ts
94297
- function sanitizeKeyName(raw) {
94298
- const up = raw.toUpperCase();
94299
- const cleaned = up.replace(/[^A-Z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
94300
- return cleaned.length > 0 ? cleaned : "SECRET";
94301
- }
94302
- function datePart(now = new Date) {
94303
- const y = now.getUTCFullYear();
94304
- const m = String(now.getUTCMonth() + 1).padStart(2, "0");
94305
- const d = String(now.getUTCDate()).padStart(2, "0");
94306
- return `${y}${m}${d}`;
94307
- }
94308
- function deriveSlug(inputs, existing) {
94309
- let base;
94310
- if (inputs.key_name && inputs.key_name.trim().length > 0) {
94311
- base = sanitizeKeyName(inputs.key_name);
94312
- } else {
94313
- base = `${inputs.rule_id}_${datePart(inputs.now)}`;
94314
- }
94315
- if (!existing.has(base))
94316
- return base;
94317
- let n = 2;
94318
- while (existing.has(`${base}_${n}`))
94319
- n++;
94320
- return `${base}_${n}`;
94321
- }
94322
- // telegram-plugin/secret-detect/url-redact.ts
94323
- var SENSITIVE_PARAMS = new Set([
94324
- "token",
94325
- "key",
94326
- "api_key",
94327
- "apikey",
94328
- "secret",
94329
- "access_token",
94330
- "password",
94331
- "pass",
94332
- "auth",
94333
- "client_secret",
94334
- "refresh_token",
94335
- "signature"
94336
- ]);
94337
- var URL_RE = /\b(?:https?|wss?|ftp):\/\/[^\s<>"']+/gi;
94338
- function redactUrls(text) {
94339
- return text.replace(URL_RE, (m) => {
94340
- const redacted = redactOne(m);
94341
- return redacted ?? m;
94342
- });
94343
- }
94344
- function redactOne(raw) {
94345
- let u;
94346
- try {
94347
- u = new URL(raw);
94348
- } catch {
94349
- return null;
94350
- }
94351
- let changed = false;
94352
- if (u.username || u.password) {
94353
- u.username = "***";
94354
- u.password = "";
94355
- changed = true;
94356
- }
94357
- for (const [key] of u.searchParams) {
94358
- if (SENSITIVE_PARAMS.has(key.toLowerCase())) {
94359
- u.searchParams.set(key, "***");
94360
- changed = true;
94361
- }
94362
- }
94363
- return changed ? u.toString() : null;
94364
- }
94365
-
94366
- // telegram-plugin/secret-detect/index.ts
94367
- function detectSecrets(text) {
94368
- if (!text || text.length === 0)
94369
- return [];
94370
- const windows = chunk(text);
94371
- const raw = [];
94372
- for (const win of windows) {
94373
- for (const p of ALL_PATTERNS) {
94374
- const re = new RegExp(p.regex.source, p.regex.flags.includes("g") ? p.regex.flags : p.regex.flags + "g");
94375
- let m;
94376
- while ((m = re.exec(win.text)) !== null) {
94377
- if (m[0].length === 0) {
94378
- re.lastIndex++;
94379
- continue;
94380
- }
94381
- const cap = p.captureIndex === 0 ? m[0] : m[p.captureIndex];
94382
- if (!cap)
94383
- continue;
94384
- const matchStart = p.captureIndex === 0 ? m.index : m.index + m[0].indexOf(cap);
94385
- if (matchStart < 0)
94386
- continue;
94387
- const globalStart = win.offset + matchStart;
94388
- const globalEnd = globalStart + cap.length;
94389
- const keyName = p.rule_id === "env_key_value" ? m[1] : undefined;
94390
- if (p.rule_id === "env_key_value") {
94391
- const ENV_KV_MIN_LEN = 12;
94392
- const ENV_KV_MIN_ENTROPY = 3.5;
94393
- if (cap.length < ENV_KV_MIN_LEN)
94394
- continue;
94395
- if (shannonEntropy(cap) < ENV_KV_MIN_ENTROPY)
94396
- continue;
94397
- }
94398
- raw.push({
94399
- rule_id: p.rule_id,
94400
- start: globalStart,
94401
- end: globalEnd,
94402
- matched_text: cap,
94403
- key_name: keyName,
94404
- confidence: "high"
94405
- });
94406
- }
94407
- }
94408
- const kvHits = scanKeyValue(win.text);
94409
- for (const h of kvHits) {
94410
- raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
94411
- }
94412
- const genHits = scanGenericSecrets(win.text);
94413
- for (const h of genHits) {
94414
- raw.push({ ...h, start: h.start + win.offset, end: h.end + win.offset });
94415
- }
94416
- }
94417
- const deduped = dedupeRaw(raw);
94418
- const final = dropOverlaps(deduped);
94419
- const existing = new Set;
94420
- const out = [];
94421
- for (const h of final) {
94422
- const suggested_slug = deriveSlug({ key_name: h.key_name, rule_id: h.rule_id }, existing);
94423
- existing.add(suggested_slug);
94424
- out.push({
94425
- rule_id: h.rule_id,
94426
- matched_text: h.matched_text,
94427
- start: h.start,
94428
- end: h.end,
94429
- confidence: h.confidence,
94430
- suppressed: isSuppressed(text, h.start, h.end),
94431
- suggested_slug,
94432
- key_name: h.key_name
94433
- });
94434
- }
94435
- out.sort((a, b) => a.start - b.start);
94436
- return out;
94437
- }
94438
- function dedupeRaw(raw) {
94439
- const seen = new Map;
94440
- for (const h of raw) {
94441
- const key = `${h.start}:${h.end}`;
94442
- const existing = seen.get(key);
94443
- if (!existing) {
94444
- seen.set(key, h);
94445
- continue;
94446
- }
94447
- if (existing.confidence === "ambiguous" && h.confidence === "high") {
94448
- seen.set(key, h);
94449
- }
94450
- }
94451
- return Array.from(seen.values());
94452
- }
94453
- function dropOverlaps(hits) {
94454
- const out = hits.filter((h) => !(h.confidence === "ambiguous" && hits.some((o) => o !== h && o.start <= h.start && o.end >= h.end && !(o.start === h.start && o.end === h.end))));
94455
- out.sort((a, b) => a.start - b.start || a.end - b.end);
94456
- return out;
94457
- }
94458
-
94459
- // telegram-plugin/secret-detect/redact.ts
94460
- var REDACTED_MARKER = "[REDACTED]";
94461
- function redact(text) {
94462
- if (!text || text.length === 0)
94463
- return text;
94464
- const urlScrubbed = redactUrls(text);
94465
- const hits = detectSecrets(urlScrubbed).filter((h) => h.rule_id !== "generic_high_entropy");
94466
- if (hits.length === 0)
94467
- return urlScrubbed;
94468
- const sorted = [...hits].sort((a, b) => b.start - a.start);
94469
- let out = urlScrubbed;
94470
- for (const h of sorted) {
94471
- out = out.slice(0, h.start) + redactedMarker(h.rule_id) + out.slice(h.end);
94472
- }
94473
- return out;
94474
- }
94475
- function redactedMarker(ruleId) {
94476
- const trimmed = ruleId.replace(/^(kv|env)_/, "");
94477
- if (!trimmed || trimmed === "key_value" || trimmed === "kv_entropy") {
94478
- return REDACTED_MARKER;
94479
- }
94480
- return `[REDACTED:${trimmed}]`;
94481
- }
94482
94676
  // src/issues/store.ts
94483
94677
  var ISSUES_FILE = "issues.jsonl";
94484
94678
  var ISSUES_LOCK = "issues.lock";
@@ -98428,6 +98622,42 @@ function withSynthesizedTools(result) {
98428
98622
  ]
98429
98623
  };
98430
98624
  }
98625
+ var READ_ONLY_TOOL_NAMES = new Set([
98626
+ "recall",
98627
+ "reflect",
98628
+ "search",
98629
+ "list_memories",
98630
+ "list_directives",
98631
+ "list_mental_models",
98632
+ "get_mental_model",
98633
+ "get_memory",
98634
+ "get_bank"
98635
+ ]);
98636
+ function redactToolArguments(value) {
98637
+ if (typeof value === "string")
98638
+ return redact(value);
98639
+ if (Array.isArray(value))
98640
+ return value.map(redactToolArguments);
98641
+ if (value && typeof value === "object") {
98642
+ const out = {};
98643
+ for (const [k, v] of Object.entries(value)) {
98644
+ out[k] = redactToolArguments(v);
98645
+ }
98646
+ return out;
98647
+ }
98648
+ return value;
98649
+ }
98650
+ function redactToolCallParams(params) {
98651
+ const called = params;
98652
+ if (!called || typeof called !== "object")
98653
+ return params;
98654
+ if (called.arguments === undefined || called.arguments === null)
98655
+ return params;
98656
+ if (typeof called.name === "string" && READ_ONLY_TOOL_NAMES.has(called.name)) {
98657
+ return params;
98658
+ }
98659
+ return { ...called, arguments: redactToolArguments(called.arguments) };
98660
+ }
98431
98661
  function buildFallbackToolsList() {
98432
98662
  const tools = Object.entries(FALLBACK_TOOL_TABLE).map(([name, [required, props]]) => ({
98433
98663
  name,
@@ -98754,8 +98984,9 @@ class HindsightShim {
98754
98984
  if (called?.name && SYNTHESIZED_TOOL_NAMES.includes(called.name)) {
98755
98985
  return this.synthesizedCall(called.name, called.arguments);
98756
98986
  }
98987
+ const forwarded = redactToolCallParams(params);
98757
98988
  try {
98758
- const res = await this.upstream.request("tools/call", params, this.opts.toolsCallTimeoutMs ?? TOOLS_CALL_TIMEOUT_MS);
98989
+ const res = await this.upstream.request("tools/call", forwarded, this.opts.toolsCallTimeoutMs ?? TOOLS_CALL_TIMEOUT_MS);
98759
98990
  this.notifyIfRecovered();
98760
98991
  if (res.error) {
98761
98992
  return {