@yuneta/gobj-js 7.26.19 → 7.26.20

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 CHANGED
@@ -669,13 +669,21 @@ container at once) instead of guessing.
669
669
 
670
670
  ```javascript
671
671
  is_secret_name("password") // → true ("token", "jwt", "apikey"...)
672
+ is_secret_name("sim_pin") // → true (pass, pin, otp: whole segments)
673
+ is_secret_name("mapping") // → false (pin inside a word)
672
674
  is_secret_name("token_endpoint") // → false (names something ABOUT one)
673
675
  mask_secrets_inline("login user=a passw=x") // → "login user=a passw=********"
676
+ mask_secrets_inline("curl -H 'Authorization: Bearer abc' u")
677
+ // → "curl -H 'Authorization: Bearer ********' u"
674
678
  json_mask_secrets({user: "a", password: "x"}) // → {user: "a", password: "********"}
679
+ json_mask_secrets({note: "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4In0.c2ln"})
680
+ // → {note: "********"}: a JWT under any key
675
681
  trace_json_masked(kw, "sent") // trace_json() of the masked copy
676
682
  ```
677
683
 
678
- As the C kernel's: linear and bounded (a text over 4 MB is not shown at all).
684
+ As the C kernel's: the same names, the same shapes by value (a JWT, the token
685
+ after `Bearer `), a NUL in a string read as a blank; linear and bounded (a text
686
+ over 4 MB is not shown at all). `key` and `cert` are not secrets' names.
679
687
 
680
688
  ### Logging
681
689
 
