eslint-plugin-secure-coding 2.3.4 → 2.4.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/CHANGELOG.md +51 -1
- package/README.md +3 -2
- package/package.json +2 -9
- package/src/index.d.ts +1 -1
- package/src/index.js +0 -49
- package/src/types/index.d.ts +4 -29
- package/src/types/index.js +3 -4
- package/src/rules/database-injection/index.d.ts +0 -13
- package/src/rules/database-injection/index.js +0 -406
- package/src/rules/no-credentials-in-storage-api/index.d.ts +0 -6
- package/src/rules/no-credentials-in-storage-api/index.js +0 -54
- package/src/rules/no-document-cookie/index.d.ts +0 -5
- package/src/rules/no-document-cookie/index.js +0 -89
- package/src/rules/no-insecure-cookie-settings/index.d.ts +0 -9
- package/src/rules/no-insecure-cookie-settings/index.js +0 -306
- package/src/rules/no-insecure-jwt/index.d.ts +0 -10
- package/src/rules/no-insecure-jwt/index.js +0 -380
- package/src/rules/no-insufficient-postmessage-validation/index.d.ts +0 -14
- package/src/rules/no-insufficient-postmessage-validation/index.js +0 -392
- package/src/rules/no-insufficient-random/index.d.ts +0 -9
- package/src/rules/no-insufficient-random/index.js +0 -208
- package/src/rules/no-postmessage-origin-wildcard/index.d.ts +0 -8
- package/src/rules/no-postmessage-origin-wildcard/index.js +0 -56
- package/src/rules/no-sql-injection/index.d.ts +0 -10
- package/src/rules/no-sql-injection/index.js +0 -335
- package/src/rules/no-timing-attack/index.d.ts +0 -10
- package/src/rules/no-timing-attack/index.js +0 -447
- package/src/rules/no-unencrypted-local-storage/index.d.ts +0 -8
- package/src/rules/no-unencrypted-local-storage/index.js +0 -61
- package/src/rules/no-unsanitized-html/index.d.ts +0 -9
- package/src/rules/no-unsanitized-html/index.js +0 -335
- package/src/rules/no-weak-crypto/index.d.ts +0 -11
- package/src/rules/no-weak-crypto/index.js +0 -351
|
@@ -1,306 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noInsecureCookieSettings = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
/**
|
|
6
|
-
* Check if a node is inside a cookie configuration
|
|
7
|
-
*/
|
|
8
|
-
function isInsideCookieConfig(node, sourceCode) {
|
|
9
|
-
let current = node;
|
|
10
|
-
// Traverse up the parent chain
|
|
11
|
-
while (current && 'parent' in current && current.parent) {
|
|
12
|
-
current = current.parent;
|
|
13
|
-
// Check for cookie-related method calls
|
|
14
|
-
if (current.type === 'CallExpression') {
|
|
15
|
-
const callExpr = current;
|
|
16
|
-
// Check for res.cookie() calls
|
|
17
|
-
if (callExpr.callee.type === 'MemberExpression') {
|
|
18
|
-
const memberExpr = callExpr.callee;
|
|
19
|
-
if (memberExpr.property.type === 'Identifier' && memberExpr.property.name === 'cookie') {
|
|
20
|
-
// Check if the node is an argument of this call
|
|
21
|
-
if (callExpr.arguments.some((arg) => arg === node || (arg.type === 'ObjectExpression' && sourceCode.getText(arg).includes(sourceCode.getText(node))))) {
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Check for other cookie-related calls using text matching
|
|
27
|
-
const callText = sourceCode.getText(current);
|
|
28
|
-
if (/\b(cookie|cookies|setCookie|res\.cookie|document\.cookie)\b/i.test(callText)) {
|
|
29
|
-
const callee = callExpr.callee;
|
|
30
|
-
// Specific check for cookies.set / cookie.set
|
|
31
|
-
if (callee.type === 'MemberExpression' &&
|
|
32
|
-
callee.property.type === 'Identifier' &&
|
|
33
|
-
callee.property.name === 'set') {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
// Check if node is part of this call
|
|
37
|
-
const nodeText = sourceCode.getText(node);
|
|
38
|
-
if (callText.includes(nodeText)) {
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Check if an object expression has secure cookie settings
|
|
48
|
-
*/
|
|
49
|
-
function hasSecureCookieSettings(node, sourceCode) {
|
|
50
|
-
const text = sourceCode.getText(node);
|
|
51
|
-
// Check for httpOnly flag (case-insensitive)
|
|
52
|
-
const hasHttpOnly = /\bhttpOnly\s*:\s*(true|'true'|"true")/i.test(text);
|
|
53
|
-
// Check for secure flag (case-insensitive)
|
|
54
|
-
const hasSecure = /\bsecure\s*:\s*(true|'true'|"true")/i.test(text);
|
|
55
|
-
// Check for sameSite flag (should be 'strict', 'lax', or 'none')
|
|
56
|
-
const hasSameSite = /\bsameSite\s*:\s*['"](strict|lax|none)['"]/i.test(text);
|
|
57
|
-
return { hasHttpOnly, hasSecure, hasSameSite };
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Check if a string matches any ignore pattern
|
|
61
|
-
*/
|
|
62
|
-
function matchesIgnorePattern(text, ignorePatterns) {
|
|
63
|
-
return ignorePatterns.some(pattern => {
|
|
64
|
-
try {
|
|
65
|
-
const regex = new RegExp(pattern, 'i');
|
|
66
|
-
return regex.test(text);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
exports.noInsecureCookieSettings = (0, eslint_devkit_1.createRule)({
|
|
74
|
-
name: 'no-insecure-cookie-settings',
|
|
75
|
-
meta: {
|
|
76
|
-
type: 'problem',
|
|
77
|
-
deprecated: true,
|
|
78
|
-
replacedBy: ['@see eslint-plugin-express-security/no-insecure-cookie-options'],
|
|
79
|
-
docs: {
|
|
80
|
-
description: 'Detects insecure cookie configurations (missing httpOnly, secure, sameSite flags)',
|
|
81
|
-
},
|
|
82
|
-
hasSuggestions: true,
|
|
83
|
-
messages: {
|
|
84
|
-
insecureCookieSettings: (0, eslint_devkit_1.formatLLMMessage)({
|
|
85
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
86
|
-
issueName: 'Insecure Cookie Configuration',
|
|
87
|
-
cwe: 'CWE-614',
|
|
88
|
-
description: 'Insecure cookie settings detected: {{issue}}',
|
|
89
|
-
severity: 'HIGH',
|
|
90
|
-
fix: '{{safeAlternative}}',
|
|
91
|
-
documentationLink: 'https://cwe.mitre.org/data/definitions/614.html',
|
|
92
|
-
}),
|
|
93
|
-
addSecureFlags: (0, eslint_devkit_1.formatLLMMessage)({
|
|
94
|
-
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
95
|
-
issueName: 'Add Secure Flags',
|
|
96
|
-
description: 'Set secure cookie flags',
|
|
97
|
-
severity: 'LOW',
|
|
98
|
-
fix: '{ httpOnly: true, secure: true, sameSite: "strict" }',
|
|
99
|
-
documentationLink: 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security',
|
|
100
|
-
}),
|
|
101
|
-
},
|
|
102
|
-
schema: [
|
|
103
|
-
{
|
|
104
|
-
type: 'object',
|
|
105
|
-
properties: {
|
|
106
|
-
allowInTests: {
|
|
107
|
-
type: 'boolean',
|
|
108
|
-
default: false,
|
|
109
|
-
description: 'Allow insecure cookies in test files',
|
|
110
|
-
},
|
|
111
|
-
cookieLibraries: {
|
|
112
|
-
type: 'array',
|
|
113
|
-
items: { type: 'string' },
|
|
114
|
-
default: [],
|
|
115
|
-
description: 'Cookie library patterns to recognize',
|
|
116
|
-
},
|
|
117
|
-
ignorePatterns: {
|
|
118
|
-
type: 'array',
|
|
119
|
-
items: { type: 'string' },
|
|
120
|
-
default: [],
|
|
121
|
-
description: 'Additional safe patterns to ignore',
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
additionalProperties: false,
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
defaultOptions: [
|
|
129
|
-
{
|
|
130
|
-
allowInTests: false,
|
|
131
|
-
cookieLibraries: [],
|
|
132
|
-
ignorePatterns: [],
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
create(context, [options = {}]) {
|
|
136
|
-
const { allowInTests = false, ignorePatterns = [], } = options;
|
|
137
|
-
const filename = context.getFilename();
|
|
138
|
-
const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
139
|
-
const sourceCode = context.sourceCode || context.sourceCode;
|
|
140
|
-
function checkObjectExpression(node) {
|
|
141
|
-
if (isTestFile) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
// Check if this ObjectExpression is the third argument of a cookie call
|
|
145
|
-
// First, check if parent is directly a CallExpression
|
|
146
|
-
if (node.parent && node.parent.type === 'CallExpression') {
|
|
147
|
-
const parentCall = node.parent;
|
|
148
|
-
const callee = parentCall.callee;
|
|
149
|
-
// Check if it's a cookie call
|
|
150
|
-
if (callee.type === 'MemberExpression' &&
|
|
151
|
-
callee.property.type === 'Identifier' &&
|
|
152
|
-
callee.property.name === 'cookie') {
|
|
153
|
-
// Check if this node is the third argument (index 2)
|
|
154
|
-
// Use both reference check and range check for reliability
|
|
155
|
-
const thirdArg = parentCall.arguments.length >= 3 ? parentCall.arguments[2] : null;
|
|
156
|
-
const isThirdArg = thirdArg && (thirdArg === node ||
|
|
157
|
-
(thirdArg.type === 'ObjectExpression' &&
|
|
158
|
-
thirdArg.range[0] === node.range[0] &&
|
|
159
|
-
thirdArg.range[1] === node.range[1]));
|
|
160
|
-
if (isThirdArg) {
|
|
161
|
-
// Check if the parent call is ignored
|
|
162
|
-
const callText = sourceCode.getText(parentCall);
|
|
163
|
-
if (matchesIgnorePattern(callText, ignorePatterns)) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
// If not handled above, check if it's inside a cookie config using helper
|
|
170
|
-
if (!isInsideCookieConfig(node, sourceCode)) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
// If it's inside a cookie config, check it
|
|
174
|
-
const text = sourceCode.getText(node);
|
|
175
|
-
// Check if it matches any ignore pattern
|
|
176
|
-
if (matchesIgnorePattern(text, ignorePatterns)) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const { hasHttpOnly, hasSecure, hasSameSite } = hasSecureCookieSettings(node, sourceCode);
|
|
180
|
-
const issues = [];
|
|
181
|
-
if (!hasHttpOnly) {
|
|
182
|
-
issues.push('missing httpOnly flag');
|
|
183
|
-
}
|
|
184
|
-
if (!hasSecure) {
|
|
185
|
-
issues.push('missing secure flag');
|
|
186
|
-
}
|
|
187
|
-
if (!hasSameSite) {
|
|
188
|
-
issues.push('missing sameSite flag');
|
|
189
|
-
}
|
|
190
|
-
if (issues.length > 0) {
|
|
191
|
-
const issueDescription = issues.join(', ');
|
|
192
|
-
const safeAlternative = 'Set httpOnly: true, secure: true, sameSite: "strict"';
|
|
193
|
-
context.report({
|
|
194
|
-
node,
|
|
195
|
-
messageId: 'insecureCookieSettings',
|
|
196
|
-
data: {
|
|
197
|
-
issue: issueDescription,
|
|
198
|
-
safeAlternative,
|
|
199
|
-
},
|
|
200
|
-
suggest: [
|
|
201
|
-
{
|
|
202
|
-
messageId: 'addSecureFlags',
|
|
203
|
-
fix(fixer) {
|
|
204
|
-
// Find the last property in the object
|
|
205
|
-
const properties = node.properties;
|
|
206
|
-
if (properties.length === 0) {
|
|
207
|
-
// Empty object - add all flags
|
|
208
|
-
return fixer.replaceText(node, '{ httpOnly: true, secure: true, sameSite: "strict" }');
|
|
209
|
-
}
|
|
210
|
-
const lastProperty = properties[properties.length - 1];
|
|
211
|
-
const lastPropertyText = sourceCode.getText(lastProperty);
|
|
212
|
-
const needsComma = !lastPropertyText.trim().endsWith(',');
|
|
213
|
-
const insertPosition = lastProperty.range[1];
|
|
214
|
-
const missingFlags = [];
|
|
215
|
-
if (!hasHttpOnly)
|
|
216
|
-
missingFlags.push('httpOnly: true');
|
|
217
|
-
if (!hasSecure)
|
|
218
|
-
missingFlags.push('secure: true');
|
|
219
|
-
if (!hasSameSite)
|
|
220
|
-
missingFlags.push('sameSite: "strict"');
|
|
221
|
-
const prefix = needsComma ? ',' : '';
|
|
222
|
-
const insertion = prefix + '\n ' + missingFlags.join(',\n ');
|
|
223
|
-
return fixer.insertTextAfterRange([insertPosition, insertPosition], insertion);
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
],
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
function checkCallExpression(node) {
|
|
231
|
-
if (isTestFile) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const callee = node.callee;
|
|
235
|
-
const callText = sourceCode.getText(node);
|
|
236
|
-
// Check if it matches any ignore pattern
|
|
237
|
-
if (matchesIgnorePattern(callText, ignorePatterns)) {
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
// Check for res.cookie() calls or cookies.set() calls
|
|
241
|
-
const isResCookie = callee.type === 'MemberExpression' &&
|
|
242
|
-
callee.property.type === 'Identifier' &&
|
|
243
|
-
callee.property.name === 'cookie';
|
|
244
|
-
const isUniversalCookie = callee.type === 'MemberExpression' &&
|
|
245
|
-
callee.property.type === 'Identifier' &&
|
|
246
|
-
callee.property.name === 'set' &&
|
|
247
|
-
callee.object.type === 'Identifier' &&
|
|
248
|
-
(callee.object.name === 'cookies' || callee.object.name === 'cookie');
|
|
249
|
-
if (isResCookie || isUniversalCookie) {
|
|
250
|
-
// Check if third argument (options) is provided
|
|
251
|
-
if (node.arguments.length < 3) {
|
|
252
|
-
context.report({
|
|
253
|
-
node,
|
|
254
|
-
messageId: 'insecureCookieSettings',
|
|
255
|
-
data: {
|
|
256
|
-
issue: 'missing cookie options with httpOnly, secure, and sameSite flags',
|
|
257
|
-
safeAlternative: 'Add options object: res.cookie(name, value, { httpOnly: true, secure: true, sameSite: "strict" })',
|
|
258
|
-
},
|
|
259
|
-
suggest: [
|
|
260
|
-
{
|
|
261
|
-
messageId: 'addSecureFlags',
|
|
262
|
-
fix(fixer) {
|
|
263
|
-
// Add options as third argument
|
|
264
|
-
const lastArg = node.arguments[node.arguments.length - 1];
|
|
265
|
-
const insertPosition = lastArg.range[1];
|
|
266
|
-
return fixer.insertTextAfterRange([insertPosition, insertPosition], `, { httpOnly: true, secure: true, sameSite: "strict" }`);
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
],
|
|
270
|
-
});
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
function checkAssignmentExpression(node) {
|
|
276
|
-
if (isTestFile) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
// Check for document.cookie assignments
|
|
280
|
-
if (node.left.type === 'MemberExpression' &&
|
|
281
|
-
node.left.object.type === 'Identifier' &&
|
|
282
|
-
node.left.object.name === 'document' &&
|
|
283
|
-
node.left.property.type === 'Identifier' &&
|
|
284
|
-
node.left.property.name === 'cookie') {
|
|
285
|
-
const text = sourceCode.getText(node);
|
|
286
|
-
// Check if it matches any ignore pattern
|
|
287
|
-
if (matchesIgnorePattern(text, ignorePatterns)) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
context.report({
|
|
291
|
-
node,
|
|
292
|
-
messageId: 'insecureCookieSettings',
|
|
293
|
-
data: {
|
|
294
|
-
issue: 'using document.cookie directly (cannot set httpOnly flag)',
|
|
295
|
-
safeAlternative: 'Use server-side cookie setting with httpOnly: true, secure: true, sameSite: "strict"',
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
return {
|
|
301
|
-
ObjectExpression: checkObjectExpression,
|
|
302
|
-
CallExpression: checkCallExpression,
|
|
303
|
-
AssignmentExpression: checkAssignmentExpression,
|
|
304
|
-
};
|
|
305
|
-
},
|
|
306
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { type SecurityRuleOptions } from '@interlace/eslint-devkit';
|
|
2
|
-
export interface Options extends SecurityRuleOptions {
|
|
3
|
-
/** Allow specific insecure algorithms for legacy compatibility. Default: [] (strict) */
|
|
4
|
-
allowedInsecureAlgorithms?: string[];
|
|
5
|
-
/** Minimum key length for HMAC secrets. Default: 32 (256 bits) */
|
|
6
|
-
minSecretLength?: number;
|
|
7
|
-
/** JWT libraries to consider safe. Default: jsonwebtoken, jose, jwt */
|
|
8
|
-
trustedJwtLibraries?: string[];
|
|
9
|
-
}
|
|
10
|
-
export declare const noInsecureJwt: ESLintUtils.RuleModule<MessageIds, Options, unknown, ESLintUtils.RuleListener>;
|