@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 +249 -211
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1991 -1956
- package/package.json +1 -1
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.
|
|
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: "
|
|
4799
|
-
rationale: "
|
|
4800
|
-
remediation: "
|
|
4801
|
-
category: "
|
|
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
|
-
"
|
|
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: "
|
|
4830
|
+
title: "Use a trusted origin for credentialed callers",
|
|
4809
4831
|
outcome: "no-match",
|
|
4810
|
-
files: [{ path: "src/server.ts", source: "
|
|
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: "
|
|
4839
|
+
title: "Review a wildcard that blocks access to credentialed responses",
|
|
4818
4840
|
outcome: "match",
|
|
4819
|
-
files: [{ path: "src/server.ts", source: "
|
|
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:
|
|
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
|
|
4961
|
-
const
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
const
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
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
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
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
|
-
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
|
|
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
|
|
5001
|
-
const
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
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
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
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 (
|
|
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 (
|
|
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
|
|
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 =
|
|
5372
|
+
const policy = literalString2(
|
|
5336
5373
|
exportedNextConfigProperty(context.sourceCode, ["images", "contentSecurityPolicy"])
|
|
5337
5374
|
);
|
|
5338
|
-
const attachmentDisposition = disposition === null ||
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
11666
|
+
const key2 = propertyKeyName(property);
|
|
11630
11667
|
if (key2 !== null) effective.set(key2, property);
|
|
11631
11668
|
}
|
|
11632
11669
|
continue;
|
|
11633
11670
|
}
|
|
11634
|
-
const key =
|
|
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 =
|
|
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 =
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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", {
|