eslint-plugin-node-security 4.0.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 +83 -0
- package/README.md +50 -0
- package/package.json +79 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +118 -0
- package/src/index.js.map +1 -0
- package/src/rules/detect-child-process/index.d.ts +30 -0
- package/src/rules/detect-child-process/index.js +535 -0
- package/src/rules/detect-child-process/index.js.map +1 -0
- package/src/rules/detect-eval-with-expression/index.d.ts +28 -0
- package/src/rules/detect-eval-with-expression/index.js +398 -0
- package/src/rules/detect-eval-with-expression/index.js.map +1 -0
- package/src/rules/detect-non-literal-fs-filename/index.d.ts +26 -0
- package/src/rules/detect-non-literal-fs-filename/index.js +460 -0
- package/src/rules/detect-non-literal-fs-filename/index.js.map +1 -0
- package/src/rules/detect-suspicious-dependencies/index.d.ts +12 -0
- package/src/rules/detect-suspicious-dependencies/index.js +77 -0
- package/src/rules/detect-suspicious-dependencies/index.js.map +1 -0
- package/src/rules/lock-file/index.d.ts +13 -0
- package/src/rules/lock-file/index.js +94 -0
- package/src/rules/lock-file/index.js.map +1 -0
- package/src/rules/no-arbitrary-file-access/index.d.ts +12 -0
- package/src/rules/no-arbitrary-file-access/index.js +201 -0
- package/src/rules/no-arbitrary-file-access/index.js.map +1 -0
- package/src/rules/no-buffer-overread/index.d.ts +39 -0
- package/src/rules/no-buffer-overread/index.js +612 -0
- package/src/rules/no-buffer-overread/index.js.map +1 -0
- package/src/rules/no-cryptojs/index.d.ts +24 -0
- package/src/rules/no-cryptojs/index.js +104 -0
- package/src/rules/no-cryptojs/index.js.map +1 -0
- package/src/rules/no-cryptojs-weak-random/index.d.ts +24 -0
- package/src/rules/no-cryptojs-weak-random/index.js +112 -0
- package/src/rules/no-cryptojs-weak-random/index.js.map +1 -0
- package/src/rules/no-data-in-temp-storage/index.d.ts +14 -0
- package/src/rules/no-data-in-temp-storage/index.js +99 -0
- package/src/rules/no-data-in-temp-storage/index.js.map +1 -0
- package/src/rules/no-deprecated-cipher-method/index.d.ts +23 -0
- package/src/rules/no-deprecated-cipher-method/index.js +118 -0
- package/src/rules/no-deprecated-cipher-method/index.js.map +1 -0
- package/src/rules/no-dynamic-dependency-loading/index.d.ts +12 -0
- package/src/rules/no-dynamic-dependency-loading/index.js +55 -0
- package/src/rules/no-dynamic-dependency-loading/index.js.map +1 -0
- package/src/rules/no-dynamic-require/index.d.ts +21 -0
- package/src/rules/no-dynamic-require/index.js +122 -0
- package/src/rules/no-dynamic-require/index.js.map +1 -0
- package/src/rules/no-ecb-mode/index.d.ts +23 -0
- package/src/rules/no-ecb-mode/index.js +113 -0
- package/src/rules/no-ecb-mode/index.js.map +1 -0
- package/src/rules/no-insecure-key-derivation/index.d.ts +24 -0
- package/src/rules/no-insecure-key-derivation/index.js +116 -0
- package/src/rules/no-insecure-key-derivation/index.js.map +1 -0
- package/src/rules/no-insecure-rsa-padding/index.d.ts +24 -0
- package/src/rules/no-insecure-rsa-padding/index.js +110 -0
- package/src/rules/no-insecure-rsa-padding/index.js.map +1 -0
- package/src/rules/no-pii-in-logs/index.d.ts +12 -0
- package/src/rules/no-pii-in-logs/index.js +74 -0
- package/src/rules/no-pii-in-logs/index.js.map +1 -0
- package/src/rules/no-self-signed-certs/index.d.ts +23 -0
- package/src/rules/no-self-signed-certs/index.js +116 -0
- package/src/rules/no-self-signed-certs/index.js.map +1 -0
- package/src/rules/no-sha1-hash/index.d.ts +24 -0
- package/src/rules/no-sha1-hash/index.js +128 -0
- package/src/rules/no-sha1-hash/index.js.map +1 -0
- package/src/rules/no-static-iv/index.d.ts +23 -0
- package/src/rules/no-static-iv/index.js +147 -0
- package/src/rules/no-static-iv/index.js.map +1 -0
- package/src/rules/no-timing-unsafe-compare/index.d.ts +23 -0
- package/src/rules/no-timing-unsafe-compare/index.js +114 -0
- package/src/rules/no-timing-unsafe-compare/index.js.map +1 -0
- package/src/rules/no-toctou-vulnerability/index.d.ts +26 -0
- package/src/rules/no-toctou-vulnerability/index.js +214 -0
- package/src/rules/no-toctou-vulnerability/index.js.map +1 -0
- package/src/rules/no-unsafe-dynamic-require/index.d.ts +19 -0
- package/src/rules/no-unsafe-dynamic-require/index.js +112 -0
- package/src/rules/no-unsafe-dynamic-require/index.js.map +1 -0
- package/src/rules/no-weak-cipher-algorithm/index.d.ts +25 -0
- package/src/rules/no-weak-cipher-algorithm/index.js +190 -0
- package/src/rules/no-weak-cipher-algorithm/index.js.map +1 -0
- package/src/rules/no-weak-hash-algorithm/index.d.ts +25 -0
- package/src/rules/no-weak-hash-algorithm/index.js +218 -0
- package/src/rules/no-weak-hash-algorithm/index.js.map +1 -0
- package/src/rules/no-zip-slip/index.d.ts +35 -0
- package/src/rules/no-zip-slip/index.js +451 -0
- package/src/rules/no-zip-slip/index.js.map +1 -0
- package/src/rules/prefer-native-crypto/index.d.ts +23 -0
- package/src/rules/prefer-native-crypto/index.js +124 -0
- package/src/rules/prefer-native-crypto/index.js.map +1 -0
- package/src/rules/require-dependency-integrity/index.d.ts +12 -0
- package/src/rules/require-dependency-integrity/index.js +70 -0
- package/src/rules/require-dependency-integrity/index.js.map +1 -0
- package/src/rules/require-secure-credential-storage/index.d.ts +12 -0
- package/src/rules/require-secure-credential-storage/index.js +54 -0
- package/src/rules/require-secure-credential-storage/index.js.map +1 -0
- package/src/rules/require-secure-deletion/index.d.ts +12 -0
- package/src/rules/require-secure-deletion/index.js +46 -0
- package/src/rules/require-secure-deletion/index.js.map +1 -0
- package/src/rules/require-storage-encryption/index.d.ts +12 -0
- package/src/rules/require-storage-encryption/index.js +54 -0
- package/src/rules/require-storage-encryption/index.js.map +1 -0
- package/src/types/index.d.ts +24 -0
- package/src/types/index.js +8 -0
- package/src/types/index.js.map +1 -0
|
@@ -0,0 +1,94 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.lockFile = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* @fileoverview Ensure package lock file exists
|
|
11
|
+
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
+
* @see https://cwe.mitre.org/data/definitions/829.html
|
|
13
|
+
*/
|
|
14
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
|
+
exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
16
|
+
name: 'lock-file',
|
|
17
|
+
meta: {
|
|
18
|
+
type: 'suggestion',
|
|
19
|
+
docs: {
|
|
20
|
+
description: 'Ensure package lock file exists for the configured package manager',
|
|
21
|
+
},
|
|
22
|
+
messages: {
|
|
23
|
+
violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
|
|
24
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
25
|
+
issueName: 'Lock File Missing',
|
|
26
|
+
cwe: 'CWE-829',
|
|
27
|
+
description: 'Package lock file missing ({{ lockFile }}) for {{ packageManager }}. Commit the lock file to ensure supply chain integrity.',
|
|
28
|
+
severity: 'HIGH',
|
|
29
|
+
fix: 'Generate and commit the {{ lockFile }} file.',
|
|
30
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/829.html',
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
schema: [
|
|
34
|
+
{
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
packageManager: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
enum: ['npm', 'yarn', 'pnpm'],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
defaultOptions: [{ packageManager: 'npm' }],
|
|
47
|
+
create(context) {
|
|
48
|
+
const fs = require('node:fs');
|
|
49
|
+
const path = require('node:path');
|
|
50
|
+
// Check once per file
|
|
51
|
+
let checked = false;
|
|
52
|
+
const options = context.options[0] || {};
|
|
53
|
+
const packageManager = options.packageManager || 'npm';
|
|
54
|
+
const lockFiles = {
|
|
55
|
+
npm: 'package-lock.json',
|
|
56
|
+
yarn: 'yarn.lock',
|
|
57
|
+
pnpm: 'pnpm-lock.yaml',
|
|
58
|
+
};
|
|
59
|
+
const targetLockFile = lockFiles[packageManager];
|
|
60
|
+
return {
|
|
61
|
+
Program(node) {
|
|
62
|
+
if (checked)
|
|
63
|
+
return;
|
|
64
|
+
checked = true;
|
|
65
|
+
// Find project root (simplified)
|
|
66
|
+
let dir = path.dirname(context.filename);
|
|
67
|
+
let found = false;
|
|
68
|
+
// Search up to 10 levels for the lock file
|
|
69
|
+
for (let i = 0; i < 10; i++) {
|
|
70
|
+
const lockPath = path.join(dir, targetLockFile);
|
|
71
|
+
if (fs.existsSync(lockPath)) {
|
|
72
|
+
found = true;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
const parentDir = path.dirname(dir);
|
|
76
|
+
if (parentDir === dir)
|
|
77
|
+
break;
|
|
78
|
+
dir = parentDir;
|
|
79
|
+
}
|
|
80
|
+
if (!found) {
|
|
81
|
+
context.report({
|
|
82
|
+
node,
|
|
83
|
+
messageId: 'violationDetected',
|
|
84
|
+
data: {
|
|
85
|
+
packageManager,
|
|
86
|
+
lockFile: targetLockFile,
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/lock-file/index.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH;;;;GAIG;AAEH,4DAAsF;AAWzE,QAAA,QAAQ,GAAG,IAAA,0BAAU,EAA0B;IAC1D,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,oEAAoE;SAClF;QACD,QAAQ,EAAE;YACR,iBAAiB,EAAE,IAAA,gCAAgB,EAAC;gBAClC,IAAI,EAAE,4BAAY,CAAC,QAAQ;gBAC3B,SAAS,EAAE,mBAAmB;gBAC9B,GAAG,EAAE,SAAS;gBACd,WAAW,EAAE,6HAA6H;gBAC1I,QAAQ,EAAE,MAAM;gBAChB,GAAG,EAAE,8CAA8C;gBACnD,iBAAiB,EAAE,iDAAiD;aACrE,CAAC;SACH;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,cAAc,EAAE;wBACd,IAAI,EAAE,QAAQ;wBACd,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC;qBAC9B;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,cAAc,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC;IAC3C,MAAM,CAAC,OAAO;QACZ,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QAElC,sBAAsB;QACtB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,KAAK,CAAC;QAEvD,MAAM,SAAS,GAA2B;YACxC,GAAG,EAAE,mBAAmB;YACxB,IAAI,EAAE,WAAW;YACjB,IAAI,EAAE,gBAAgB;SACvB,CAAC;QAEF,MAAM,cAAc,GAAG,SAAS,CAAC,cAAc,CAAC,CAAC;QAEjD,OAAO;YACL,OAAO,CAAC,IAAsB;gBAC5B,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBAEf,iCAAiC;gBACjC,IAAI,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;gBACzC,IAAI,KAAK,GAAG,KAAK,CAAC;gBAElB,2CAA2C;gBAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;oBAChD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;wBAC5B,KAAK,GAAG,IAAI,CAAC;wBACb,MAAM;oBACR,CAAC;oBACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;oBACpC,IAAI,SAAS,KAAK,GAAG;wBAAE,MAAM;oBAC7B,GAAG,GAAG,SAAS,CAAC;gBAClB,CAAC;gBAED,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,OAAO,CAAC,MAAM,CAAC;wBACb,IAAI;wBACJ,SAAS,EAAE,mBAAmB;wBAC9B,IAAI,EAAE;4BACJ,cAAc;4BACd,QAAQ,EAAE,cAAc;yBACzB;qBACF,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;SACF,CAAC;IACJ,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
export interface Options {
|
|
7
|
+
}
|
|
8
|
+
type RuleOptions = [Options?];
|
|
9
|
+
export declare const noArbitraryFileAccess: import("@typescript-eslint/utils/ts-eslint").RuleModule<"violationDetected", RuleOptions, unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,201 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.noArbitraryFileAccess = void 0;
|
|
9
|
+
/**
|
|
10
|
+
* @fileoverview Prevent file access from user input
|
|
11
|
+
*
|
|
12
|
+
* False Positive Reduction:
|
|
13
|
+
* This rule detects safe patterns including:
|
|
14
|
+
* - path.basename() sanitization
|
|
15
|
+
* - path.join() with validated base directories
|
|
16
|
+
* - startsWith() validation guards
|
|
17
|
+
* - Early-return throw patterns
|
|
18
|
+
*/
|
|
19
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
20
|
+
exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
21
|
+
name: 'no-arbitrary-file-access',
|
|
22
|
+
meta: {
|
|
23
|
+
type: 'problem',
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Prevent file access from user input',
|
|
26
|
+
},
|
|
27
|
+
messages: {
|
|
28
|
+
violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
|
|
29
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
30
|
+
issueName: 'Arbitrary File Access',
|
|
31
|
+
cwe: 'CWE-22',
|
|
32
|
+
description: 'File path from user input - path traversal vulnerability',
|
|
33
|
+
severity: 'HIGH',
|
|
34
|
+
fix: 'Validate and sanitize file paths, use allowlists',
|
|
35
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/22.html',
|
|
36
|
+
})
|
|
37
|
+
},
|
|
38
|
+
schema: [],
|
|
39
|
+
},
|
|
40
|
+
defaultOptions: [],
|
|
41
|
+
create(context) {
|
|
42
|
+
const sourceCode = context.sourceCode;
|
|
43
|
+
function report(node) {
|
|
44
|
+
context.report({ node, messageId: 'violationDetected' });
|
|
45
|
+
}
|
|
46
|
+
const fsReadMethods = ['readFile', 'readFileSync', 'readdir', 'readdirSync', 'stat', 'statSync'];
|
|
47
|
+
const fsWriteMethods = ['writeFile', 'writeFileSync', 'appendFile', 'appendFileSync'];
|
|
48
|
+
const userInputSources = ['req', 'request', 'params', 'query', 'body'];
|
|
49
|
+
// Track variables that have been sanitized with path.basename()
|
|
50
|
+
const sanitizedVariables = new Set();
|
|
51
|
+
// Track variables that have been validated with startsWith() guards
|
|
52
|
+
const validatedVariables = new Set();
|
|
53
|
+
/**
|
|
54
|
+
* Check if a variable is assigned from path.basename() or path.join() with basename
|
|
55
|
+
*/
|
|
56
|
+
function checkVariableDeclaration(node) {
|
|
57
|
+
if (node.id.type !== 'Identifier' || !node.init) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const varName = node.id.name;
|
|
61
|
+
const init = node.init;
|
|
62
|
+
// Check for path.basename() assignment
|
|
63
|
+
if (init.type === 'CallExpression' &&
|
|
64
|
+
init.callee.type === 'MemberExpression' &&
|
|
65
|
+
init.callee.object.type === 'Identifier' &&
|
|
66
|
+
init.callee.object.name === 'path' &&
|
|
67
|
+
init.callee.property.type === 'Identifier' &&
|
|
68
|
+
init.callee.property.name === 'basename') {
|
|
69
|
+
sanitizedVariables.add(varName);
|
|
70
|
+
}
|
|
71
|
+
// Check for path.join() with a sanitized variable or literal base
|
|
72
|
+
if (init.type === 'CallExpression' &&
|
|
73
|
+
init.callee.type === 'MemberExpression' &&
|
|
74
|
+
init.callee.object.type === 'Identifier' &&
|
|
75
|
+
init.callee.object.name === 'path' &&
|
|
76
|
+
init.callee.property.type === 'Identifier' &&
|
|
77
|
+
init.callee.property.name === 'join') {
|
|
78
|
+
// Check if any argument is a sanitized variable
|
|
79
|
+
const hasSanitizedArg = init.arguments.some((arg) => arg.type === 'Identifier' && sanitizedVariables.has(arg.name));
|
|
80
|
+
// Check if first arg is a safe base (literal or known safe variable)
|
|
81
|
+
const firstArg = init.arguments[0];
|
|
82
|
+
const hasSafeBase = firstArg && (firstArg.type === 'Literal' ||
|
|
83
|
+
(firstArg.type === 'Identifier' && /^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name)));
|
|
84
|
+
if (hasSanitizedArg && hasSafeBase) {
|
|
85
|
+
sanitizedVariables.add(varName);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if there's a startsWith() guard validation for this variable
|
|
91
|
+
* Looks for patterns like:
|
|
92
|
+
* if (!path.startsWith(baseDir)) { throw ... }
|
|
93
|
+
* if (!path.startsWith(baseDir)) { return ... }
|
|
94
|
+
*/
|
|
95
|
+
function hasStartsWithGuard(node, varName) {
|
|
96
|
+
// Already validated
|
|
97
|
+
if (validatedVariables.has(varName)) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
// Walk up to find the containing block or function
|
|
101
|
+
let current = node.parent;
|
|
102
|
+
while (current) {
|
|
103
|
+
// If we've reached a function body or block, search its statements
|
|
104
|
+
if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement) {
|
|
105
|
+
const statements = current.body;
|
|
106
|
+
// Look for IF statements in this block that validate our variable
|
|
107
|
+
for (const stmt of statements) {
|
|
108
|
+
if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
109
|
+
const testText = sourceCode.getText(stmt.test).toLowerCase();
|
|
110
|
+
// Check for startsWith() validation pattern with our variable
|
|
111
|
+
if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
|
|
112
|
+
// Check if this is a guard clause (negated condition with throw/return)
|
|
113
|
+
const consequent = stmt.consequent;
|
|
114
|
+
// Handle block statement: if (...) { throw/return; }
|
|
115
|
+
if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement && consequent.body.length > 0) {
|
|
116
|
+
const firstStmt = consequent.body[0];
|
|
117
|
+
if (firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
|
|
118
|
+
validatedVariables.add(varName);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Handle direct statement: if (...) throw/return;
|
|
123
|
+
if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || consequent.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
|
|
124
|
+
validatedVariables.add(varName);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Also check if current IS an if statement (when node is inside the consequent)
|
|
132
|
+
if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
133
|
+
const testText = sourceCode.getText(current.test).toLowerCase();
|
|
134
|
+
if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
|
|
135
|
+
validatedVariables.add(varName);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
current = current.parent;
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if a variable comes from a sanitized/validated source
|
|
145
|
+
*/
|
|
146
|
+
function isVariableSafe(varName, node) {
|
|
147
|
+
// Already tracked as sanitized
|
|
148
|
+
if (sanitizedVariables.has(varName)) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
// Has startsWith guard validation
|
|
152
|
+
if (hasStartsWithGuard(node, varName)) {
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
// Check naming conventions that suggest safety
|
|
156
|
+
if (/^(safe|sanitized|validated|clean)/i.test(varName)) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
// Track variable declarations for sanitization patterns
|
|
163
|
+
VariableDeclarator(node) {
|
|
164
|
+
checkVariableDeclaration(node);
|
|
165
|
+
},
|
|
166
|
+
CallExpression(node) {
|
|
167
|
+
// Detect fs.* with user input
|
|
168
|
+
if (node.callee.type === 'MemberExpression' &&
|
|
169
|
+
node.callee.object.type === 'Identifier' &&
|
|
170
|
+
node.callee.object.name === 'fs' &&
|
|
171
|
+
node.callee.property.type === 'Identifier' &&
|
|
172
|
+
[...fsReadMethods, ...fsWriteMethods].includes(node.callee.property.name)) {
|
|
173
|
+
const pathArg = node.arguments[0];
|
|
174
|
+
// Skip if path is a literal (safe)
|
|
175
|
+
if (pathArg && pathArg.type === 'Literal') {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
// Check if path is a variable
|
|
179
|
+
if (pathArg && pathArg.type === 'Identifier') {
|
|
180
|
+
const varName = pathArg.name;
|
|
181
|
+
// Skip if variable is sanitized or validated
|
|
182
|
+
if (isVariableSafe(varName, node)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
report(node);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
// Flag if path is from a member expression (user input sources)
|
|
189
|
+
if (pathArg?.type === 'MemberExpression' &&
|
|
190
|
+
pathArg.object.type === 'Identifier') {
|
|
191
|
+
const objName = pathArg.object.name.toLowerCase();
|
|
192
|
+
if (userInputSources.includes(objName)) {
|
|
193
|
+
report(node);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-arbitrary-file-access/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: no-buffer-overread
|
|
8
|
+
* Detects buffer access beyond bounds (CWE-126)
|
|
9
|
+
*
|
|
10
|
+
* Buffer overread occurs when reading from buffers beyond their allocated
|
|
11
|
+
* length, potentially leading to information disclosure, crashes, or
|
|
12
|
+
* other security issues.
|
|
13
|
+
*
|
|
14
|
+
* False Positive Reduction:
|
|
15
|
+
* This rule uses security utilities to reduce false positives by detecting:
|
|
16
|
+
* - Safe buffer access patterns
|
|
17
|
+
* - Bounds checking operations
|
|
18
|
+
* - JSDoc annotations (@safe, @validated)
|
|
19
|
+
* - Input validation functions
|
|
20
|
+
*/
|
|
21
|
+
import type { TSESLint, SecurityRuleOptions } from '@interlace/eslint-devkit';
|
|
22
|
+
type MessageIds = 'bufferOverread' | 'unsafeBufferAccess' | 'missingBoundsCheck' | 'negativeBufferIndex' | 'userControlledBufferIndex' | 'unsafeBufferSlice' | 'bufferLengthNotChecked' | 'useSafeBufferAccess' | 'validateBufferIndices' | 'checkBufferBounds' | 'strategyBoundsChecking' | 'strategyInputValidation' | 'strategySafeBuffers';
|
|
23
|
+
export interface Options extends SecurityRuleOptions {
|
|
24
|
+
/** Buffer methods to check for bounds safety */
|
|
25
|
+
bufferMethods?: string[];
|
|
26
|
+
/** Functions that validate buffer indices */
|
|
27
|
+
boundsCheckFunctions?: string[];
|
|
28
|
+
/** Buffer types to monitor */
|
|
29
|
+
bufferTypes?: string[];
|
|
30
|
+
/** Additional function names to consider as buffer index validators */
|
|
31
|
+
trustedSanitizers?: string[];
|
|
32
|
+
/** Additional JSDoc annotations to consider as safe markers */
|
|
33
|
+
strictMode?: boolean;
|
|
34
|
+
}
|
|
35
|
+
type RuleOptions = [Options?];
|
|
36
|
+
export declare const noBufferOverread: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
37
|
+
name: string;
|
|
38
|
+
};
|
|
39
|
+
export {};
|