@@ -795,6 +803,11 @@ gobj_command(yuno, "set-global-no-trace", {level: "timer_periodic", set: 0}, yun
795
803
  // no_trace_levels.__global_no_trace__ = [] -- stays off after a reload
796
804
  ```
797
805
 
806
+ `set` is read as the C kernel reads it: a boolean, an integer, or text --
807
+ `set`/`reset`, a JSON integer (`"1"`, `"-1"`, `"0"`), `true`/`false`,
808
+ `yes`/`no`, `on`/`off` in any case. Anything else answers `-1` *"bitmask set
809
+ or re-set?"*: a real (`2.5`), `" 1"`, `"0x1"`, `"1e3"`, `"+5"`, `"007"`.
810
+
798
811
  The traffic of the websocket is `C_IEVENT_CLI`'s own level `ievents`; its lines
799
812
  go to the yuno attr `trace_ievent_callback`, or to the console.
800
813
 
@@ -3436,6 +3436,11 @@ var SECRET_NAME_JOINED = [
3436
3436
  "sessionkey",
3437
3437
  "authdata"
3438
3438
  ];
3439
+ var SECRET_NAME_SEGMENTS = /* @__PURE__ */ new Set([
3440
+ "pass",
3441
+ "pin",
3442
+ "otp"
3443
+ ]);
3439
3444
  var NOT_SECRET_SEGMENTS = /* @__PURE__ */ new Set([
3440
3445
  "endpoint",
3441
3446
  "url",
@@ -3468,10 +3473,16 @@ function is_secret_name(name) {
3468
3473
  if (typeof name !== "string" || name.length < 3) return false;
3469
3474
  let lower = name.toLowerCase();
3470
3475
  let hit = false;
3471
- for (const part of SECRET_NAME_PARTS) if (lower.indexOf(part) >= 0) {
3476
+ for (const segment of lower.split(/[^a-z0-9]+/)) if (SECRET_NAME_SEGMENTS.has(segment)) {
3472
3477
  hit = true;
3473
3478
  break;
3474
3479
  }
3480
+ if (!hit) {
3481
+ for (const part of SECRET_NAME_PARTS) if (lower.indexOf(part) >= 0) {
3482
+ hit = true;
3483
+ break;
3484
+ }
3485
+ }
3475
3486
  if (!hit) {
3476
3487
  let joined = lower.replace(/[_\-. \t]/g, "");
3477
3488
  for (const part of SECRET_NAME_JOINED) if (joined.indexOf(part) >= 0) {
@@ -3509,17 +3520,63 @@ function inline_secret_value_end(str, p, outer) {
3509
3520
  }
3510
3521
  }
3511
3522
  /************************************************************
3523
+ * A credential by its SHAPE, whatever names it -- the C kernel's
3524
+ * jwt_len() / bearer_token_len(), the shapes the agent's audit
3525
+ * record redacts:
3526
+ * - a JWT: "eyJ" (the base64url of '{"') at the start of a
3527
+ * word, and three parts of base64url joined by '.'; the dots
3528
+ * at the end of the run are not its own (a JWT ends a
3529
+ * sentence: "... eyJ.eyJ.sig.");
3530
+ * - the token after "Bearer " (any case, a word of its own).
3531
+ ************************************************************/
3532
+ function is_ascii_alnum_code(code) {
3533
+ return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122;
3534
+ }
3535
+ function is_base64url_code(code) {
3536
+ return is_ascii_alnum_code(code) || code === 45 || code === 95;
3537
+ }
3538
+ function jwt_len(str, p) {
3539
+ if (str.length - p < 3 || str[p] !== "e" || str[p + 1] !== "y" || str[p + 2] !== "J") return 0;
3540
+ if (p > 0 && (is_base64url_code(str.charCodeAt(p - 1)) || str[p - 1] === ".")) return 0;
3541
+ let e = p;
3542
+ let dots = 0;
3543
+ while (e < str.length && (is_base64url_code(str.charCodeAt(e)) || str[e] === ".")) {
3544
+ if (str[e] === ".") dots++;
3545
+ e++;
3546
+ }
3547
+ while (str[e - 1] === ".") {
3548
+ e--;
3549
+ dots--;
3550
+ }
3551
+ return dots === 2 ? e - p : 0;
3552
+ }
3553
+ var BEARER_STOPS = " \r\n'\",;}]\0";
3554
+ function bearer_token(str, p) {
3555
+ const word_len = 6;
3556
+ if (str.length - p <= word_len || str.slice(p, p + word_len).toLowerCase() !== "bearer") return [0, 0];
3557
+ if (p > 0 && is_ascii_alnum_code(str.charCodeAt(p - 1))) return [0, 0];
3558
+ let t = p + word_len;
3559
+ if (str[t] !== " " && str[t] !== " ") return [0, 0];
3560
+ while (t < str.length && (str[t] === " " || str[t] === " ")) t++;
3561
+ let e = t;
3562
+ while (e < str.length && BEARER_STOPS.indexOf(str[e]) < 0) e++;
3563
+ return [t - p, e - t];
3564
+ }
3565
+ /************************************************************
3512
3566
  * A text (a command line) with the value of every "name=value"
3513
3567
  * whose name is a secret's written as "********", quoted or not
3514
3568
  * (and the "value" of a write-attr whose attribute names a
3515
- * secret). The rest as it is. Return null when there was nothing
3516
- * to mask. As the C kernel's mask_secrets_inline().
3569
+ * secret); and a JWT or the token after "Bearer ", wherever they
3570
+ * are, whatever names them. The rest as it is. Return null when
3571
+ * there was nothing to mask. As the C kernel's
3572
+ * mask_secrets_inline().
3517
3573
  ************************************************************/
3518
3574
  var MASK_MAX_NAME = 128;
3519
3575
  var MASK_MAX_BYTES = 4194304;
3520
3576
  var MASK_TOO_LARGE = "<not shown: too large to mask>";
3521
3577
  function mask_secrets_inline(str) {
3522
- if (typeof str !== "string" || str.indexOf("=") < 0) return null;
3578
+ if (typeof str !== "string") return null;
3579
+ if (str.indexOf("=") < 0 && str.indexOf("eyJ") < 0 && !/bearer/i.test(str)) return null;
3523
3580
  if (str.length > MASK_MAX_BYTES) return MASK_TOO_LARGE;
3524
3581
  let value_is_secret = false;
3525
3582
  let re_attr = /(^|[ \t])attribute=["']?([^ \t"']*)/g;
@@ -3543,6 +3600,18 @@ function mask_secrets_inline(str) {
3543
3600
  if (!outer && (p === 0 || str[p - 1] === " " || str[p - 1] === " " || str[p - 1] === "=")) outer = c;
3544
3601
  else if (c === outer && is_blank_or_end(p + 1)) outer = "";
3545
3602
  }
3603
+ let shape = c === "e" ? jwt_len(str, p) : 0;
3604
+ let token_offset = 0;
3605
+ if (c === "b" || c === "B") [token_offset, shape] = bearer_token(str, p);
3606
+ if (shape) {
3607
+ parts.push(str.substring(copied, p + token_offset));
3608
+ parts.push("********");
3609
+ p += token_offset + shape;
3610
+ copied = p;
3611
+ changed = true;
3612
+ word = p;
3613
+ continue;
3614
+ }
3546
3615
  if (c !== "=") {
3547
3616
  p++;
3548
3617
  continue;
@@ -3579,7 +3648,8 @@ function mask_secrets_inline(str) {
3579
3648
  * value of a key with a secret's name is "********", whatever
3580
3649
  * its type (not an absent one, a null or an empty string); so is
3581
3650
  * the "value" of a dict whose "attribute" names a secret; and a
3582
- * string is masked as mask_secrets_inline(). Return a masked
3651
+ * string, under any key, is masked as mask_secrets_inline() (a
3652
+ * JWT, a "Bearer" token, a secret name=value). Return a masked
3583
3653
  * copy, or `jn` itself when there was nothing to mask. As the C
3584
3654
  * kernel's json_mask_secrets().
3585
3655
  *
@@ -3608,7 +3678,7 @@ function _json_mask_secrets(jn, memo, depth, budget) {
3608
3678
  return MASK_TOO_LARGE;
3609
3679
  }
3610
3680
  budget.left -= jn.length;
3611
- let masked = mask_secrets_inline(jn);
3681
+ let masked = mask_secrets_inline(jn.indexOf("\0") >= 0 ? jn.replace(/\u0000/g, " ") : jn);
3612
3682
  return masked !== null ? masked : jn;
3613
3683
  }
3614
3684
  let is_array = Array.isArray(jn);
@@ -7110,15 +7180,34 @@ function save_trace_scope(gobj, attr, scope, levels) {
7110
7180
  return gobj_save_persistent_attrs(gobj, attr);
7111
7181
  }
7112
7182
  /***************************************************************
7113
- * "set" as the C command reads it: TRUE / set / 1, FALSE / reset / 0
7183
+ * "set" as the C command reads it (trace_set_value() of c_yuno.c):
7184
+ * a boolean; an integer in the range of an int (true unless 0); or
7185
+ * text -- set / reset, a JSON integer -?(0|[1-9][0-9]*), or true /
7186
+ * false, yes / no, on / off, any case. null for anything else: a
7187
+ * real (2.5), " 1", "0x1", "1e3", "+5", "007" and "2.5" are refused,
7188
+ * as in C. Up to gobj-js 7.26.19 the text went through Number(),
7189
+ * which took all of them, and "yes" / "on" were refused.
7114
7190
  ***************************************************************/
7191
+ var INT_MIN = -2147483648;
7192
+ var INT_MAX = 2147483647;
7115
7193
  function parse_set(value) {
7116
7194
  if (value === true || value === false) return value;
7117
- let v = String(value === void 0 || value === null ? "" : value).toLowerCase();
7118
- if (v === "true" || v === "set") return true;
7119
- if (v === "false" || v === "reset") return false;
7120
- if (v === "" || isNaN(Number(v))) return null;
7121
- return Number(v) ? true : false;
7195
+ if (typeof value === "number") {
7196
+ if (!Number.isInteger(value) || value < INT_MIN || value > INT_MAX) return null;
7197
+ return value !== 0;
7198
+ }
7199
+ if (typeof value !== "string" || value === "") return null;
7200
+ let v = value.toLowerCase();
7201
+ if (v === "set") return true;
7202
+ if (v === "reset") return false;
7203
+ if (/^-?(0|[1-9][0-9]*)$/.test(value)) {
7204
+ let n = Number(value);
7205
+ if (n < INT_MIN || n > INT_MAX) return null;
7206
+ return n !== 0;
7207
+ }
7208
+ if (v === "true" || v === "yes" || v === "on") return true;
7209
+ if (v === "false" || v === "no" || v === "off") return false;
7210
+ return null;
7122
7211
  }
7123
7212
  function yuno_prefix() {
7124
7213
  return `${gobj_yuno_role()}^${gobj_yuno_name()}`;