eslint-plugin-node-security 4.6.0 → 4.7.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/README.md +1 -0
- package/package.json +5 -8
- package/src/index.d.ts +17 -10
- package/src/index.js +5 -36
- package/src/oxlint.d.ts +17 -20
- package/src/oxlint.js +0 -19
- package/src/rules/detect-child-process/index.js +0 -107
- package/src/rules/detect-eval-with-expression/index.js +2 -48
- package/src/rules/detect-non-literal-fs-filename/index.js +0 -82
- package/src/rules/detect-suspicious-dependencies/index.js +0 -12
- package/src/rules/lock-file/index.js +0 -18
- package/src/rules/no-arbitrary-file-access/index.js +0 -52
- package/src/rules/no-buffer-overread/index.js +3 -96
- package/src/rules/no-cryptojs/index.js +2 -10
- package/src/rules/no-cryptojs-weak-random/index.js +1 -13
- package/src/rules/no-data-in-temp-storage/index.js +0 -12
- package/src/rules/no-deprecated-buffer/index.js +0 -15
- package/src/rules/no-deprecated-cipher-method/index.js +0 -9
- package/src/rules/no-dynamic-algorithm-selection/index.js +0 -13
- package/src/rules/no-dynamic-command-string/index.js +201 -0
- package/src/rules/no-dynamic-dependency-loading/index.js +0 -13
- package/src/rules/no-dynamic-require/index.js +0 -10
- package/src/rules/no-ecb-mode/index.js +0 -7
- package/src/rules/no-insecure-key-derivation/index.js +0 -10
- package/src/rules/no-insecure-rsa-padding/index.js +0 -9
- package/src/rules/no-math-random-crypto/index.js +1 -17
- package/src/rules/no-self-signed-certs/index.js +1 -9
- package/src/rules/no-sha1-hash/index.js +0 -9
- package/src/rules/no-shell-injection/index.js +0 -20
- package/src/rules/no-ssrf/index.js +4 -80
- package/src/rules/no-static-iv/index.js +2 -21
- package/src/rules/no-timing-unsafe-compare/index.js +1 -11
- package/src/rules/no-toctou-vulnerability/index.js +0 -22
- package/src/rules/no-unsafe-buffer-alloc/index.js +0 -13
- package/src/rules/no-unsafe-dynamic-require/index.js +0 -22
- package/src/rules/no-weak-cipher-algorithm/index.js +0 -19
- package/src/rules/no-weak-hash-algorithm/index.js +0 -22
- package/src/rules/no-zip-slip/index.js +3 -81
- package/src/rules/prefer-native-crypto/index.js +4 -11
- package/src/rules/require-dependency-integrity/index.js +0 -11
- package/src/rules/require-secure-credential-storage/index.js +0 -12
- package/src/rules/require-secure-deletion/index.js +0 -29
- package/src/rules/require-storage-encryption/index.js +0 -12
- package/src/types/index.js +0 -6
- package/CHANGELOG.md +0 -413
- package/src/index.js.map +0 -1
- package/src/oxlint.js.map +0 -1
- package/src/rules/detect-child-process/index.d.ts +0 -52
- package/src/rules/detect-child-process/index.js.map +0 -1
- package/src/rules/detect-eval-with-expression/index.d.ts +0 -46
- package/src/rules/detect-eval-with-expression/index.js.map +0 -1
- package/src/rules/detect-non-literal-fs-filename/index.d.ts +0 -52
- package/src/rules/detect-non-literal-fs-filename/index.js.map +0 -1
- package/src/rules/detect-suspicious-dependencies/index.d.ts +0 -12
- package/src/rules/detect-suspicious-dependencies/index.js.map +0 -1
- package/src/rules/lock-file/index.d.ts +0 -13
- package/src/rules/lock-file/index.js.map +0 -1
- package/src/rules/no-arbitrary-file-access/index.d.ts +0 -12
- package/src/rules/no-arbitrary-file-access/index.js.map +0 -1
- package/src/rules/no-buffer-overread/index.d.ts +0 -39
- package/src/rules/no-buffer-overread/index.js.map +0 -1
- package/src/rules/no-cryptojs/index.d.ts +0 -24
- package/src/rules/no-cryptojs/index.js.map +0 -1
- package/src/rules/no-cryptojs-weak-random/index.d.ts +0 -24
- package/src/rules/no-cryptojs-weak-random/index.js.map +0 -1
- package/src/rules/no-data-in-temp-storage/index.d.ts +0 -14
- package/src/rules/no-data-in-temp-storage/index.js.map +0 -1
- package/src/rules/no-deprecated-buffer/index.d.ts +0 -23
- package/src/rules/no-deprecated-buffer/index.js.map +0 -1
- package/src/rules/no-deprecated-cipher-method/index.d.ts +0 -23
- package/src/rules/no-deprecated-cipher-method/index.js.map +0 -1
- package/src/rules/no-dynamic-algorithm-selection/index.d.ts +0 -26
- package/src/rules/no-dynamic-algorithm-selection/index.js.map +0 -1
- package/src/rules/no-dynamic-dependency-loading/index.d.ts +0 -12
- package/src/rules/no-dynamic-dependency-loading/index.js.map +0 -1
- package/src/rules/no-dynamic-require/index.d.ts +0 -21
- package/src/rules/no-dynamic-require/index.js.map +0 -1
- package/src/rules/no-ecb-mode/index.d.ts +0 -23
- package/src/rules/no-ecb-mode/index.js.map +0 -1
- package/src/rules/no-insecure-key-derivation/index.d.ts +0 -24
- package/src/rules/no-insecure-key-derivation/index.js.map +0 -1
- package/src/rules/no-insecure-rsa-padding/index.d.ts +0 -24
- package/src/rules/no-insecure-rsa-padding/index.js.map +0 -1
- package/src/rules/no-math-random-crypto/index.d.ts +0 -26
- package/src/rules/no-math-random-crypto/index.js.map +0 -1
- package/src/rules/no-self-signed-certs/index.d.ts +0 -23
- package/src/rules/no-self-signed-certs/index.js.map +0 -1
- package/src/rules/no-sha1-hash/index.d.ts +0 -24
- package/src/rules/no-sha1-hash/index.js.map +0 -1
- package/src/rules/no-shell-injection/index.d.ts +0 -20
- package/src/rules/no-shell-injection/index.js.map +0 -1
- package/src/rules/no-ssrf/index.d.ts +0 -25
- package/src/rules/no-ssrf/index.js.map +0 -1
- package/src/rules/no-static-iv/index.d.ts +0 -23
- package/src/rules/no-static-iv/index.js.map +0 -1
- package/src/rules/no-timing-unsafe-compare/index.d.ts +0 -23
- package/src/rules/no-timing-unsafe-compare/index.js.map +0 -1
- package/src/rules/no-toctou-vulnerability/index.d.ts +0 -26
- package/src/rules/no-toctou-vulnerability/index.js.map +0 -1
- package/src/rules/no-unsafe-buffer-alloc/index.d.ts +0 -47
- package/src/rules/no-unsafe-buffer-alloc/index.js.map +0 -1
- package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -19
- package/src/rules/no-unsafe-dynamic-require/index.js.map +0 -1
- package/src/rules/no-weak-cipher-algorithm/index.d.ts +0 -25
- package/src/rules/no-weak-cipher-algorithm/index.js.map +0 -1
- package/src/rules/no-weak-hash-algorithm/index.d.ts +0 -25
- package/src/rules/no-weak-hash-algorithm/index.js.map +0 -1
- package/src/rules/no-zip-slip/index.d.ts +0 -35
- package/src/rules/no-zip-slip/index.js.map +0 -1
- package/src/rules/prefer-native-crypto/index.d.ts +0 -23
- package/src/rules/prefer-native-crypto/index.js.map +0 -1
- package/src/rules/require-dependency-integrity/index.d.ts +0 -12
- package/src/rules/require-dependency-integrity/index.js.map +0 -1
- package/src/rules/require-secure-credential-storage/index.d.ts +0 -12
- package/src/rules/require-secure-credential-storage/index.js.map +0 -1
- package/src/rules/require-secure-deletion/index.d.ts +0 -17
- package/src/rules/require-secure-deletion/index.js.map +0 -1
- package/src/rules/require-storage-encryption/index.d.ts +0 -12
- package/src/rules/require-storage-encryption/index.js.map +0 -1
- package/src/types/index.js.map +0 -1
|
@@ -1,29 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noZipSlip = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
5
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
-
/**
|
|
12
|
-
* Check if path contains dangerous traversal sequences
|
|
13
|
-
*/
|
|
14
6
|
const containsPathTraversal = (pathText) => {
|
|
15
|
-
// Check for ../ sequences
|
|
16
7
|
return /\.\.\//.test(pathText) ||
|
|
17
|
-
/\.\.\\/.test(pathText) ||
|
|
8
|
+
/\.\.\\/.test(pathText) ||
|
|
18
9
|
pathText.startsWith('..') ||
|
|
19
|
-
/\/\.\./.test(pathText);
|
|
10
|
+
/\/\.\./.test(pathText);
|
|
20
11
|
};
|
|
21
|
-
/**
|
|
22
|
-
* Check if destination is dangerous (sensitive system directories)
|
|
23
|
-
* Note: /tmp is NOT dangerous - it's the standard safe temp location
|
|
24
|
-
*/
|
|
25
12
|
const isDangerousDestination = (destText) => {
|
|
26
|
-
// /tmp is SAFE - it's the standard temp location
|
|
27
13
|
if (destText.startsWith('/tmp') || destText.includes('os.tmpdir') || destText.includes('TMPDIR')) {
|
|
28
14
|
return false;
|
|
29
15
|
}
|
|
@@ -176,42 +162,27 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
176
162
|
const options = context.options[0] || {};
|
|
177
163
|
const { archiveFunctions = ['extract', 'extractAll', 'extractAllTo', 'unzip', 'untar', 'extractArchive'], pathValidationFunctions = ['validatePath', 'sanitizePath', 'checkPath', 'safePath'], safeLibraries = ['yauzl', 'safe-archive-extract', 'tar-stream', 'unzipper'], } = options;
|
|
178
164
|
const filename = context.filename;
|
|
179
|
-
// Safety checks are implemented directly in the handlers
|
|
180
|
-
/**
|
|
181
|
-
* Check if this is an archive extraction operation
|
|
182
|
-
*/
|
|
183
165
|
const isArchiveExtraction = (node) => {
|
|
184
166
|
const callee = node.callee;
|
|
185
|
-
// Check for archive method calls (e.g., zip.extractAllTo)
|
|
186
167
|
if (callee.type === 'MemberExpression' &&
|
|
187
168
|
callee.property.type === 'Identifier' &&
|
|
188
169
|
archiveFunctions.includes(callee.property.name)) {
|
|
189
170
|
return true;
|
|
190
171
|
}
|
|
191
|
-
// Check for standalone archive functions (e.g., extractArchive)
|
|
192
172
|
if (callee.type === 'Identifier' &&
|
|
193
173
|
archiveFunctions.includes(callee.name)) {
|
|
194
174
|
return true;
|
|
195
175
|
}
|
|
196
176
|
return false;
|
|
197
177
|
};
|
|
198
|
-
/**
|
|
199
|
-
* Check if path has been validated or sanitized
|
|
200
|
-
* Detects patterns like:
|
|
201
|
-
* - validatePath(), sanitizePath() custom functions
|
|
202
|
-
* - path.basename() sanitization
|
|
203
|
-
* - startsWith() validation in preceding if-block
|
|
204
|
-
*/
|
|
205
178
|
const isPathValidated = (pathNode) => {
|
|
206
179
|
let current = pathNode;
|
|
207
180
|
while (current) {
|
|
208
|
-
// Check for custom validation function wrappers
|
|
209
181
|
if (current.type === 'CallExpression' &&
|
|
210
182
|
current.callee.type === 'Identifier' &&
|
|
211
183
|
pathValidationFunctions.includes(current.callee.name)) {
|
|
212
184
|
return true;
|
|
213
185
|
}
|
|
214
|
-
// Check for path.basename() sanitization
|
|
215
186
|
if (current.type === 'CallExpression' &&
|
|
216
187
|
current.callee.type === 'MemberExpression' &&
|
|
217
188
|
current.callee.object.type === 'Identifier' &&
|
|
@@ -220,10 +191,8 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
220
191
|
current.callee.property.name === 'basename') {
|
|
221
192
|
return true;
|
|
222
193
|
}
|
|
223
|
-
// Check for preceding if-block with startsWith validation
|
|
224
194
|
if (current.type === 'IfStatement') {
|
|
225
195
|
const test = current.test;
|
|
226
|
-
// if (path.startsWith(...)) or if (!path.startsWith(...)) { throw/return }
|
|
227
196
|
if (test.type === 'CallExpression' &&
|
|
228
197
|
test.callee.type === 'MemberExpression' &&
|
|
229
198
|
test.callee.property.type === 'Identifier' &&
|
|
@@ -237,7 +206,6 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
237
206
|
test.argument.callee.property.name === 'startsWith') {
|
|
238
207
|
return true;
|
|
239
208
|
}
|
|
240
|
-
// if (path.includes('..')) { throw/return }
|
|
241
209
|
if (test.type === 'CallExpression' &&
|
|
242
210
|
test.callee.type === 'MemberExpression' &&
|
|
243
211
|
test.callee.property.type === 'Identifier' &&
|
|
@@ -249,20 +217,13 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
249
217
|
}
|
|
250
218
|
return false;
|
|
251
219
|
};
|
|
252
|
-
/**
|
|
253
|
-
* Check if this uses a safe library
|
|
254
|
-
* Safe libraries have built-in path validation or are known to be secure
|
|
255
|
-
*/
|
|
256
220
|
const isSafeLibrary = (node) => {
|
|
257
221
|
const callee = node.callee;
|
|
258
|
-
// Check for method calls on safe library instances (e.g., yauzl.open())
|
|
259
222
|
if (callee.type === 'MemberExpression' &&
|
|
260
223
|
callee.object.type === 'Identifier' &&
|
|
261
224
|
safeLibraries.includes(callee.object.name)) {
|
|
262
225
|
return true;
|
|
263
226
|
}
|
|
264
|
-
// Check for direct calls to safe library functions (e.g., extract(file, opts))
|
|
265
|
-
// This handles patterns like: const extract = require('extract-zip'); extract(...)
|
|
266
227
|
if (callee.type === 'Identifier') {
|
|
267
228
|
const name = callee.name.toLowerCase();
|
|
268
229
|
if (name === 'extract' || name === 'unzipper' ||
|
|
@@ -273,13 +234,10 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
273
234
|
return false;
|
|
274
235
|
};
|
|
275
236
|
return {
|
|
276
|
-
// Check archive extraction calls
|
|
277
237
|
CallExpression(node) {
|
|
278
238
|
if (isArchiveExtraction(node) && !isSafeLibrary(node)) {
|
|
279
|
-
// Check for @safe annotations in the source
|
|
280
239
|
const sourceCode = context.sourceCode;
|
|
281
240
|
let hasSafeAnnotation = false;
|
|
282
|
-
// Look for @safe comments in the source code
|
|
283
241
|
const allComments = sourceCode.getAllComments();
|
|
284
242
|
for (const comment of allComments) {
|
|
285
243
|
if (comment.type === 'Block' && comment.value.includes('@safe')) {
|
|
@@ -288,36 +246,26 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
288
246
|
}
|
|
289
247
|
}
|
|
290
248
|
if (hasSafeAnnotation) {
|
|
291
|
-
return;
|
|
249
|
+
return;
|
|
292
250
|
}
|
|
293
|
-
// Check if destination is dangerous
|
|
294
251
|
const args = node.arguments;
|
|
295
252
|
let destArg;
|
|
296
|
-
// Determine which argument is the destination based on the function
|
|
297
253
|
if (node.callee.type === 'MemberExpression' && node.callee.property.type === 'Identifier') {
|
|
298
254
|
const methodName = node.callee.property.name;
|
|
299
255
|
if (['extractAllTo', 'unzip'].includes(methodName)) {
|
|
300
|
-
// Destination is the first argument
|
|
301
256
|
destArg = args[0];
|
|
302
257
|
}
|
|
303
258
|
else {
|
|
304
|
-
// For other archive functions, destination is typically the
|
|
305
|
-
// second argument. isArchiveExtraction() already guaranteed
|
|
306
|
-
// methodName is listed in archiveFunctions for member callees.
|
|
307
259
|
destArg = args.length >= 2 ? args[1] : undefined;
|
|
308
260
|
}
|
|
309
261
|
}
|
|
310
262
|
else {
|
|
311
|
-
// For standalone functions like extractArchive(file, dest);
|
|
312
|
-
// isArchiveExtraction() already guaranteed the callee is an
|
|
313
|
-
// Identifier naming an archive function.
|
|
314
263
|
destArg = args.length >= 2 ? args[1] : undefined;
|
|
315
264
|
}
|
|
316
265
|
const destText = destArg && destArg.type === 'Literal' && typeof destArg.value === 'string' ? destArg.value : '';
|
|
317
266
|
const isDestDangerous = isDangerousDestination(destText);
|
|
318
267
|
const isMethodCall = node.callee.type === 'MemberExpression';
|
|
319
268
|
if (isMethodCall) {
|
|
320
|
-
// Method calls report unsafeArchiveExtraction unless destination is a safe relative path
|
|
321
269
|
const isSafeRelativePath = destText.startsWith('./') || destText.startsWith('../');
|
|
322
270
|
if (!isSafeRelativePath) {
|
|
323
271
|
context.report({
|
|
@@ -335,10 +283,6 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
335
283
|
],
|
|
336
284
|
});
|
|
337
285
|
}
|
|
338
|
-
// For safe relative paths, don't report any error
|
|
339
|
-
// Additionally report dangerous destination for dangerous destinations.
|
|
340
|
-
// `isDestDangerous` implies `destArg` is a string Literal (destText
|
|
341
|
-
// is derived from it), so no `|| node` fallback is needed here.
|
|
342
286
|
if (isDestDangerous && destArg) {
|
|
343
287
|
context.report({
|
|
344
288
|
node: destArg,
|
|
@@ -351,7 +295,6 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
351
295
|
}
|
|
352
296
|
}
|
|
353
297
|
else {
|
|
354
|
-
// Standalone calls: report dangerousArchiveDestination for dangerous destinations, unsafeArchiveExtraction otherwise
|
|
355
298
|
if (isDestDangerous) {
|
|
356
299
|
context.report({
|
|
357
300
|
node,
|
|
@@ -380,18 +323,15 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
380
323
|
}
|
|
381
324
|
}
|
|
382
325
|
}
|
|
383
|
-
// Check for path.join or similar operations with archive entry names
|
|
384
326
|
const callee = node.callee;
|
|
385
327
|
if (callee.type === 'MemberExpression' &&
|
|
386
328
|
callee.property.type === 'Identifier' &&
|
|
387
329
|
['join', 'resolve', 'relative', 'normalize'].includes(callee.property.name)) {
|
|
388
|
-
// Check arguments for potential archive entry usage
|
|
389
330
|
const args = node.arguments;
|
|
390
331
|
for (const arg of args) {
|
|
391
332
|
if (arg.type === 'MemberExpression' &&
|
|
392
333
|
arg.property.type === 'Identifier' &&
|
|
393
334
|
['name', 'path', 'fileName', 'entryName', 'relativePath', 'filename', 'pathname'].includes(arg.property.name)) {
|
|
394
|
-
// This looks like path.join(dest, entry.name) - check if validated
|
|
395
335
|
if (!isPathValidated(arg)) {
|
|
396
336
|
context.report({
|
|
397
337
|
node: arg,
|
|
@@ -406,19 +346,14 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
406
346
|
}
|
|
407
347
|
}
|
|
408
348
|
},
|
|
409
|
-
// Check string literals for dangerous paths
|
|
410
349
|
Literal(node) {
|
|
411
350
|
if (typeof node.value !== 'string') {
|
|
412
351
|
return;
|
|
413
352
|
}
|
|
414
353
|
const text = node.value;
|
|
415
|
-
// Check for path traversal in strings that look like file paths
|
|
416
354
|
if ((text.includes('/') || text.includes('\\')) && containsPathTraversal(text)) {
|
|
417
|
-
// Check if this is in an archive-related context
|
|
418
355
|
let current = node;
|
|
419
356
|
let isArchiveContext = false;
|
|
420
|
-
// Every assignment of `isArchiveContext = true` is followed by `break`,
|
|
421
|
-
// so the negation is dead (CodeQL: `js/useless-conditional`).
|
|
422
357
|
while (current) {
|
|
423
358
|
if (current.type === 'CallExpression' && isArchiveExtraction(current)) {
|
|
424
359
|
isArchiveContext = true;
|
|
@@ -437,7 +372,6 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
437
372
|
}
|
|
438
373
|
current = current.parent;
|
|
439
374
|
}
|
|
440
|
-
// Also check if the variable name suggests archive usage
|
|
441
375
|
const parent = node.parent;
|
|
442
376
|
if (parent && parent.type === 'VariableDeclarator' && parent.id.type === 'Identifier') {
|
|
443
377
|
const varName = parent.id.name.toLowerCase();
|
|
@@ -458,31 +392,19 @@ exports.noZipSlip = (0, eslint_devkit_1.createRule)({
|
|
|
458
392
|
});
|
|
459
393
|
}
|
|
460
394
|
}
|
|
461
|
-
// Dangerous-destination check moved to the CallExpression handler
|
|
462
|
-
// (see lines 407-427). It already fires `dangerousArchiveDestination`
|
|
463
|
-
// for archive-extraction calls; firing again here would duplicate.
|
|
464
|
-
// The previous "fire on any /etc or /home literal" logic produced
|
|
465
|
-
// false positives on unrelated calls (fs.readFileSync, exec, etc.)
|
|
466
|
-
// and is no longer needed now that the call-site check is precise.
|
|
467
395
|
},
|
|
468
|
-
// Check variable assignments
|
|
469
396
|
VariableDeclarator(node) {
|
|
470
397
|
if (!node.init || node.id.type !== 'Identifier') {
|
|
471
398
|
return;
|
|
472
399
|
}
|
|
473
400
|
const varName = node.id.name.toLowerCase();
|
|
474
|
-
// Check if this variable holds archive-related data
|
|
475
401
|
if (varName.includes('entry') || varName.includes('file') || varName.includes('path')) {
|
|
476
402
|
if (node.init.type === 'MemberExpression' &&
|
|
477
403
|
node.init.property.type === 'Identifier' &&
|
|
478
404
|
['name', 'path'].includes(node.init.property.name)) {
|
|
479
|
-
// This looks like: const entryName = entry.name;
|
|
480
|
-
// Check if this variable is used unsafely later
|
|
481
|
-
// This is a simplified check - in practice we'd need more sophisticated analysis
|
|
482
405
|
}
|
|
483
406
|
}
|
|
484
407
|
}
|
|
485
408
|
};
|
|
486
409
|
},
|
|
487
410
|
});
|
|
488
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.preferNativeCrypto = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
-
// Third-party crypto libraries that should be replaced with native
|
|
11
5
|
const THIRD_PARTY_CRYPTO_LIBS = new Set([
|
|
12
6
|
'crypto-js',
|
|
13
7
|
'cryptojs',
|
|
14
|
-
'sjcl',
|
|
15
|
-
'forge',
|
|
8
|
+
'sjcl',
|
|
9
|
+
'forge',
|
|
16
10
|
'node-forge',
|
|
17
11
|
'jsencrypt',
|
|
18
|
-
'bcryptjs',
|
|
12
|
+
'bcryptjs',
|
|
19
13
|
'js-sha256',
|
|
20
14
|
'js-sha512',
|
|
21
15
|
'js-sha3',
|
|
@@ -102,7 +96,7 @@ exports.preferNativeCrypto = (0, eslint_devkit_1.createRule)({
|
|
|
102
96
|
return {
|
|
103
97
|
ImportDeclaration(node) {
|
|
104
98
|
if (typeof node.source.value === 'string') {
|
|
105
|
-
const lib = node.source.value.split('/')[0];
|
|
99
|
+
const lib = node.source.value.split('/')[0];
|
|
106
100
|
if (THIRD_PARTY_CRYPTO_LIBS.has(lib)) {
|
|
107
101
|
reportThirdPartyLib(node, lib);
|
|
108
102
|
}
|
|
@@ -123,4 +117,3 @@ exports.preferNativeCrypto = (0, eslint_devkit_1.createRule)({
|
|
|
123
117
|
};
|
|
124
118
|
},
|
|
125
119
|
});
|
|
126
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.requireDependencyIntegrity = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Require integrity hashes for external resources
|
|
11
|
-
*/
|
|
12
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
13
5
|
exports.requireDependencyIntegrity = (0, eslint_devkit_1.createRule)({
|
|
14
6
|
name: 'require-dependency-integrity',
|
|
@@ -42,11 +34,9 @@ exports.requireDependencyIntegrity = (0, eslint_devkit_1.createRule)({
|
|
|
42
34
|
Literal(node) {
|
|
43
35
|
if (typeof node.value !== 'string')
|
|
44
36
|
return;
|
|
45
|
-
// Check for script/link tags without integrity
|
|
46
37
|
const value = node.value.toLowerCase();
|
|
47
38
|
if ((value.includes('<script') && value.includes('src=')) ||
|
|
48
39
|
(value.includes('<link') && value.includes('href='))) {
|
|
49
|
-
// Check if CDN source
|
|
50
40
|
if (value.includes('cdn.') || value.includes('cdnjs.') ||
|
|
51
41
|
value.includes('unpkg.') || value.includes('jsdelivr.')) {
|
|
52
42
|
if (!value.includes('integrity=')) {
|
|
@@ -70,4 +60,3 @@ exports.requireDependencyIntegrity = (0, eslint_devkit_1.createRule)({
|
|
|
70
60
|
};
|
|
71
61
|
},
|
|
72
62
|
});
|
|
73
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.requireSecureCredentialStorage = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Enforce secure storage patterns for credentials
|
|
11
|
-
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
-
* @see https://cwe.mitre.org/data/definitions/522.html
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
exports.requireSecureCredentialStorage = (0, eslint_devkit_1.createRule)({
|
|
16
6
|
name: 'require-secure-credential-storage',
|
|
@@ -42,7 +32,6 @@ exports.requireSecureCredentialStorage = (0, eslint_devkit_1.createRule)({
|
|
|
42
32
|
if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
43
33
|
node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
44
34
|
['setItem', 'writeFile'].includes(node.callee.property.name)) {
|
|
45
|
-
// Check for encryption wrapper
|
|
46
35
|
const hasEncryption = node.arguments.some(arg => arg.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
|
|
47
36
|
arg.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
48
37
|
arg.callee.name.includes('encrypt'));
|
|
@@ -54,4 +43,3 @@ exports.requireSecureCredentialStorage = (0, eslint_devkit_1.createRule)({
|
|
|
54
43
|
};
|
|
55
44
|
},
|
|
56
45
|
});
|
|
57
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,29 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.requireSecureDeletion = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Require secure data deletion patterns
|
|
11
|
-
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
-
* @see https://cwe.mitre.org/data/definitions/459.html
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
|
-
/**
|
|
16
|
-
* Property-name fragments that mark a value as a secret whose lifetime matters.
|
|
17
|
-
*
|
|
18
|
-
* `delete` unbinds a property; it does not scrub the string, and every other
|
|
19
|
-
* reference (a spread copy, a log line, an already-serialised response body)
|
|
20
|
-
* keeps the value alive. That is the CWE-459 "incomplete cleanup" this rule is
|
|
21
|
-
* about — and it only means anything when the property actually held a secret.
|
|
22
|
-
*
|
|
23
|
-
* Matching is on the property NAME, deliberately: the value at a `delete` site
|
|
24
|
-
* is not available to a syntactic rule, so the name is the only signal there
|
|
25
|
-
* is. The cost of that is bounded by keeping this list narrow.
|
|
26
|
-
*/
|
|
27
5
|
const SENSITIVE_PROPERTY_FRAGMENTS = [
|
|
28
6
|
'password', 'passwd', 'pwd', 'passphrase',
|
|
29
7
|
'secret', 'apikey', 'api_key',
|
|
@@ -75,9 +53,7 @@ exports.requireSecureDeletion = (0, eslint_devkit_1.createRule)({
|
|
|
75
53
|
...SENSITIVE_PROPERTY_FRAGMENTS,
|
|
76
54
|
...additionalSensitiveProperties.map((f) => f.toLowerCase()),
|
|
77
55
|
];
|
|
78
|
-
/** The property name being deleted, or undefined if it isn't statically known. */
|
|
79
56
|
function deletedPropertyName(node) {
|
|
80
|
-
// `delete obj?.password` wraps the member expression in a ChainExpression.
|
|
81
57
|
const argument = node.type === 'ChainExpression' ? node.expression : node;
|
|
82
58
|
if (argument.type !== 'MemberExpression')
|
|
83
59
|
return undefined;
|
|
@@ -93,10 +69,6 @@ exports.requireSecureDeletion = (0, eslint_devkit_1.createRule)({
|
|
|
93
69
|
UnaryExpression(node) {
|
|
94
70
|
if (node.operator !== 'delete')
|
|
95
71
|
return;
|
|
96
|
-
// Only a `delete` of a *named, statically known, sensitive* property is
|
|
97
|
-
// reportable. Firing on every `delete obj.prop` produced 120 findings
|
|
98
|
-
// on a 1,470-file corpus with no security content whatsoever — the rule
|
|
99
|
-
// was a `delete` detector, not a secret-cleanup detector.
|
|
100
72
|
const property = deletedPropertyName(node.argument);
|
|
101
73
|
if (!property)
|
|
102
74
|
return;
|
|
@@ -108,4 +80,3 @@ exports.requireSecureDeletion = (0, eslint_devkit_1.createRule)({
|
|
|
108
80
|
};
|
|
109
81
|
},
|
|
110
82
|
});
|
|
111
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.requireStorageEncryption = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Require encryption for persistent storage
|
|
11
|
-
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
-
* @see https://cwe.mitre.org/data/definitions/311.html
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
exports.requireStorageEncryption = (0, eslint_devkit_1.createRule)({
|
|
16
6
|
name: 'require-storage-encryption',
|
|
@@ -42,7 +32,6 @@ exports.requireStorageEncryption = (0, eslint_devkit_1.createRule)({
|
|
|
42
32
|
if (node.callee.type === 'MemberExpression' &&
|
|
43
33
|
node.callee.property.type === 'Identifier' &&
|
|
44
34
|
['setItem', 'writeFile'].includes(node.callee.property.name)) {
|
|
45
|
-
// Check for encryption wrapper
|
|
46
35
|
const hasEncryption = node.arguments.some(arg => arg.type === 'CallExpression' &&
|
|
47
36
|
arg.callee.type === 'Identifier' &&
|
|
48
37
|
arg.callee.name.includes('encrypt'));
|
|
@@ -54,4 +43,3 @@ exports.requireStorageEncryption = (0, eslint_devkit_1.createRule)({
|
|
|
54
43
|
};
|
|
55
44
|
},
|
|
56
45
|
});
|
|
57
|
-
//# sourceMappingURL=index.js.map
|
package/src/types/index.js
CHANGED