@mnemonik/cli 7.141.0 → 7.141.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/auth/credentials.js +266 -3
  2. package/dist/auth/index.js +266 -3
  3. package/dist/auth/installSession.js +262 -0
  4. package/dist/auth/status.js +262 -0
  5. package/dist/authorizationWords.js +262 -0
  6. package/dist/diagnostics.js +266 -3
  7. package/dist/digests.json +38 -38
  8. package/dist/hooksLog.js +4 -0
  9. package/dist/identity/migrate.js +266 -3
  10. package/dist/install/hosts.js +262 -0
  11. package/dist/install/journal.js +266 -3
  12. package/dist/install/journey.js +266 -3
  13. package/dist/install/ownership.js +266 -3
  14. package/dist/install/transaction.js +266 -3
  15. package/dist/install/ui.js +266 -3
  16. package/dist/installSession.js +262 -0
  17. package/dist/installation.js +266 -3
  18. package/dist/launcher.js +266 -3
  19. package/dist/preflight.js +354 -92
  20. package/dist/project/eligibility.js +266 -3
  21. package/dist/project/records.js +266 -3
  22. package/dist/project.js +270 -7
  23. package/dist/router.js +270 -7
  24. package/dist/runtime/releaseSource.js +266 -3
  25. package/dist/runtime/selfUpdate.js +266 -3
  26. package/dist/runtime/signers.js +67 -7
  27. package/dist/runtime/store.js +4 -0
  28. package/dist/runtime/updateCheck.js +266 -3
  29. package/dist/sbom.json +21 -21
  30. package/dist/scanner/control.js +266 -3
  31. package/dist/scanner/data.js +262 -0
  32. package/dist/scanner/discover.js +354 -92
  33. package/dist/scanner/enable.js +270 -7
  34. package/dist/scanner/picker.js +325 -63
  35. package/dist/scanner/service.js +266 -3
  36. package/dist/scanner/update.js +266 -3
  37. package/dist/scanner-release.json +24 -24
  38. package/dist/status.js +266 -3
  39. package/dist/vendor/local-setup.js +266 -3
  40. package/package.json +31 -31
package/dist/project.js CHANGED
@@ -886,9 +886,13 @@ var require_ignore = __commonJS({
886
886
  });
887
887
 
888
888
  // packages/shared/dist/secretPatterns.js
889
+ var SECRET_LABEL, SECRET_LABEL_RE, LABELED_VALUE_RE;
889
890
  var init_secretPatterns = __esm({
890
891
  "packages/shared/dist/secretPatterns.js"() {
891
892
  "use strict";
893
+ SECRET_LABEL = String.raw`[\w$-]*(?:api[_-]?key|secret|token|password|credential|auth)|aws_secret_access_key`;
894
+ SECRET_LABEL_RE = new RegExp(`^(?:${SECRET_LABEL}|MYSQL_PWD|SSHPASS)$`, "i");
895
+ LABELED_VALUE_RE = new RegExp(String.raw`(?<![\w$-])((["']?)(?:${SECRET_LABEL})\2\s*[:=]\s*)(?=\S)`, "gi");
892
896
  }
893
897
  });
894
898
 
