@sarj/eslint-plugin 16.12.0 → 16.13.0

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
@@ -52,7 +52,7 @@ module.exports = __toCommonJS(src_exports);
52
52
  // package.json
53
53
  var package_default = {
54
54
  name: "@sarj/eslint-plugin",
55
- version: "16.12.0",
55
+ version: "16.13.0",
56
56
  packageManager: "npm@12.2.0",
57
57
  description: "Custom ESLint rules for hypermodern TypeScript / React / Next.js projects",
58
58
  type: "module",
@@ -4794,254 +4794,291 @@ var no_comment_cruft_default = createRule({
4794
4794
 
4795
4795
  // src/rules/no-cors-wildcard-with-credentials.ts
4796
4796
  var import_utils28 = require("@typescript-eslint/utils");
4797
+ var ACAO_HEADER = "access-control-allow-origin";
4798
+ var ACAC_HEADER = "access-control-allow-credentials";
4799
+ var HEADER_METHODS = /* @__PURE__ */ new Set(["set", "setHeader", "append", "delete", "removeHeader"]);
4800
+ var CONTROL_STATEMENTS = /* @__PURE__ */ new Set([
4801
+ "BlockStatement",
4802
+ "IfStatement",
4803
+ "SwitchStatement",
4804
+ "TryStatement",
4805
+ "ForStatement",
4806
+ "ForInStatement",
4807
+ "ForOfStatement",
4808
+ "WhileStatement",
4809
+ "DoWhileStatement",
4810
+ "WithStatement",
4811
+ "LabeledStatement"
4812
+ ]);
4797
4813
  var NO_CORS_WILDCARD_WITH_CREDENTIALS_DOCUMENTATION = {
4798
- summary: "Disallow wildcard CORS origins when credentials are enabled.",
4799
- rationale: "Reflecting every origin while allowing credentials can let an untrusted site read authenticated cross-origin responses.",
4800
- remediation: "Enumerate the trusted origins that may receive credentialed responses.",
4801
- category: "security",
4814
+ summary: "Review wildcard CORS origins combined with an explicit credentials opt-in.",
4815
+ rationale: "Browsers do not expose credentialed cross-origin responses when Access-Control-Allow-Origin is '*'. Credentialless requests can still use a wildcard even when Access-Control-Allow-Credentials is emitted, so review the intended caller contract.",
4816
+ remediation: "Use a trusted explicit origin for credentialed callers, or omit the credentials opt-in for a credentialless wildcard endpoint.",
4817
+ category: "correctness",
4818
+ defaultLevel: "warning",
4819
+ autofix: "none",
4820
+ references: ["https://fetch.spec.whatwg.org/#cors-protocol-and-credentials"],
4802
4821
  limitations: [
4803
- "The rule detects literal CORS option and header combinations within the same syntactic scope; it does not resolve runtime configuration."
4822
+ "Options require a stable official cors default import or hono/cors named or namespace import, one literal object argument, literal origin '*', and credentials true. Custom bindings, callbacks, arrays, conditional origins and dynamic options are excluded.",
4823
+ "Literal header maps use effective case-sensitive JavaScript property writes followed by native Headers combination of differently-cased header names. Unknown values/spreads/computed keys, accessors and non-null prototypes lose proof; literals used only inside object spreads are not reported independently.",
4824
+ "Manual set/setHeader/delete/removeHeader calls use direct identifier receivers and literal arguments in straight-line program or function bodies. Control-flow bodies, member receivers, aliases and unknown operations are excluded. Append requires a proven empty native Headers constructor or a preceding known write/removal.",
4825
+ "The rule reviews a local configuration contract, not actual request credentials or a data disclosure. Runtime patches, custom header method implementations, interprocedural mutation and later consumers are not resolved. No autofix."
4804
4826
  ],
4805
4827
  examples: [
4806
4828
  {
4807
4829
  id: "trusted-origin-with-credentials",
4808
- title: "Credentials are limited to a trusted origin",
4830
+ title: "Use a trusted origin for credentialed callers",
4809
4831
  outcome: "no-match",
4810
- files: [{ path: "src/server.ts", source: "app.use(cors({ origin: 'https://app.example.com', credentials: true }));" }],
4832
+ files: [{ path: "src/server.ts", source: "import cors from 'cors'; cors({ origin: 'https://app.example.com', credentials: true });" }],
4811
4833
  focusPath: "src/server.ts",
4812
4834
  expectedCount: 0,
4813
4835
  public: true
4814
4836
  },
4815
4837
  {
4816
4838
  id: "wildcard-origin-with-credentials",
4817
- title: "Credentials are enabled for every origin",
4839
+ title: "Review a wildcard that blocks access to credentialed responses",
4818
4840
  outcome: "match",
4819
- files: [{ path: "src/server.ts", source: "app.use(cors({ origin: '*', credentials: true }));" }],
4841
+ files: [{ path: "src/server.ts", source: "import cors from 'cors'; cors({ origin: '*', credentials: true });" }],
4820
4842
  focusPath: "src/server.ts",
4821
4843
  expectedCount: 1,
4822
4844
  public: true
4823
4845
  }
4824
4846
  ]
4825
4847
  };
4826
- var ACAO_HEADER = "access-control-allow-origin";
4827
- var ACAC_HEADER = "access-control-allow-credentials";
4828
- var HEADER_SET_METHODS = /* @__PURE__ */ new Set(["setheader", "set", "append"]);
4829
- function isCredentialsTrueValue(node) {
4830
- node = unwrapExpression(node);
4831
- if (node.type === "Literal") {
4832
- if (node.value === true) {
4833
- return true;
4834
- }
4835
- if (typeof node.value === "string") {
4836
- return node.value.trim().toLowerCase() === "true";
4837
- }
4838
- }
4839
- return false;
4840
- }
4841
- function isStarLiteral(node) {
4842
- return node.type === "Literal" && node.value === "*";
4843
- }
4844
- function propertyKeyName(prop) {
4845
- return import_utils28.ASTUtils.getPropertyName(prop) ?? void 0;
4846
- }
4847
- function isCorsWildcardCredentialsCall(node) {
4848
- const name = calleeName(node);
4849
- if (name === void 0 || name.toLowerCase() !== "cors") {
4850
- return false;
4851
- }
4852
- const options = node.arguments.map(unwrapExpression).find(
4853
- (arg) => arg.type === "ObjectExpression"
4854
- );
4855
- if (options === void 0) {
4856
- return false;
4857
- }
4858
- let hasCredentials = false;
4859
- let hasWildcardOrigin = false;
4860
- for (const prop of options.properties) {
4861
- if (prop.type !== "Property") {
4862
- continue;
4863
- }
4864
- const key = propertyKeyName(prop);
4865
- if (key === "credentials" && isTrueLiteral(prop.value)) {
4866
- hasCredentials = true;
4867
- } else if (key === "origin" && subtreeContainsStarLiteral(prop.value)) {
4868
- hasWildcardOrigin = true;
4869
- }
4870
- }
4871
- return hasCredentials && hasWildcardOrigin;
4872
- }
4873
- function isTrueLiteral(node) {
4874
- node = unwrapExpression(node);
4875
- return node.type === "Literal" && node.value === true;
4876
- }
4877
- function subtreeContainsStarLiteral(node) {
4878
- if (isStarLiteral(node)) {
4879
- return true;
4880
- }
4881
- return forEachOwnAstChild(
4882
- node,
4883
- subtreeContainsStarLiteral,
4884
- (key) => key !== "loc" && key !== "range"
4885
- );
4886
- }
4887
- function calleeName(node) {
4888
- const callee = unwrapExpression(node.callee);
4889
- if (callee.type === "Identifier") {
4890
- return callee.name;
4891
- }
4892
- if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier") {
4893
- return callee.property.name;
4894
- }
4895
- return void 0;
4896
- }
4897
- function isWildcardCredentialsHeaderObject(node) {
4898
- let wildcardOrigin = false;
4899
- let credentialsTrue = false;
4900
- for (const prop of node.properties) {
4901
- if (prop.type !== "Property") {
4902
- continue;
4903
- }
4904
- const key = propertyKeyName(prop);
4905
- if (key === void 0) {
4906
- continue;
4907
- }
4908
- const header = key.toLowerCase();
4909
- if (header === ACAO_HEADER && isStarLiteral(prop.value)) {
4910
- wildcardOrigin = true;
4911
- } else if (header === ACAC_HEADER && isCredentialsTrueValue(prop.value)) {
4912
- credentialsTrue = true;
4913
- }
4914
- }
4915
- return wildcardOrigin && credentialsTrue;
4916
- }
4917
- function classifyHeaderSetCall(node) {
4918
- const callee = unwrapExpression(node.callee);
4919
- if (callee.type !== "MemberExpression" || callee.computed || callee.property.type !== "Identifier" || !HEADER_SET_METHODS.has(callee.property.name.toLowerCase())) {
4920
- return void 0;
4921
- }
4922
- const [nameArg, valueArg] = node.arguments;
4923
- if (nameArg === void 0 || valueArg === void 0 || nameArg.type !== "Literal" || typeof nameArg.value !== "string") {
4924
- return void 0;
4925
- }
4926
- const header = nameArg.value.toLowerCase();
4927
- if (header === ACAO_HEADER && isStarLiteral(valueArg)) {
4928
- return "origin";
4929
- }
4930
- if (header === ACAC_HEADER && isCredentialsTrueValue(valueArg)) {
4931
- return "credentials";
4932
- }
4933
- return void 0;
4934
- }
4935
- function enclosingScope(node) {
4936
- let current = node.parent;
4937
- while (current) {
4938
- if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
4939
- return current;
4940
- }
4941
- current = current.parent;
4942
- }
4943
- return void 0;
4944
- }
4945
4848
  var no_cors_wildcard_with_credentials_default = createRule({
4946
4849
  name: "no-cors-wildcard-with-credentials",
4947
4850
  documentation: NO_CORS_WILDCARD_WITH_CREDENTIALS_DOCUMENTATION,
4948
4851
  meta: {
4949
4852
  type: "problem",
4950
- docs: {
4951
- description: "Disallow wildcard CORS origins when credentials are enabled."
4952
- },
4853
+ docs: { description: NO_CORS_WILDCARD_WITH_CREDENTIALS_DOCUMENTATION.summary },
4953
4854
  schema: [],
4954
4855
  messages: {
4955
- corsWildcardWithCredentials: 'CORS reflects any Origin (`"*"`) while allowing credentials \u2014 any site can read authenticated responses. Enumerate explicit trusted origins instead of using `"*"` with credentials.'
4856
+ corsWildcardWithCredentials: "A wildcard CORS origin blocks browser access to credentialed responses. Review the intended contract: use a trusted explicit origin for credentialed callers, or omit the credentials opt-in for a credentialless endpoint."
4956
4857
  }
4957
4858
  },
4958
4859
  defaultOptions: [],
4959
4860
  create(context) {
4960
- const scopeHeaderSets = /* @__PURE__ */ new Map();
4961
- const variableIds = /* @__PURE__ */ new WeakMap();
4962
- let nextVariableId = 0;
4963
- function recordHeaderSet(node, kind) {
4964
- const unwrappedNodeCallee = unwrapExpression(node.callee);
4965
- if (unwrappedNodeCallee.type !== import_utils28.AST_NODE_TYPES.MemberExpression) return;
4966
- const receiver = receiverIdentity(unwrappedNodeCallee.object);
4967
- if (receiver === null) return;
4968
- const key = enclosingScope(node) ?? "module";
4969
- let receivers = scopeHeaderSets.get(key);
4970
- if (receivers === void 0) {
4971
- receivers = /* @__PURE__ */ new Map();
4972
- scopeHeaderSets.set(key, receivers);
4973
- }
4974
- let entry = receivers.get(receiver);
4975
- if (entry === void 0) {
4976
- entry = { originNodes: [], credentialsNodes: [] };
4977
- receivers.set(receiver, entry);
4978
- }
4979
- if (kind === "origin") {
4980
- entry.originNodes.push(node);
4981
- } else {
4982
- entry.credentialsNodes.push(node);
4861
+ const sourceCode = context.sourceCode;
4862
+ const importedCors = /* @__PURE__ */ new Set();
4863
+ const importedNamespaces = /* @__PURE__ */ new Set();
4864
+ const receiverProof = /* @__PURE__ */ new Map();
4865
+ function binding(node) {
4866
+ return import_utils28.ASTUtils.findVariable(sourceCode.getScope(node), node.name);
4867
+ }
4868
+ function officialCors(node) {
4869
+ const value = unwrapExpression(node);
4870
+ if (value.type === "Identifier") {
4871
+ const variable2 = binding(value);
4872
+ return variable2 !== null && importedCors.has(variable2);
4983
4873
  }
4874
+ if (value.type !== "MemberExpression" || value.optional || value.computed || value.property.type !== "Identifier" || value.property.name !== "cors" || value.object.type !== "Identifier") return false;
4875
+ const variable = binding(value.object);
4876
+ return variable !== null && importedNamespaces.has(variable);
4984
4877
  }
4985
- function receiverIdentity(node) {
4986
- if (node.type === import_utils28.AST_NODE_TYPES.Identifier) {
4987
- const variable = import_utils28.ASTUtils.findVariable(
4988
- context.sourceCode.getScope(node),
4989
- node.name
4990
- );
4991
- return variable === null ? `global:${node.name}` : `variable:${variableId(variable)}`;
4878
+ function recordImports(node) {
4879
+ for (const statement of node.body) {
4880
+ if (statement.type === "ImportDeclaration" && statement.importKind !== "type") recordImport(statement);
4881
+ }
4882
+ }
4883
+ function recordImport(node) {
4884
+ for (const specifier of node.specifiers) {
4885
+ const role = importRole(specifier, node.source.value);
4886
+ if (role === null) continue;
4887
+ const variable = binding(specifier.local);
4888
+ if (variable === null || variable.references.some((reference) => reference.isWrite())) continue;
4889
+ (role === "namespace" ? importedNamespaces : importedCors).add(variable);
4890
+ }
4891
+ }
4892
+ function inspectSequence(node) {
4893
+ if (node.body.some((statement) => CONTROL_STATEMENTS.has(statement.type))) return;
4894
+ const states = /* @__PURE__ */ new Map();
4895
+ for (const statement of node.body) {
4896
+ if (statement.type === "ImportDeclaration" || statement.type === "FunctionDeclaration") continue;
4897
+ if (statement.type === "VariableDeclaration") {
4898
+ recordHeaderDeclaration(statement, states);
4899
+ continue;
4900
+ }
4901
+ const expression = statement.type === "ExpressionStatement" ? unwrapExpression(statement.expression) : null;
4902
+ if (expression?.type !== "CallExpression" || !applyHeaderWrite(expression, states)) states.clear();
4992
4903
  }
4993
- if (node.type === import_utils28.AST_NODE_TYPES.ThisExpression) return "this";
4994
- if (node.type === import_utils28.AST_NODE_TYPES.MemberExpression && !node.computed && node.property.type === import_utils28.AST_NODE_TYPES.Identifier) {
4995
- const owner = receiverIdentity(node.object);
4996
- return owner === null ? null : `${owner}.${node.property.name}`;
4904
+ for (const state of states.values()) {
4905
+ if (state.values[ACAO_HEADER] === "*" && state.values[ACAC_HEADER] === "true" && state.origin !== null) {
4906
+ context.report({ node: state.origin, messageId: "corsWildcardWithCredentials" });
4907
+ }
4997
4908
  }
4998
- return null;
4999
4909
  }
5000
- function variableId(variable) {
5001
- const existing = variableIds.get(variable);
5002
- if (existing !== void 0) return existing;
5003
- const value = nextVariableId++;
5004
- variableIds.set(variable, value);
5005
- return value;
4910
+ function recordHeaderDeclaration(node, states) {
4911
+ for (const declaration of node.declarations) {
4912
+ if (declaration.id.type === "Identifier" && declaration.init !== null && freshHeaders(declaration.init)) {
4913
+ const key = receiverKey(declaration.id);
4914
+ if (key !== null) states.set(key, { values: { [ACAO_HEADER]: null, [ACAC_HEADER]: null }, origin: null });
4915
+ } else if (declaration.id.type !== "Identifier" || declaration.init !== null && unwrapExpression(declaration.init).type !== "Literal") states.clear();
4916
+ }
4917
+ }
4918
+ function freshHeaders(node) {
4919
+ const value = unwrapExpression(node);
4920
+ if (value.type !== "NewExpression" || value.arguments.length !== 0) return false;
4921
+ const callee = unwrapExpression(value.callee);
4922
+ return callee.type === "Identifier" && callee.name === "Headers" && !binding(callee)?.defs.length;
4923
+ }
4924
+ function applyHeaderWrite(node, states) {
4925
+ const callee = unwrapExpression(node.callee);
4926
+ if (node.optional || callee.type !== "MemberExpression" || callee.optional || callee.computed || callee.property.type !== "Identifier" || !HEADER_METHODS.has(callee.property.name) || callee.object.type !== "Identifier") return false;
4927
+ const method = callee.property.name;
4928
+ const removal = method === "delete" || method === "removeHeader";
4929
+ if (node.arguments.length !== (removal ? 1 : 2)) return false;
4930
+ const name = literalString(node.arguments[0]);
4931
+ const value = removal ? null : headerValue(node.arguments[1]);
4932
+ if (name === null || !removal && value === null) return false;
4933
+ const key = receiverKey(callee.object);
4934
+ if (key === null) return false;
4935
+ let state = states.get(key);
4936
+ if (state === void 0) {
4937
+ state = { values: { [ACAO_HEADER]: void 0, [ACAC_HEADER]: void 0 }, origin: null };
4938
+ states.set(key, state);
4939
+ }
4940
+ const header = name.toLowerCase();
4941
+ if (header !== ACAO_HEADER && header !== ACAC_HEADER) return true;
4942
+ state.values[header] = method === "append" ? appendValue(state.values[header], value) : value;
4943
+ if (header === ACAO_HEADER) state.origin = node;
4944
+ return true;
4945
+ }
4946
+ function receiverKey(node) {
4947
+ const variable = binding(node);
4948
+ const key = variable ?? node.name;
4949
+ const cached = receiverProof.get(key);
4950
+ if (cached !== void 0) return cached ? key : null;
4951
+ const references = variable?.references ?? sourceCode.scopeManager?.globalScope?.through.filter((reference) => reference.identifier.name === node.name) ?? [];
4952
+ const unaliased = !variable?.defs.some(aliasDefinition) && references.every(headerReceiverReference);
4953
+ receiverProof.set(key, unaliased);
4954
+ return unaliased ? key : null;
5006
4955
  }
5007
4956
  return {
5008
- NewExpression(node) {
5009
- if (isCorsWildcardCredentialsCall(node)) {
5010
- context.report({ node, messageId: "corsWildcardWithCredentials" });
5011
- }
4957
+ Program(node) {
4958
+ recordImports(node);
4959
+ inspectSequence(node);
4960
+ },
4961
+ BlockStatement(node) {
4962
+ if (["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"].includes(node.parent.type)) inspectSequence(node);
5012
4963
  },
5013
4964
  CallExpression(node) {
5014
- if (isCorsWildcardCredentialsCall(node)) {
4965
+ if (node.optional || node.arguments.length !== 1 || !officialCors(node.callee)) return;
4966
+ const argument = node.arguments[0];
4967
+ const options = argument === void 0 ? void 0 : unwrapExpression(argument);
4968
+ if (options?.type !== "ObjectExpression") return;
4969
+ const properties = effectiveProperties(options);
4970
+ const origin = properties?.get("origin");
4971
+ const credentials = properties?.get("credentials");
4972
+ const value = credentials === void 0 ? void 0 : unwrapExpression(credentials.value);
4973
+ if (origin !== void 0 && literalString(origin.value) === "*" && value?.type === "Literal" && value.value === true) {
5015
4974
  context.report({ node, messageId: "corsWildcardWithCredentials" });
5016
- return;
5017
- }
5018
- const kind = classifyHeaderSetCall(node);
5019
- if (kind !== void 0) {
5020
- recordHeaderSet(node, kind);
5021
4975
  }
5022
4976
  },
5023
4977
  ObjectExpression(node) {
5024
- if (isWildcardCredentialsHeaderObject(node)) {
4978
+ if (spreadOnly(node)) return;
4979
+ const properties = effectiveProperties(node);
4980
+ if (properties === null) return;
4981
+ const headers = /* @__PURE__ */ new Map();
4982
+ for (const [key, property] of properties) {
4983
+ const value = headerValue(property.value);
4984
+ if (value === null) return;
4985
+ const name = key.toLowerCase();
4986
+ if (name !== ACAO_HEADER && name !== ACAC_HEADER) continue;
4987
+ const previous = headers.get(name);
4988
+ headers.set(name, previous === void 0 ? value : previous + ", " + value);
4989
+ }
4990
+ if (headers.get(ACAO_HEADER) === "*" && headers.get(ACAC_HEADER) === "true") {
5025
4991
  context.report({ node, messageId: "corsWildcardWithCredentials" });
5026
4992
  }
5027
- },
5028
- "Program:exit"() {
5029
- for (const receivers of scopeHeaderSets.values()) {
5030
- for (const { originNodes, credentialsNodes } of receivers.values()) {
5031
- if (originNodes.length > 0 && credentialsNodes.length > 0) {
5032
- for (const node of originNodes) {
5033
- context.report({
5034
- node,
5035
- messageId: "corsWildcardWithCredentials"
5036
- });
5037
- }
5038
- }
5039
- }
5040
- }
5041
4993
  }
5042
4994
  };
5043
4995
  }
5044
4996
  });
4997
+ function importRole(specifier, module2) {
4998
+ if (module2 === "hono/cors" && specifier.type === "ImportNamespaceSpecifier") return "namespace";
4999
+ if (module2 === "cors" && specifier.type === "ImportDefaultSpecifier") return "function";
5000
+ if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type") return null;
5001
+ const name = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
5002
+ return module2 === "cors" && name === "default" || module2 === "hono/cors" && name === "cors" ? "function" : null;
5003
+ }
5004
+ function aliasDefinition(definition) {
5005
+ if (definition.type !== "Variable") return false;
5006
+ if (definition.node.id.type !== "Identifier") return true;
5007
+ const initializer = definition.node.init;
5008
+ return initializer !== null && ["Identifier", "MemberExpression", "AssignmentExpression", "ConditionalExpression", "LogicalExpression", "SequenceExpression"].includes(unwrapExpression(initializer).type);
5009
+ }
5010
+ function headerReceiverReference(reference) {
5011
+ let typeName = reference.identifier;
5012
+ while (typeName.parent.type === "TSQualifiedName") typeName = typeName.parent;
5013
+ if (reference.isValueReference === false || typeName.parent.type === "TSTypeQuery") return true;
5014
+ if (reference.isWrite()) return reference.init === true;
5015
+ const member = reference.identifier.parent;
5016
+ if (member.type !== "MemberExpression" || member.object !== reference.identifier || member.computed || member.optional || member.property.type !== "Identifier" || !HEADER_METHODS.has(member.property.name)) return false;
5017
+ return member.parent.type === "CallExpression" && member.parent.callee === member && !member.parent.optional;
5018
+ }
5019
+ function appendValue(previous, value) {
5020
+ if (previous === void 0) return void 0;
5021
+ return previous === null ? value : previous + ", " + value;
5022
+ }
5023
+ function literalString(node) {
5024
+ const value = node === void 0 ? void 0 : unwrapExpression(node);
5025
+ return value?.type === "Literal" && typeof value.value === "string" ? value.value : null;
5026
+ }
5027
+ function headerValue(node) {
5028
+ const value = node === void 0 ? void 0 : unwrapExpression(node);
5029
+ if (value?.type !== "Literal" || !["string", "boolean", "number"].includes(typeof value.value)) return null;
5030
+ return String(value.value).replace(/^[\t\n\r ]+|[\t\n\r ]+$/g, "");
5031
+ }
5032
+ function effectiveProperties(node) {
5033
+ const properties = /* @__PURE__ */ new Map();
5034
+ for (const entry of node.properties) {
5035
+ const entries = propertyEntries(entry);
5036
+ if (entries === null) return null;
5037
+ for (const [key, property] of entries) properties.set(key, property);
5038
+ }
5039
+ return properties;
5040
+ }
5041
+ function propertyEntries(entry) {
5042
+ if (entry.type === "SpreadElement") {
5043
+ const value = unwrapExpression(entry.argument);
5044
+ return value.type === "ObjectExpression" ? effectiveProperties(value) : null;
5045
+ }
5046
+ if (entry.kind !== "init" || entry.method) return null;
5047
+ const key = entry.computed ? literalPropertyKey(entry.key) : import_utils28.ASTUtils.getPropertyName(entry);
5048
+ if (key === null) return null;
5049
+ if (key === "__proto__" && !entry.computed) {
5050
+ const value = unwrapExpression(entry.value);
5051
+ return value.type === "Literal" && value.value === null ? [] : null;
5052
+ }
5053
+ return [[key, entry]];
5054
+ }
5055
+ function literalPropertyKey(node) {
5056
+ const value = unwrapExpression(node);
5057
+ const literal = literalString(value);
5058
+ if (literal !== null) return literal;
5059
+ if (value.type !== "TemplateLiteral" || value.expressions.length !== 0) return null;
5060
+ return value.quasis[0]?.value.cooked ?? null;
5061
+ }
5062
+ function spreadOnly(node) {
5063
+ let current = outerExpression(node);
5064
+ while (current.parent !== void 0) {
5065
+ const parent = current.parent;
5066
+ if (parent.type === "SpreadElement") return parent.parent.type === "ObjectExpression";
5067
+ if (![
5068
+ "Property",
5069
+ "ObjectExpression",
5070
+ "ConditionalExpression",
5071
+ "LogicalExpression",
5072
+ "SequenceExpression",
5073
+ "TSAsExpression",
5074
+ "TSSatisfiesExpression",
5075
+ "TSNonNullExpression",
5076
+ "TSTypeAssertion"
5077
+ ].includes(parent.type)) return false;
5078
+ current = parent;
5079
+ }
5080
+ return false;
5081
+ }
5045
5082
 
5046
5083
  // src/rules/no-duplicate-lifecycle-refresh-listeners.ts
5047
5084
  var import_utils30 = require("@typescript-eslint/utils");
@@ -5300,7 +5337,7 @@ var NO_DANGEROUSLY_ALLOW_SVG_DOCUMENTATION = {
5300
5337
  }
5301
5338
  ]
5302
5339
  };
5303
- function literalString(node) {
5340
+ function literalString2(node) {
5304
5341
  if (node?.value.type === "Literal" && typeof node.value.value === "string") return node.value.value;
5305
5342
  if (node?.value.type === "TemplateLiteral" && node.value.expressions.length === 0) return node.value.quasis[0]?.value.cooked ?? null;
5306
5343
  return null;
@@ -5332,10 +5369,10 @@ var no_dangerously_allow_svg_default = createRule({
5332
5369
  const node = exportedNextConfigProperty(context.sourceCode, ["images", "dangerouslyAllowSVG"]);
5333
5370
  if (node !== null && node.value.type === "Literal" && node.value.value === true) {
5334
5371
  const disposition = exportedNextConfigProperty(context.sourceCode, ["images", "contentDispositionType"]);
5335
- const policy = literalString(
5372
+ const policy = literalString2(
5336
5373
  exportedNextConfigProperty(context.sourceCode, ["images", "contentSecurityPolicy"])
5337
5374
  );
5338
- const attachmentDisposition = disposition === null || literalString(disposition) === "attachment";
5375
+ const attachmentDisposition = disposition === null || literalString2(disposition) === "attachment";
5339
5376
  if (attachmentDisposition && policy !== null && hasHardenedSvgPolicy(policy)) return;
5340
5377
  context.report({ node, messageId: "noDangerouslyAllowSvg" });
5341
5378
  }
@@ -8721,7 +8758,7 @@ var LOGGING_OPTION_PROPERTIES = {
8721
8758
  loggerNames: { type: "array", items: { type: "string" } },
8722
8759
  logFunctions: { type: "array", items: { type: "string" } }
8723
8760
  };
8724
- function calleeName2(callee) {
8761
+ function calleeName(callee) {
8725
8762
  callee = unwrapExpression(callee);
8726
8763
  if (callee.type === "Identifier") {
8727
8764
  return callee.name;
@@ -8769,7 +8806,7 @@ function createLogMatcher(options = {}) {
8769
8806
  if (expr.type !== "CallExpression" || logFunctions.size === 0) {
8770
8807
  return false;
8771
8808
  }
8772
- const name = calleeName2(expr.callee);
8809
+ const name = calleeName(expr.callee);
8773
8810
  return name !== null && logFunctions.has(name);
8774
8811
  }
8775
8812
  function isLoggingCall(expr) {
@@ -11555,7 +11592,7 @@ function isRawBlobName(name) {
11555
11592
  const last = tokens.at(-1);
11556
11593
  return last !== void 0 && RAW_BLOB_WORDS.has(last);
11557
11594
  }
11558
- function propertyKeyName2(prop) {
11595
+ function propertyKeyName(prop) {
11559
11596
  if (prop.computed) {
11560
11597
  return null;
11561
11598
  }
@@ -11626,12 +11663,12 @@ var no_secret_in_log_default = createRule({
11626
11663
  continue;
11627
11664
  }
11628
11665
  for (const property of literalProperties(entry.argument)) {
11629
- const key2 = propertyKeyName2(property);
11666
+ const key2 = propertyKeyName(property);
11630
11667
  if (key2 !== null) effective.set(key2, property);
11631
11668
  }
11632
11669
  continue;
11633
11670
  }
11634
- const key = propertyKeyName2(entry);
11671
+ const key = propertyKeyName(entry);
11635
11672
  if (key === null) {
11636
11673
  effective.clear();
11637
11674
  continue;
@@ -11652,7 +11689,7 @@ var no_secret_in_log_default = createRule({
11652
11689
  return false;
11653
11690
  }
11654
11691
  function reportSecretProperty(prop) {
11655
- const keyName = propertyKeyName2(prop);
11692
+ const keyName = propertyKeyName(prop);
11656
11693
  const value = valueName(prop.value);
11657
11694
  if (value !== null && hasRedactionMarker(value)) return false;
11658
11695
  const name = value !== null && isSecretKeyword(value) ? value : keyName;
@@ -12220,7 +12257,7 @@ var no_sentinel_return_on_catch_default = createRule({
12220
12257
  if (matcher.isLoggingCall(current)) {
12221
12258
  return caughtName === null || argsIncludeBinding(current.arguments, caughtName);
12222
12259
  }
12223
- const name = calleeName2(current.callee);
12260
+ const name = calleeName(current.callee);
12224
12261
  return name !== null && REPORT_NAME_RE.test(name) && (caughtName === null || argsIncludeBinding(current.arguments, caughtName));
12225
12262
  });
12226
12263
  }
@@ -25312,6 +25349,7 @@ var LIBRARY_IMPORT_POLICY = ["error", {
25312
25349
  var ADVISORY_RULES = [
25313
25350
  "@sarj/excessive-commentary",
25314
25351
  "@sarj/no-bespoke-api-case-conversion",
25352
+ "@sarj/no-cors-wildcard-with-credentials",
25315
25353
  "@sarj/no-exception-text-in-5xx-response",
25316
25354
  "@sarj/no-restated-comment",
25317
25355
  "@sarj/no-restated-jsdoc",
@@ -25349,7 +25387,7 @@ var RECOMMENDED_RULES = {
25349
25387
  "@sarj/enforce-file-structure": "error",
25350
25388
  "@sarj/no-client-side-data-fetching": "error",
25351
25389
  "@sarj/no-comment-cruft": "error",
25352
- "@sarj/no-cors-wildcard-with-credentials": "error",
25390
+ "@sarj/no-cors-wildcard-with-credentials": "warn",
25353
25391
  "@sarj/no-dynamic-sql": "error",
25354
25392
  "@sarj/no-fat-try-blocks": ["error", { max: 5 }],
25355
25393
  "@sarj/no-hand-rolled-sleep": "error",
@@ -25468,7 +25506,7 @@ var STRICT_RULES = {
25468
25506
  "@sarj/enforce-file-structure": "error",
25469
25507
  "@sarj/no-client-side-data-fetching": "error",
25470
25508
  "@sarj/no-comment-cruft": "error",
25471
- "@sarj/no-cors-wildcard-with-credentials": "error",
25509
+ "@sarj/no-cors-wildcard-with-credentials": "warn",
25472
25510
  "@sarj/no-dynamic-sql": "error",
25473
25511
  "@sarj/no-enum": "error",
25474
25512
  "@sarj/no-fat-try-blocks": ["error", { max: 5 }],
package/dist/index.d.cts CHANGED
@@ -294,7 +294,7 @@ declare const RULES: {
294
294
  /** @deprecated All repositories use one policy; retained for import compatibility. */
295
295
  declare const APPLICATION_ONLY_RULES: readonly [];
296
296
  /** Rules staged as non-blocking warnings while corpus adoption evidence accumulates. */
297
- declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-exception-text-in-5xx-response", "@sarj/no-restated-comment", "@sarj/no-restated-jsdoc", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-whole-object-assertion", "@sarj/require-sql-access-class"];
297
+ declare const ADVISORY_RULES: readonly ["@sarj/excessive-commentary", "@sarj/no-bespoke-api-case-conversion", "@sarj/no-cors-wildcard-with-credentials", "@sarj/no-exception-text-in-5xx-response", "@sarj/no-restated-comment", "@sarj/no-restated-jsdoc", "@sarj/prefer-millisecond-control-duration-schema", "@sarj/prefer-module-level-refined-schema", "@sarj/prefer-node-crypto-hash", "@sarj/prefer-node-fs-promises", "@sarj/prefer-shared-zod-enum", "@sarj/prefer-whole-object-assertion", "@sarj/require-sql-access-class"];
298
298
  declare const RECOMMENDED_RULES: {
299
299
  readonly "@sarj/named-child-schema-type": "error";
300
300
  readonly "@sarj/no-conditional-empty-object-spread": "error";
@@ -510,7 +510,7 @@ declare const RECOMMENDED_RULES: {
510
510
  readonly "@sarj/enforce-file-structure": "error";
511
511
  readonly "@sarj/no-client-side-data-fetching": "error";
512
512
  readonly "@sarj/no-comment-cruft": "error";
513
- readonly "@sarj/no-cors-wildcard-with-credentials": "error";
513
+ readonly "@sarj/no-cors-wildcard-with-credentials": "warn";
514
514
  readonly "@sarj/no-dynamic-sql": "error";
515
515
  readonly "@sarj/no-fat-try-blocks": readonly ["error", {
516
516
  readonly max: 5;
@@ -824,7 +824,7 @@ declare const STRICT_RULES: {
824
824
  readonly "@sarj/enforce-file-structure": "error";
825
825
  readonly "@sarj/no-client-side-data-fetching": "error";
826
826
  readonly "@sarj/no-comment-cruft": "error";
827
- readonly "@sarj/no-cors-wildcard-with-credentials": "error";
827
+ readonly "@sarj/no-cors-wildcard-with-credentials": "warn";
828
828
  readonly "@sarj/no-dynamic-sql": "error";
829
829
  readonly "@sarj/no-enum": "error";
830
830
  readonly "@sarj/no-fat-try-blocks": readonly ["error", {