@sarj/eslint-plugin 15.26.7 → 15.26.8

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/dist/index.cjs CHANGED
@@ -9486,7 +9486,7 @@ var NO_SECRET_IN_LOG_DOCUMENTATION = {
9486
9486
  rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
9487
9487
  remediation: "Omit the value, log allowlisted non-sensitive context, or use an approved redactor; truncation alone is not a safety guarantee.",
9488
9488
  category: "security",
9489
- limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers. Name-based exemptions are policy heuristics, not proof that a value is safely redacted."],
9489
+ limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers. Literal containers and template/string concatenation expressions are inspected; unknown spreads, computed properties, aliases, and opaque calls are not followed. Name-based exemptions are policy heuristics, not proof that a value is safely redacted."],
9490
9490
  examples: [
9491
9491
  { id: "redacted-secret", title: "Log non-sensitive context instead of the secret", outcome: "no-match", files: [{ path: "src/auth.ts", source: "logger.info('auth', { requestId });" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
9492
9492
  { id: "logged-secret", title: "Do not send a secret to logs", outcome: "match", files: [{ path: "src/auth.ts", source: "logger.error('auth failed', { token });" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
@@ -9537,13 +9537,25 @@ var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
9537
9537
  "len",
9538
9538
  "length"
9539
9539
  ]);
9540
- var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
9541
- var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
9540
+ var SECRET_REDACTION_TOKENS = /* @__PURE__ */ new Set([
9541
+ "prefix",
9542
+ "suffix",
9543
+ "redact",
9544
+ "redacted",
9545
+ "mask",
9546
+ "masked",
9547
+ "hash",
9548
+ "hashed",
9549
+ "hint",
9550
+ "len",
9551
+ "length",
9552
+ "tag"
9553
+ ]);
9542
9554
  function isSecretKeyword(name) {
9543
9555
  return !hasRedactionMarker(name) && isSecretName(name, LOG_INNOCUOUS_WORDS);
9544
9556
  }
9545
9557
  function hasRedactionMarker(name) {
9546
- return REDACTION_RE.test(name) || tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok));
9558
+ return tokenize(name).some((token) => SECRET_REDACTION_TOKENS.has(token));
9547
9559
  }
9548
9560
  function valueName(node) {
9549
9561
  if (node.type === "Identifier") return node.name;
@@ -9563,8 +9575,27 @@ var RAW_BLOB_WORDS = /* @__PURE__ */ new Set([
9563
9575
  "params"
9564
9576
  ]);
9565
9577
  var RAW_BLOB_IDENTIFIERS = /* @__PURE__ */ new Set(["formdata"]);
9566
- var BLOB_REDACTION_RE = /redact|sanit|scrub|mask|truncat|anonym|filtered|preview|summar/i;
9567
- var BLOB_REDACTION_TOKENS = /* @__PURE__ */ new Set([
9578
+ var BLOB_TRANSFORM_TOKENS = /* @__PURE__ */ new Set([
9579
+ "redact",
9580
+ "redacted",
9581
+ "sanitize",
9582
+ "sanitized",
9583
+ "scrub",
9584
+ "scrubbed",
9585
+ "mask",
9586
+ "masked",
9587
+ "truncate",
9588
+ "truncated",
9589
+ "anonymize",
9590
+ "anonymized",
9591
+ "filter",
9592
+ "filtered",
9593
+ "preview",
9594
+ "summarize",
9595
+ "summarized",
9596
+ "summary"
9597
+ ]);
9598
+ var BLOB_SAFE_TOKENS = /* @__PURE__ */ new Set([
9568
9599
  "safe",
9569
9600
  "clean",
9570
9601
  "shape",
@@ -9586,11 +9617,8 @@ function rawBlobValueName(value) {
9586
9617
  return null;
9587
9618
  }
9588
9619
  function isRawBlobName(name) {
9589
- if (REDACTION_RE.test(name) || BLOB_REDACTION_RE.test(name)) {
9590
- return false;
9591
- }
9592
9620
  const tokens = tokenize(name);
9593
- if (tokens.some((tok) => BLOB_REDACTION_TOKENS.has(tok))) {
9621
+ if (tokens.some((tok) => SECRET_REDACTION_TOKENS.has(tok) || BLOB_TRANSFORM_TOKENS.has(tok) || BLOB_SAFE_TOKENS.has(tok))) {
9594
9622
  return false;
9595
9623
  }
9596
9624
  const first = leadingWord(name);
@@ -9660,12 +9688,62 @@ var no_secret_in_log_default = createRule({
9660
9688
  }
9661
9689
  function reportRawBlob(node, value) {
9662
9690
  if (!blobArmApplies) {
9663
- return;
9691
+ return false;
9664
9692
  }
9665
9693
  const name = rawBlobValueName(value);
9666
9694
  if (name !== null) {
9667
9695
  context.report({ node, messageId: "noRawBodyInLog", data: { name } });
9696
+ return true;
9668
9697
  }
9698
+ return false;
9699
+ }
9700
+ function literalProperties(value) {
9701
+ const effective = /* @__PURE__ */ new Map();
9702
+ for (const entry of value.properties) {
9703
+ if (entry.type === "SpreadElement") {
9704
+ if (entry.argument.type !== "ObjectExpression") {
9705
+ effective.clear();
9706
+ continue;
9707
+ }
9708
+ for (const property of literalProperties(entry.argument)) {
9709
+ const key2 = propertyKeyName2(property);
9710
+ if (key2 !== null) effective.set(key2, property);
9711
+ }
9712
+ continue;
9713
+ }
9714
+ const key = propertyKeyName2(entry);
9715
+ if (key === null) {
9716
+ effective.clear();
9717
+ continue;
9718
+ }
9719
+ effective.set(key, entry);
9720
+ }
9721
+ return [...effective.values()];
9722
+ }
9723
+ function inspectLoggedValue(value) {
9724
+ if (value.type === "ObjectExpression") {
9725
+ for (const property of literalProperties(value)) {
9726
+ if (reportSecretProperty(property) || reportRawBlob(property, property.value)) continue;
9727
+ inspectLoggedValue(property.value);
9728
+ }
9729
+ return;
9730
+ }
9731
+ if (value.type === "ArrayExpression") {
9732
+ for (const element of value.elements) {
9733
+ if (element !== null && element.type !== "SpreadElement") inspectLoggedValue(element);
9734
+ }
9735
+ return;
9736
+ }
9737
+ if (value.type === "TemplateLiteral") {
9738
+ for (const expression of value.expressions) inspectLoggedValue(expression);
9739
+ return;
9740
+ }
9741
+ if (value.type === "BinaryExpression" && value.operator === "+") {
9742
+ inspectLoggedValue(value.left);
9743
+ inspectLoggedValue(value.right);
9744
+ return;
9745
+ }
9746
+ if (!reportSecretArgument(value)) reportRawBlob(value, value);
9669
9747
  }
9670
9748
  return {
9671
9749
  CallExpression(node) {
@@ -9673,20 +9751,7 @@ var no_secret_in_log_default = createRule({
9673
9751
  return;
9674
9752
  }
9675
9753
  for (const arg of node.arguments) {
9676
- if (arg.type === "ObjectExpression") {
9677
- for (const prop of arg.properties) {
9678
- if (prop.type !== "Property") {
9679
- continue;
9680
- }
9681
- if (!reportSecretProperty(prop)) {
9682
- reportRawBlob(prop, prop.value);
9683
- }
9684
- }
9685
- continue;
9686
- }
9687
- if (!reportSecretArgument(arg)) {
9688
- reportRawBlob(arg, arg);
9689
- }
9754
+ if (arg.type !== "SpreadElement") inspectLoggedValue(arg);
9690
9755
  }
9691
9756
  }
9692
9757
  };
@@ -23162,7 +23227,7 @@ var RULES = {
23162
23227
  };
23163
23228
  var meta = {
23164
23229
  name: "@sarj/eslint-plugin",
23165
- version: "15.26.7"
23230
+ version: "15.26.8"
23166
23231
  };
23167
23232
  var APPLICATION_ONLY_RULES = [];
23168
23233
  var LIBRARY_IMPORT_POLICY = ["error", {
package/dist/index.d.cts CHANGED
@@ -996,7 +996,7 @@ type FlatPreset = {
996
996
  declare const PLUGIN: {
997
997
  readonly meta: {
998
998
  readonly name: "@sarj/eslint-plugin";
999
- readonly version: "15.26.7";
999
+ readonly version: "15.26.8";
1000
1000
  };
1001
1001
  readonly rules: {
1002
1002
  readonly "no-conditional-empty-object-spread": DocumentedRule<[], "avoid">;
package/dist/index.d.ts CHANGED
@@ -996,7 +996,7 @@ type FlatPreset = {
996
996
  declare const PLUGIN: {
997
997
  readonly meta: {
998
998
  readonly name: "@sarj/eslint-plugin";
999
- readonly version: "15.26.7";
999
+ readonly version: "15.26.8";
1000
1000
  };
1001
1001
  readonly rules: {
1002
1002
  readonly "no-conditional-empty-object-spread": DocumentedRule<[], "avoid">;
package/dist/index.js CHANGED
@@ -9468,7 +9468,7 @@ var NO_SECRET_IN_LOG_DOCUMENTATION = {
9468
9468
  rationale: "Logs are widely retained and distributed, so credentials and raw bodies can become durable data leaks.",
9469
9469
  remediation: "Omit the value, log allowlisted non-sensitive context, or use an approved redactor; truncation alone is not a safety guarantee.",
9470
9470
  category: "security",
9471
- limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers. Name-based exemptions are policy heuristics, not proof that a value is safely redacted."],
9471
+ limitations: ["Detection uses configurable logger names and statically recognizable secret names, raw-body names, and redaction markers. Literal containers and template/string concatenation expressions are inspected; unknown spreads, computed properties, aliases, and opaque calls are not followed. Name-based exemptions are policy heuristics, not proof that a value is safely redacted."],
9472
9472
  examples: [
9473
9473
  { id: "redacted-secret", title: "Log non-sensitive context instead of the secret", outcome: "no-match", files: [{ path: "src/auth.ts", source: "logger.info('auth', { requestId });" }], focusPath: "src/auth.ts", expectedCount: 0, public: true },
9474
9474
  { id: "logged-secret", title: "Do not send a secret to logs", outcome: "match", files: [{ path: "src/auth.ts", source: "logger.error('auth failed', { token });" }], focusPath: "src/auth.ts", expectedCount: 1, public: true }
@@ -9519,13 +9519,25 @@ var LOG_INNOCUOUS_WORDS = /* @__PURE__ */ new Set([
9519
9519
  "len",
9520
9520
  "length"
9521
9521
  ]);
9522
- var REDACTION_RE = /prefix|suffix|redact|mask|hash|hint|_len|length/i;
9523
- var WHOLE_TOKEN_REDACTION_MARKERS = /* @__PURE__ */ new Set(["tag"]);
9522
+ var SECRET_REDACTION_TOKENS = /* @__PURE__ */ new Set([
9523
+ "prefix",
9524
+ "suffix",
9525
+ "redact",
9526
+ "redacted",
9527
+ "mask",
9528
+ "masked",
9529
+ "hash",
9530
+ "hashed",
9531
+ "hint",
9532
+ "len",
9533
+ "length",
9534
+ "tag"
9535
+ ]);
9524
9536
  function isSecretKeyword(name) {
9525
9537
  return !hasRedactionMarker(name) && isSecretName(name, LOG_INNOCUOUS_WORDS);
9526
9538
  }
9527
9539
  function hasRedactionMarker(name) {
9528
- return REDACTION_RE.test(name) || tokenize(name).some((tok) => WHOLE_TOKEN_REDACTION_MARKERS.has(tok));
9540
+ return tokenize(name).some((token) => SECRET_REDACTION_TOKENS.has(token));
9529
9541
  }
9530
9542
  function valueName(node) {
9531
9543
  if (node.type === "Identifier") return node.name;
@@ -9545,8 +9557,27 @@ var RAW_BLOB_WORDS = /* @__PURE__ */ new Set([
9545
9557
  "params"
9546
9558
  ]);
9547
9559
  var RAW_BLOB_IDENTIFIERS = /* @__PURE__ */ new Set(["formdata"]);
9548
- var BLOB_REDACTION_RE = /redact|sanit|scrub|mask|truncat|anonym|filtered|preview|summar/i;
9549
- var BLOB_REDACTION_TOKENS = /* @__PURE__ */ new Set([
9560
+ var BLOB_TRANSFORM_TOKENS = /* @__PURE__ */ new Set([
9561
+ "redact",
9562
+ "redacted",
9563
+ "sanitize",
9564
+ "sanitized",
9565
+ "scrub",
9566
+ "scrubbed",
9567
+ "mask",
9568
+ "masked",
9569
+ "truncate",
9570
+ "truncated",
9571
+ "anonymize",
9572
+ "anonymized",
9573
+ "filter",
9574
+ "filtered",
9575
+ "preview",
9576
+ "summarize",
9577
+ "summarized",
9578
+ "summary"
9579
+ ]);
9580
+ var BLOB_SAFE_TOKENS = /* @__PURE__ */ new Set([
9550
9581
  "safe",
9551
9582
  "clean",
9552
9583
  "shape",
@@ -9568,11 +9599,8 @@ function rawBlobValueName(value) {
9568
9599
  return null;
9569
9600
  }
9570
9601
  function isRawBlobName(name) {
9571
- if (REDACTION_RE.test(name) || BLOB_REDACTION_RE.test(name)) {
9572
- return false;
9573
- }
9574
9602
  const tokens = tokenize(name);
9575
- if (tokens.some((tok) => BLOB_REDACTION_TOKENS.has(tok))) {
9603
+ if (tokens.some((tok) => SECRET_REDACTION_TOKENS.has(tok) || BLOB_TRANSFORM_TOKENS.has(tok) || BLOB_SAFE_TOKENS.has(tok))) {
9576
9604
  return false;
9577
9605
  }
9578
9606
  const first = leadingWord(name);
@@ -9642,12 +9670,62 @@ var no_secret_in_log_default = createRule({
9642
9670
  }
9643
9671
  function reportRawBlob(node, value) {
9644
9672
  if (!blobArmApplies) {
9645
- return;
9673
+ return false;
9646
9674
  }
9647
9675
  const name = rawBlobValueName(value);
9648
9676
  if (name !== null) {
9649
9677
  context.report({ node, messageId: "noRawBodyInLog", data: { name } });
9678
+ return true;
9650
9679
  }
9680
+ return false;
9681
+ }
9682
+ function literalProperties(value) {
9683
+ const effective = /* @__PURE__ */ new Map();
9684
+ for (const entry of value.properties) {
9685
+ if (entry.type === "SpreadElement") {
9686
+ if (entry.argument.type !== "ObjectExpression") {
9687
+ effective.clear();
9688
+ continue;
9689
+ }
9690
+ for (const property of literalProperties(entry.argument)) {
9691
+ const key2 = propertyKeyName2(property);
9692
+ if (key2 !== null) effective.set(key2, property);
9693
+ }
9694
+ continue;
9695
+ }
9696
+ const key = propertyKeyName2(entry);
9697
+ if (key === null) {
9698
+ effective.clear();
9699
+ continue;
9700
+ }
9701
+ effective.set(key, entry);
9702
+ }
9703
+ return [...effective.values()];
9704
+ }
9705
+ function inspectLoggedValue(value) {
9706
+ if (value.type === "ObjectExpression") {
9707
+ for (const property of literalProperties(value)) {
9708
+ if (reportSecretProperty(property) || reportRawBlob(property, property.value)) continue;
9709
+ inspectLoggedValue(property.value);
9710
+ }
9711
+ return;
9712
+ }
9713
+ if (value.type === "ArrayExpression") {
9714
+ for (const element of value.elements) {
9715
+ if (element !== null && element.type !== "SpreadElement") inspectLoggedValue(element);
9716
+ }
9717
+ return;
9718
+ }
9719
+ if (value.type === "TemplateLiteral") {
9720
+ for (const expression of value.expressions) inspectLoggedValue(expression);
9721
+ return;
9722
+ }
9723
+ if (value.type === "BinaryExpression" && value.operator === "+") {
9724
+ inspectLoggedValue(value.left);
9725
+ inspectLoggedValue(value.right);
9726
+ return;
9727
+ }
9728
+ if (!reportSecretArgument(value)) reportRawBlob(value, value);
9651
9729
  }
9652
9730
  return {
9653
9731
  CallExpression(node) {
@@ -9655,20 +9733,7 @@ var no_secret_in_log_default = createRule({
9655
9733
  return;
9656
9734
  }
9657
9735
  for (const arg of node.arguments) {
9658
- if (arg.type === "ObjectExpression") {
9659
- for (const prop of arg.properties) {
9660
- if (prop.type !== "Property") {
9661
- continue;
9662
- }
9663
- if (!reportSecretProperty(prop)) {
9664
- reportRawBlob(prop, prop.value);
9665
- }
9666
- }
9667
- continue;
9668
- }
9669
- if (!reportSecretArgument(arg)) {
9670
- reportRawBlob(arg, arg);
9671
- }
9736
+ if (arg.type !== "SpreadElement") inspectLoggedValue(arg);
9672
9737
  }
9673
9738
  }
9674
9739
  };
@@ -23183,7 +23248,7 @@ var RULES = {
23183
23248
  };
23184
23249
  var meta = {
23185
23250
  name: "@sarj/eslint-plugin",
23186
- version: "15.26.7"
23251
+ version: "15.26.8"
23187
23252
  };
23188
23253
  var APPLICATION_ONLY_RULES = [];
23189
23254
  var LIBRARY_IMPORT_POLICY = ["error", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarj/eslint-plugin",
3
- "version": "15.26.7",
3
+ "version": "15.26.8",
4
4
  "packageManager": "npm@12.1.0",
5
5
  "description": "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
6
6
  "type": "module",