@@ -901,7 +905,7 @@ var init_protectedPaths = __esm({
901
905
 
902
906
  // packages/shared/dist/projectIdentityFile.js
903
907
  import { lstat, readFile } from "node:fs/promises";
904
- import { join } from "node:path";
908
+ import { join as join2 } from "node:path";
905
909
  function parseIdentityFile(text) {
906
910
  let value;
907
911
  try {
@@ -962,7 +966,7 @@ function isConfigurationOnly(text) {
962
966
  }
963
967
  }
964
968
  async function readIdentityFile(dir, options = {}) {
965
- const path = join(dir, ".mnemonik.json");
969
+ const path = join2(dir, ".mnemonik.json");
966
970
  try {
967
971
  const stat = await lstat(path);
968
972
  if (stat.isSymbolicLink())
@@ -3200,9 +3204,6 @@ var MCP_INSTRUCTIONS = getMcpInstructions();
3200
3204
 
3201
3205
  // packages/shared/dist/codeScanner.js
3202
3206
  var import_ignore = __toESM(require_ignore(), 1);
3203
- init_secretPatterns();
3204
- init_protectedPaths();
3205
- init_projectIdentityFile();
3206
3207
 
3207
3208
  // node_modules/web-tree-sitter/web-tree-sitter.js
3208
3209
  var __defProp2 = Object.defineProperty;
@@ -7248,14 +7249,276 @@ var Query = class {
7248
7249
  };
7249
7250
 
7250
7251
  // packages/shared/dist/ast/grammars.js
7251
- import { dirname, extname, join as join2 } from "node:path";
7252
+ import { dirname, extname, join } from "node:path";
7252
7253
  import { fileURLToPath } from "node:url";
7253
- var WASM_DIR = join2(dirname(fileURLToPath(import.meta.url)), "..", "..", "wasm");
7254
+ var WASM_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "wasm");
7254
7255
 
7255
7256
  // packages/shared/dist/ast/astChunker.js
7256
7257
  var MAX_AST_PARSE_BYTES = 2 * 1024 * 1024;
7257
7258
 
7259
+ // packages/shared/dist/redactCredentials.js
7260
+ var REDACTED = "<redacted credential>";
7261
+ var MAX_PEM_BODY_LENGTH = 8 * 1024;
7262
+ var LETTER_OR_DIGIT = /[A-Za-z0-9]/;
7263
+ function hasLetterOrDigit(value) {
7264
+ return LETTER_OR_DIGIT.test(value);
7265
+ }
7266
+ function redactContextValue(match, head = "", value = "") {
7267
+ return hasLetterOrDigit(value) ? `${head}${REDACTED}` : match;
7268
+ }
7269
+ var PLACEHOLDER_PASSWORD = /^(?:\[REDACTED(?::[A-Za-z0-9_-]+)?\]|redacted|<[^<>]*>|\{\{[^{}]*\}\}|\$\{[^{}]+\}|%[A-Za-z_][A-Za-z0-9_]*%|\*+|x{3,}|\.\.\.|\u2026|password|passwd|pass|pwd|pw|secret|changeme|change[_-]me|example|test|(?:your|my)[a-z_-]*?(?:password|passwd|pass|pwd|secret|key|token|here))$/i;
7270
+ var PLACEHOLDER_ENV_VAR = /^\$[A-Z_][A-Z0-9_]*$/;
7271
+ function isPlaceholderPassword(password) {
7272
+ return PLACEHOLDER_PASSWORD.test(password) || PLACEHOLDER_ENV_VAR.test(password);
7273
+ }
7274
+ function redactUrlPassword(match, head = "", password = "") {
7275
+ return isPlaceholderPassword(password) || !hasLetterOrDigit(password) ? match : `${head}${REDACTED}@`;
7276
+ }
7277
+ var SHELL_WORD = String.raw`'[^'\n]*'|"[^"\n]*"|(?!${REDACTED})[^\s'"\`;|&()[\]{}\\,/][^\s'"\`;|&]*`;
7278
+ var OPENING_QUOTE = String.raw`(["'\`]?)`;
7279
+ var TRAILING_PUNCTUATION = /[.,:;!?)\]}]+$/;
7280
+ var PROSE_WORDS = /* @__PURE__ */ new Set([
7281
+ "a",
7282
+ "an",
7283
+ "and",
7284
+ "as",
7285
+ "for",
7286
+ "in",
7287
+ "is",
7288
+ "it",
7289
+ "on",
7290
+ "or",
7291
+ "the",
7292
+ "to",
7293
+ "with",
7294
+ "flag",
7295
+ "option",
7296
+ "argument",
7297
+ "instead",
7298
+ "which",
7299
+ "that",
7300
+ "this",
7301
+ "puts",
7302
+ "passes",
7303
+ "leaks",
7304
+ "exposes"
7305
+ ]);
7306
+ var REGEX_DELIMITER = /\/(?:[{}()[\];]|$)/;
7307
+ function shellWordSecret(word) {
7308
+ const quote = word[0];
7309
+ if ((quote === "'" || quote === '"') && word.length >= 2 && word.endsWith(quote)) {
7310
+ const inner = word.slice(1, -1);
7311
+ if (!hasLetterOrDigit(inner) || quote === '"' && inner.startsWith("$"))
7312
+ return null;
7313
+ if (inner !== inner.trim())
7314
+ return null;
7315
+ return isPlaceholderPassword(inner) ? null : inner;
7316
+ }
7317
+ if (word.startsWith("$"))
7318
+ return null;
7319
+ const core = word.replace(TRAILING_PUNCTUATION, "");
7320
+ if (!hasLetterOrDigit(core) || isPlaceholderPassword(core))
7321
+ return null;
7322
+ if (REGEX_DELIMITER.test(core))
7323
+ return null;
7324
+ return PROSE_WORDS.has(core.toLowerCase()) ? null : core;
7325
+ }
7326
+ function redactShellWord(match, head = "", word = "", tail = "") {
7327
+ return shellWordSecret(word) === null ? match : `${head}${REDACTED}${tail}`;
7328
+ }
7329
+ function redactQuotedShellWord(match, head = "", openingQuote = "", word = "", tail = "") {
7330
+ if (openingQuote !== "" && word.startsWith(openingQuote))
7331
+ return match;
7332
+ return redactShellWord(match, head, word, tail);
7333
+ }
7334
+ var SSHPASS_COMMAND = String.raw`ssh|scp|sftp|rsync|ssh-copy-id|ansible[\w-]*`;
7335
+ function redactSshpassWord(match, head = "", openingQuote = "", word = "", tail = "", command = "") {
7336
+ if (openingQuote !== "" && word.startsWith(openingQuote))
7337
+ return match;
7338
+ const secret = shellWordSecret(word);
7339
+ if (secret === null)
7340
+ return match;
7341
+ const bare = word[0] !== "'" && word[0] !== '"';
7342
+ if (bare && /^[a-z]+$/.test(secret) && command === "")
7343
+ return match;
7344
+ return `${head}${REDACTED}${tail}`;
7345
+ }
7346
+ var PATTERNS = [
7347
+ {
7348
+ kind: "bearer",
7349
+ pattern: /(\bBearer\s+)([^\s]{40,})/gi,
7350
+ replacement: redactContextValue,
7351
+ secretGroup: 2
7352
+ },
7353
+ {
7354
+ // Before openai, whose sk- shape would otherwise claim these keys.
7355
+ kind: "anthropic",
7356
+ pattern: /(?<![A-Za-z0-9_-])sk-ant-(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
7357
+ replacement: REDACTED
7358
+ },
7359
+ {
7360
+ kind: "openai",
7361
+ pattern: /(?<![A-Za-z0-9_-])sk-(?:proj-)?(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7362
+ replacement: REDACTED
7363
+ },
7364
+ {
7365
+ kind: "shopify",
7366
+ pattern: /(?<![A-Za-z0-9_])shp(?:at|ss|ca)_[A-Za-z0-9]{16,}(?![A-Za-z0-9])/g,
7367
+ replacement: REDACTED
7368
+ },
7369
+ {
7370
+ kind: "aws_access_key",
7371
+ pattern: /(?<![A-Z0-9])AKIA[A-Z0-9]{16}(?![A-Z0-9])/g,
7372
+ replacement: REDACTED
7373
+ },
7374
+ {
7375
+ kind: "github",
7376
+ pattern: /(?<![A-Za-z0-9_])gh[opu]_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
7377
+ replacement: REDACTED
7378
+ },
7379
+ {
7380
+ kind: "slack",
7381
+ pattern: /(?<![A-Za-z0-9-])xox[abp]-(?=-*[A-Za-z0-9])[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
7382
+ replacement: REDACTED
7383
+ },
7384
+ {
7385
+ kind: "sentry",
7386
+ pattern: /(?<![A-Za-z0-9_])sntry[us]_(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7387
+ replacement: REDACTED
7388
+ },
7389
+ {
7390
+ kind: "fly",
7391
+ pattern: /(?<![A-Za-z0-9_])(?:FlyV1\s+)?fm2_(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7392
+ replacement: REDACTED
7393
+ },
7394
+ {
7395
+ kind: "google_api_key",
7396
+ pattern: /(?<![A-Za-z0-9_-])AIza(?=[_-]*[A-Za-z0-9])[0-9A-Za-z_-]{35}(?![A-Za-z0-9_-])/g,
7397
+ replacement: REDACTED
7398
+ },
7399
+ {
7400
+ kind: "jwt",
7401
+ // header.payload.signature, both JSON segments starting eyJ. The length
7402
+ // floors keep `eyJ...` prose and signature-less examples out.
7403
+ pattern: /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7404
+ replacement: REDACTED
7405
+ },
7406
+ {
7407
+ kind: "mnemonik_component_token",
7408
+ // mnc_ / mncr_ + 43 base64url (oauth/components.ts); 32+ also catches a
7409
+ // partial token. The body must contain an upper-case letter or digit, so a
7410
+ // snake_case identifier is not mistaken for one; the 8-character
7411
+ // display prefix (mnc_ + 8) is too short.
7412
+ pattern: /(?<![A-Za-z0-9_])mncr?_(?=[A-Za-z0-9_-]*[A-Z0-9])[A-Za-z0-9_-]{32,}/g,
7413
+ replacement: REDACTED
7414
+ },
7415
+ {
7416
+ kind: "stripe",
7417
+ // Secret and restricted keys (sk_/rk_ live|test) and webhook signing secrets.
7418
+ pattern: /(?<![A-Za-z0-9_])(?:(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}|whsec_[A-Za-z0-9]{20,})(?![A-Za-z0-9])/g,
7419
+ replacement: REDACTED
7420
+ },
7421
+ {
7422
+ kind: "mnemonik_proxy_token",
7423
+ // pxt_ + 32 hex (the format server.ts validates). A bare prefix in prose is left.
7424
+ pattern: /(?<![A-Za-z0-9_])pxt_[0-9a-f]{32,}/g,
7425
+ replacement: REDACTED
7426
+ },
7427
+ {
7428
+ kind: "mnemonik_api_key",
7429
+ // mnk_ + 64 hex (AuthManager.generateApiKey); 32+ also catches a half key.
7430
+ // The stored 12-character key_prefix (mnk_ + 8 hex) is not a credential.
7431
+ pattern: /(?<![A-Za-z0-9_])mnk_[0-9a-f]{32,}/g,
7432
+ replacement: REDACTED
7433
+ },
7434
+ {
7435
+ kind: "mnemonik_api_key_legacy",
7436
+ // mnk_ + 43 base64url: keys minted by hand before AuthManager's hex form
7437
+ // (production's only key in 2026-10 has this shape, and one stored memory
7438
+ // held a development key in it). Authentication accepts any mnk_ bearer.
7439
+ // The body needs upper and lower case and a digit, so a snake_case
7440
+ // identifier that starts mnk_ is not taken for one.
7441
+ pattern: /(?<![A-Za-z0-9_])mnk_(?=[A-Za-z0-9_-]*[A-Z])(?=[A-Za-z0-9_-]*[a-z])(?=[A-Za-z0-9_-]*[0-9])[A-Za-z0-9_-]{40,}(?![A-Za-z0-9_-])/g,
7442
+ replacement: REDACTED
7443
+ },
7444
+ {
7445
+ kind: "named_value",
7446
+ pattern: /(\b[A-Za-z_][A-Za-z0-9_]*(?:_TOKEN|_KEY|_SECRET|_PASSWORD|_PASS|APIKEY|API_KEY)\s*(?:=|:)\s*)([^\s]{16,})/gi,
7447
+ replacement: redactContextValue,
7448
+ secretGroup: 2
7449
+ },
7450
+ {
7451
+ kind: "connection_string",
7452
+ // scheme://user:password@host: only the password is replaced; scheme,
7453
+ // user, host and database stay readable. The user may be empty
7454
+ // (redis://:password@host). The password runs to the last `@` before a
7455
+ // path, query, fragment, quote or whitespace, so an unencoded `@` inside
7456
+ // it is still covered. Runs after named_value, which already replaces a
7457
+ // whole URL assigned to a secret-named variable.
7458
+ pattern: /\b((?:postgres(?:ql)?|mysql|mariadb|mongodb|rediss?|amqps?|mssql|sqlserver)(?:\+[a-z0-9]+)?:\/\/[^\s:/?#@'"`<>]{0,256}:)([^\s/?#'"`]{1,512})@/gi,
7459
+ replacement: redactUrlPassword,
7460
+ secretGroup: 2
7461
+ },
7462
+ {
7463
+ kind: "url_basic_auth",
7464
+ // https://user:password@host: same shape and placeholder rules as a
7465
+ // connection string; only the password is replaced.
7466
+ pattern: /\b(https?:\/\/[^\s:/?#@'"`<>]{0,256}:)([^\s/?#'"`]{1,512})@/gi,
7467
+ replacement: redactUrlPassword,
7468
+ secretGroup: 2
7469
+ },
7470
+ // Command-line passwords. Each keeps the command and replaces only the
7471
+ // password word; an expansion or placeholder in that position is left.
7472
+ {
7473
+ // sshpass -p <password> (also -p'<password>' and -p"<password>"), after
7474
+ // any other sshpass flags. 13 memories, 11 scanner chunks, 15 versions and
7475
+ // 6 dated facts in production held one on 2026-10-12.
7476
+ kind: "sshpass_password",
7477
+ pattern: new RegExp(String.raw`(${OPENING_QUOTE}\bsshpass(?:[ \t]+-[a-oq-zA-Z]\S*)*[ \t]+-p[ \t]*)(${SHELL_WORD})()(?=((?:[ \t]+(?:${SSHPASS_COMMAND})(?![\w-]))?))`, "g"),
7478
+ replacement: redactSshpassWord,
7479
+ secretGroup: 3,
7480
+ commandLine: true
7481
+ },
7482
+ {
7483
+ // Environment variables whose only purpose is a password, in shell
7484
+ // assignment form (no space around `=`): libpq's PGPASSWORD (one memory,
7485
+ // version and fact in production), MySQL's MYSQL_PWD, sshpass -e's
7486
+ // SSHPASS. named_value needs an `_PASSWORD`-style suffix and 16
7487
+ // characters, so it misses all three. Not an awk program that matches or
7488
+ // prints the assignment: `/^PGPASSWORD=/ { print "PGPASSWORD=" pw }`.
7489
+ kind: "password_env_var",
7490
+ pattern: new RegExp(String.raw`(${OPENING_QUOTE}\b(?:PGPASSWORD|MYSQL_PWD|SSHPASS)=)(${SHELL_WORD})()`, "g"),
7491
+ replacement: redactQuotedShellWord,
7492
+ secretGroup: 3,
7493
+ commandLine: true
7494
+ },
7495
+ {
7496
+ // --password=<value>, the long option every CLI that takes one spells
7497
+ // this way. The `=` keeps prose about the option ("--password is
7498
+ // required") out; the spaced form is not matched for that reason.
7499
+ kind: "password_option",
7500
+ pattern: new RegExp(String.raw`(${OPENING_QUOTE}(?<![\w-])--password=)(${SHELL_WORD})()`, "g"),
7501
+ replacement: redactQuotedShellWord,
7502
+ secretGroup: 3,
7503
+ commandLine: true
7504
+ },
7505
+ {
7506
+ // echo <password> | sudo -S: sudo reads the password from stdin. One
7507
+ // memory, two versions and two facts in production.
7508
+ kind: "sudo_password",
7509
+ pattern: new RegExp(String.raw`(\becho[ \t]+(?:-[neE]+[ \t]+)?)(${SHELL_WORD})([ \t]*\|[ \t]*sudo\b[^|\n]*?[ \t](?:-[A-Za-z]*S[A-Za-z]*|--stdin)\b)`, "g"),
7510
+ replacement: redactShellWord,
7511
+ secretGroup: 2,
7512
+ commandLine: true
7513
+ }
7514
+ ];
7515
+
7516
+ // packages/shared/dist/sourceRedaction.js
7517
+ init_secretPatterns();
7518
+
7258
7519
  // packages/shared/dist/codeScanner.js
7520
+ init_protectedPaths();
7521
+ init_projectIdentityFile();
7259
7522
  var MAX_SCANNED_FILE_BYTES = 10 * 1024 * 1024;
7260
7523
  var BUILT_IN_IGNORE_DIRS = [
7261
7524
  // JS / web
package/dist/router.js CHANGED
@@ -886,9 +886,13 @@ var require_ignore = __commonJS({
886
886
  });
887
887
 
888
888
  // packages/shared/dist/secretPatterns.js
889
+ var SECRET_LABEL, SECRET_LABEL_RE, LABELED_VALUE_RE;
889
890
  var init_secretPatterns = __esm({
890
891
  "packages/shared/dist/secretPatterns.js"() {
891
892
  "use strict";
893
+ SECRET_LABEL = String.raw`[\w$-]*(?:api[_-]?key|secret|token|password|credential|auth)|aws_secret_access_key`;
894
+ SECRET_LABEL_RE = new RegExp(`^(?:${SECRET_LABEL}|MYSQL_PWD|SSHPASS)$`, "i");
895
+ LABELED_VALUE_RE = new RegExp(String.raw`(?<![\w$-])((["']?)(?:${SECRET_LABEL})\2\s*[:=]\s*)(?=\S)`, "gi");
892
896
  }
893
897
  });
894
898
 
@@ -901,7 +905,7 @@ var init_protectedPaths = __esm({
901
905
 
902
906
  // packages/shared/dist/projectIdentityFile.js
903
907
  import { lstat, readFile } from "node:fs/promises";
904
- import { join } from "node:path";
908
+ import { join as join2 } from "node:path";
905
909
  function parseIdentityFile(text) {
906
910
  let value;
907
911
  try {
@@ -962,7 +966,7 @@ function isConfigurationOnly(text) {
962
966
  }
963
967
  }
964
968
  async function readIdentityFile(dir, options = {}) {
965
- const path = join(dir, ".mnemonik.json");
969
+ const path = join2(dir, ".mnemonik.json");
966
970
  try {
967
971
  const stat = await lstat(path);
968
972
  if (stat.isSymbolicLink())
@@ -3093,9 +3097,6 @@ var MCP_INSTRUCTIONS = getMcpInstructions();
3093
3097
 
3094
3098
  // packages/shared/dist/codeScanner.js
3095
3099
  var import_ignore = __toESM(require_ignore(), 1);
3096
- init_secretPatterns();
3097
- init_protectedPaths();
3098
- init_projectIdentityFile();
3099
3100
 
3100
3101
  // node_modules/web-tree-sitter/web-tree-sitter.js
3101
3102
  var __defProp2 = Object.defineProperty;
@@ -7141,14 +7142,276 @@ var Query = class {
7141
7142
  };
7142
7143
 
7143
7144
  // packages/shared/dist/ast/grammars.js
7144
- import { dirname, extname, join as join2 } from "node:path";
7145
+ import { dirname, extname, join } from "node:path";
7145
7146
  import { fileURLToPath } from "node:url";
7146
- var WASM_DIR = join2(dirname(fileURLToPath(import.meta.url)), "..", "..", "wasm");
7147
+ var WASM_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "wasm");
7147
7148
 
7148
7149
  // packages/shared/dist/ast/astChunker.js
7149
7150
  var MAX_AST_PARSE_BYTES = 2 * 1024 * 1024;
7150
7151
 
7152
+ // packages/shared/dist/redactCredentials.js
7153
+ var REDACTED = "<redacted credential>";
7154
+ var MAX_PEM_BODY_LENGTH = 8 * 1024;
7155
+ var LETTER_OR_DIGIT = /[A-Za-z0-9]/;
7156
+ function hasLetterOrDigit(value) {
7157
+ return LETTER_OR_DIGIT.test(value);
7158
+ }
7159
+ function redactContextValue(match, head = "", value = "") {
7160
+ return hasLetterOrDigit(value) ? `${head}${REDACTED}` : match;
7161
+ }
7162
+ var PLACEHOLDER_PASSWORD = /^(?:\[REDACTED(?::[A-Za-z0-9_-]+)?\]|redacted|<[^<>]*>|\{\{[^{}]*\}\}|\$\{[^{}]+\}|%[A-Za-z_][A-Za-z0-9_]*%|\*+|x{3,}|\.\.\.|\u2026|password|passwd|pass|pwd|pw|secret|changeme|change[_-]me|example|test|(?:your|my)[a-z_-]*?(?:password|passwd|pass|pwd|secret|key|token|here))$/i;
7163
+ var PLACEHOLDER_ENV_VAR = /^\$[A-Z_][A-Z0-9_]*$/;
7164
+ function isPlaceholderPassword(password) {
7165
+ return PLACEHOLDER_PASSWORD.test(password) || PLACEHOLDER_ENV_VAR.test(password);
7166
+ }
7167
+ function redactUrlPassword(match, head = "", password = "") {
7168
+ return isPlaceholderPassword(password) || !hasLetterOrDigit(password) ? match : `${head}${REDACTED}@`;
7169
+ }
7170
+ var SHELL_WORD = String.raw`'[^'\n]*'|"[^"\n]*"|(?!${REDACTED})[^\s'"\`;|&()[\]{}\\,/][^\s'"\`;|&]*`;
7171
+ var OPENING_QUOTE = String.raw`(["'\`]?)`;
7172
+ var TRAILING_PUNCTUATION = /[.,:;!?)\]}]+$/;
7173
+ var PROSE_WORDS = /* @__PURE__ */ new Set([
7174
+ "a",
7175
+ "an",
7176
+ "and",
7177
+ "as",
7178
+ "for",
7179
+ "in",
7180
+ "is",
7181
+ "it",
7182
+ "on",
7183
+ "or",
7184
+ "the",
7185
+ "to",
7186
+ "with",
7187
+ "flag",
7188
+ "option",
7189
+ "argument",
7190
+ "instead",
7191
+ "which",
7192
+ "that",
7193
+ "this",
7194
+ "puts",
7195
+ "passes",
7196
+ "leaks",
7197
+ "exposes"
7198
+ ]);
7199
+ var REGEX_DELIMITER = /\/(?:[{}()[\];]|$)/;
7200
+ function shellWordSecret(word) {
7201
+ const quote = word[0];
7202
+ if ((quote === "'" || quote === '"') && word.length >= 2 && word.endsWith(quote)) {
7203
+ const inner = word.slice(1, -1);
7204
+ if (!hasLetterOrDigit(inner) || quote === '"' && inner.startsWith("$"))
7205
+ return null;
7206
+ if (inner !== inner.trim())
7207
+ return null;
7208
+ return isPlaceholderPassword(inner) ? null : inner;
7209
+ }
7210
+ if (word.startsWith("$"))
7211
+ return null;
7212
+ const core = word.replace(TRAILING_PUNCTUATION, "");
7213
+ if (!hasLetterOrDigit(core) || isPlaceholderPassword(core))
7214
+ return null;
7215
+ if (REGEX_DELIMITER.test(core))
7216
+ return null;
7217
+ return PROSE_WORDS.has(core.toLowerCase()) ? null : core;
7218
+ }
7219
+ function redactShellWord(match, head = "", word = "", tail = "") {
7220
+ return shellWordSecret(word) === null ? match : `${head}${REDACTED}${tail}`;
7221
+ }
7222
+ function redactQuotedShellWord(match, head = "", openingQuote = "", word = "", tail = "") {
7223
+ if (openingQuote !== "" && word.startsWith(openingQuote))
7224
+ return match;
7225
+ return redactShellWord(match, head, word, tail);
7226
+ }
7227
+ var SSHPASS_COMMAND = String.raw`ssh|scp|sftp|rsync|ssh-copy-id|ansible[\w-]*`;
7228
+ function redactSshpassWord(match, head = "", openingQuote = "", word = "", tail = "", command = "") {
7229
+ if (openingQuote !== "" && word.startsWith(openingQuote))
7230
+ return match;
7231
+ const secret = shellWordSecret(word);
7232
+ if (secret === null)
7233
+ return match;
7234
+ const bare = word[0] !== "'" && word[0] !== '"';
7235
+ if (bare && /^[a-z]+$/.test(secret) && command === "")
7236
+ return match;
7237
+ return `${head}${REDACTED}${tail}`;
7238
+ }
7239
+ var PATTERNS = [
7240
+ {
7241
+ kind: "bearer",
7242
+ pattern: /(\bBearer\s+)([^\s]{40,})/gi,
7243
+ replacement: redactContextValue,
7244
+ secretGroup: 2
7245
+ },
7246
+ {
7247
+ // Before openai, whose sk- shape would otherwise claim these keys.
7248
+ kind: "anthropic",
7249
+ pattern: /(?<![A-Za-z0-9_-])sk-ant-(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
7250
+ replacement: REDACTED
7251
+ },
7252
+ {
7253
+ kind: "openai",
7254
+ pattern: /(?<![A-Za-z0-9_-])sk-(?:proj-)?(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7255
+ replacement: REDACTED
7256
+ },
7257
+ {
7258
+ kind: "shopify",
7259
+ pattern: /(?<![A-Za-z0-9_])shp(?:at|ss|ca)_[A-Za-z0-9]{16,}(?![A-Za-z0-9])/g,
7260
+ replacement: REDACTED
7261
+ },
7262
+ {
7263
+ kind: "aws_access_key",
7264
+ pattern: /(?<![A-Z0-9])AKIA[A-Z0-9]{16}(?![A-Z0-9])/g,
7265
+ replacement: REDACTED
7266
+ },
7267
+ {
7268
+ kind: "github",
7269
+ pattern: /(?<![A-Za-z0-9_])gh[opu]_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
7270
+ replacement: REDACTED
7271
+ },
7272
+ {
7273
+ kind: "slack",
7274
+ pattern: /(?<![A-Za-z0-9-])xox[abp]-(?=-*[A-Za-z0-9])[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
7275
+ replacement: REDACTED
7276
+ },
7277
+ {
7278
+ kind: "sentry",
7279
+ pattern: /(?<![A-Za-z0-9_])sntry[us]_(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7280
+ replacement: REDACTED
7281
+ },
7282
+ {
7283
+ kind: "fly",
7284
+ pattern: /(?<![A-Za-z0-9_])(?:FlyV1\s+)?fm2_(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7285
+ replacement: REDACTED
7286
+ },
7287
+ {
7288
+ kind: "google_api_key",
7289
+ pattern: /(?<![A-Za-z0-9_-])AIza(?=[_-]*[A-Za-z0-9])[0-9A-Za-z_-]{35}(?![A-Za-z0-9_-])/g,
7290
+ replacement: REDACTED
7291
+ },
7292
+ {
7293
+ kind: "jwt",
7294
+ // header.payload.signature, both JSON segments starting eyJ. The length
7295
+ // floors keep `eyJ...` prose and signature-less examples out.
7296
+ pattern: /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
7297
+ replacement: REDACTED
7298
+ },
7299
+ {
7300
+ kind: "mnemonik_component_token",
7301
+ // mnc_ / mncr_ + 43 base64url (oauth/components.ts); 32+ also catches a
7302
+ // partial token. The body must contain an upper-case letter or digit, so a
7303
+ // snake_case identifier is not mistaken for one; the 8-character
7304
+ // display prefix (mnc_ + 8) is too short.
7305
+ pattern: /(?<![A-Za-z0-9_])mncr?_(?=[A-Za-z0-9_-]*[A-Z0-9])[A-Za-z0-9_-]{32,}/g,
7306
+ replacement: REDACTED
7307
+ },
7308
+ {
7309
+ kind: "stripe",
7310
+ // Secret and restricted keys (sk_/rk_ live|test) and webhook signing secrets.
7311
+ pattern: /(?<![A-Za-z0-9_])(?:(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}|whsec_[A-Za-z0-9]{20,})(?![A-Za-z0-9])/g,
7312
+ replacement: REDACTED
7313
+ },
7314
+ {
7315
+ kind: "mnemonik_proxy_token",
7316
+ // pxt_ + 32 hex (the format server.ts validates). A bare prefix in prose is left.
7317
+ pattern: /(?<![A-Za-z0-9_])pxt_[0-9a-f]{32,}/g,
7318
+ replacement: REDACTED
7319
+ },
7320
+ {
7321
+ kind: "mnemonik_api_key",
7322
+ // mnk_ + 64 hex (AuthManager.generateApiKey); 32+ also catches a half key.
7323
+ // The stored 12-character key_prefix (mnk_ + 8 hex) is not a credential.
7324
+ pattern: /(?<![A-Za-z0-9_])mnk_[0-9a-f]{32,}/g,
7325
+ replacement: REDACTED
7326
+ },
7327
+ {
7328
+ kind: "mnemonik_api_key_legacy",
7329
+ // mnk_ + 43 base64url: keys minted by hand before AuthManager's hex form
7330
+ // (production's only key in 2026-10 has this shape, and one stored memory
7331
+ // held a development key in it). Authentication accepts any mnk_ bearer.
7332
+ // The body needs upper and lower case and a digit, so a snake_case
7333
+ // identifier that starts mnk_ is not taken for one.
7334
+ pattern: /(?<![A-Za-z0-9_])mnk_(?=[A-Za-z0-9_-]*[A-Z])(?=[A-Za-z0-9_-]*[a-z])(?=[A-Za-z0-9_-]*[0-9])[A-Za-z0-9_-]{40,}(?![A-Za-z0-9_-])/g,
7335
+ replacement: REDACTED
7336
+ },
7337
+ {
7338
+ kind: "named_value",
7339
+ pattern: /(\b[A-Za-z_][A-Za-z0-9_]*(?:_TOKEN|_KEY|_SECRET|_PASSWORD|_PASS|APIKEY|API_KEY)\s*(?:=|:)\s*)([^\s]{16,})/gi,
7340
+ replacement: redactContextValue,
7341
+ secretGroup: 2
7342
+ },
7343
+ {
7344
+ kind: "connection_string",
7345
+ // scheme://user:password@host: only the password is replaced; scheme,
7346
+ // user, host and database stay readable. The user may be empty
7347
+ // (redis://:password@host). The password runs to the last `@` before a
7348
+ // path, query, fragment, quote or whitespace, so an unencoded `@` inside
7349
+ // it is still covered. Runs after named_value, which already replaces a
7350
+ // whole URL assigned to a secret-named variable.
7351
+ pattern: /\b((?:postgres(?:ql)?|mysql|mariadb|mongodb|rediss?|amqps?|mssql|sqlserver)(?:\+[a-z0-9]+)?:\/\/[^\s:/?#@'"`<>]{0,256}:)([^\s/?#'"`]{1,512})@/gi,
7352
+ replacement: redactUrlPassword,
7353
+ secretGroup: 2
7354
+ },
7355
+ {
7356
+ kind: "url_basic_auth",
7357
+ // https://user:password@host: same shape and placeholder rules as a
7358
+ // connection string; only the password is replaced.
7359
+ pattern: /\b(https?:\/\/[^\s:/?#@'"`<>]{0,256}:)([^\s/?#'"`]{1,512})@/gi,
7360
+ replacement: redactUrlPassword,
7361
+ secretGroup: 2
7362
+ },
7363
+ // Command-line passwords. Each keeps the command and replaces only the
7364
+ // password word; an expansion or placeholder in that position is left.
7365
+ {
7366
+ // sshpass -p <password> (also -p'<password>' and -p"<password>"), after
7367
+ // any other sshpass flags. 13 memories, 11 scanner chunks, 15 versions and
7368
+ // 6 dated facts in production held one on 2026-10-12.
7369
+ kind: "sshpass_password",
7370
+ pattern: new RegExp(String.raw`(${OPENING_QUOTE}\bsshpass(?:[ \t]+-[a-oq-zA-Z]\S*)*[ \t]+-p[ \t]*)(${SHELL_WORD})()(?=((?:[ \t]+(?:${SSHPASS_COMMAND})(?![\w-]))?))`, "g"),
7371
+ replacement: redactSshpassWord,
7372
+ secretGroup: 3,
7373
+ commandLine: true
7374
+ },
7375
+ {
7376
+ // Environment variables whose only purpose is a password, in shell
7377
+ // assignment form (no space around `=`): libpq's PGPASSWORD (one memory,
7378
+ // version and fact in production), MySQL's MYSQL_PWD, sshpass -e's
7379
+ // SSHPASS. named_value needs an `_PASSWORD`-style suffix and 16
7380
+ // characters, so it misses all three. Not an awk program that matches or
7381
+ // prints the assignment: `/^PGPASSWORD=/ { print "PGPASSWORD=" pw }`.
7382
+ kind: "password_env_var",
7383
+ pattern: new RegExp(String.raw`(${OPENING_QUOTE}\b(?:PGPASSWORD|MYSQL_PWD|SSHPASS)=)(${SHELL_WORD})()`, "g"),
7384
+ replacement: redactQuotedShellWord,
7385
+ secretGroup: 3,
7386
+ commandLine: true
7387
+ },
7388
+ {
7389
+ // --password=<value>, the long option every CLI that takes one spells
7390
+ // this way. The `=` keeps prose about the option ("--password is
7391
+ // required") out; the spaced form is not matched for that reason.
7392
+ kind: "password_option",
7393
+ pattern: new RegExp(String.raw`(${OPENING_QUOTE}(?<![\w-])--password=)(${SHELL_WORD})()`, "g"),
7394
+ replacement: redactQuotedShellWord,
7395
+ secretGroup: 3,
7396
+ commandLine: true
7397
+ },
7398
+ {
7399
+ // echo <password> | sudo -S: sudo reads the password from stdin. One
7400
+ // memory, two versions and two facts in production.
7401
+ kind: "sudo_password",
7402
+ pattern: new RegExp(String.raw`(\becho[ \t]+(?:-[neE]+[ \t]+)?)(${SHELL_WORD})([ \t]*\|[ \t]*sudo\b[^|\n]*?[ \t](?:-[A-Za-z]*S[A-Za-z]*|--stdin)\b)`, "g"),
7403
+ replacement: redactShellWord,
7404
+ secretGroup: 2,
7405
+ commandLine: true
7406
+ }
7407
+ ];
7408
+
7409
+ // packages/shared/dist/sourceRedaction.js
7410
+ init_secretPatterns();
7411
+
7151
7412
  // packages/shared/dist/codeScanner.js
7413
+ init_protectedPaths();
7414
+ init_projectIdentityFile();
7152
7415
  var MAX_SCANNED_FILE_BYTES = 10 * 1024 * 1024;
7153
7416
  var BUILT_IN_IGNORE_DIRS = [
7154
7417
  // JS / web