eslint-plugin-node-security 4.8.0 → 4.9.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 -1
- package/package.json +3 -3
- package/src/index.js +1 -95
- package/src/oxlint.js +1 -3
- package/src/rules/detect-child-process/index.js +1 -625
- package/src/rules/detect-eval-with-expression/index.js +1 -364
- package/src/rules/detect-non-literal-fs-filename/index.js +1 -516
- package/src/rules/detect-suspicious-dependencies/index.js +1 -69
- package/src/rules/lock-file/index.js +1 -92
- package/src/rules/no-arbitrary-file-access/index.js +1 -152
- package/src/rules/no-buffer-overread/index.js +1 -543
- package/src/rules/no-cryptojs/index.js +1 -99
- package/src/rules/no-cryptojs-weak-random/index.js +1 -103
- package/src/rules/no-data-in-temp-storage/index.js +1 -85
- package/src/rules/no-deprecated-buffer/index.js +1 -84
- package/src/rules/no-deprecated-cipher-method/index.js +1 -112
- package/src/rules/no-dynamic-algorithm-selection/index.js +1 -72
- package/src/rules/no-dynamic-command-string/index.js +1 -201
- package/src/rules/no-dynamic-dependency-loading/index.js +1 -45
- package/src/rules/no-dynamic-require/index.js +1 -96
- package/src/rules/no-ecb-mode/index.js +1 -108
- package/src/rules/no-insecure-key-derivation/index.js +1 -109
- package/src/rules/no-insecure-rsa-padding/index.js +1 -104
- package/src/rules/no-math-random-crypto/index.js +1 -192
- package/src/rules/no-self-signed-certs/index.js +1 -110
- package/src/rules/no-sha1-hash/index.js +1 -121
- package/src/rules/no-shell-injection/index.js +1 -68
- package/src/rules/no-ssrf/index.js +1 -221
- package/src/rules/no-static-iv/index.js +1 -129
- package/src/rules/no-timing-unsafe-compare/index.js +1 -106
- package/src/rules/no-toctou-vulnerability/index.js +1 -195
- package/src/rules/no-unsafe-buffer-alloc/index.js +1 -87
- package/src/rules/no-unsafe-dynamic-require/index.js +1 -93
- package/src/rules/no-weak-cipher-algorithm/index.js +1 -174
- package/src/rules/no-weak-hash-algorithm/index.js +1 -199
- package/src/rules/no-zip-slip/index.js +1 -410
- package/src/rules/prefer-native-crypto/index.js +1 -119
- package/src/rules/require-dependency-integrity/index.js +1 -62
- package/src/rules/require-secure-credential-storage/index.js +1 -45
- package/src/rules/require-secure-deletion/index.js +1 -82
- package/src/rules/require-storage-encryption/index.js +1 -45
- package/src/types/index.js +1 -2
|
@@ -1,92 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.lockFile = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
6
|
-
name: 'lock-file',
|
|
7
|
-
meta: {
|
|
8
|
-
type: 'suggestion',
|
|
9
|
-
docs: {
|
|
10
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/lock-file.md',
|
|
11
|
-
description: 'Ensure package lock file exists for the configured package manager',
|
|
12
|
-
cwe: 'CWE-829',
|
|
13
|
-
cvss: 7.5,
|
|
14
|
-
},
|
|
15
|
-
messages: {
|
|
16
|
-
violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
18
|
-
issueName: 'Lock File Missing',
|
|
19
|
-
cwe: 'CWE-829',
|
|
20
|
-
description: 'Package lock file missing ({{ lockFile }}) for {{ packageManager }}. Commit the lock file to ensure supply chain integrity.',
|
|
21
|
-
severity: 'HIGH',
|
|
22
|
-
fix: 'Generate and commit the {{ lockFile }} file.',
|
|
23
|
-
documentationLink: 'https://cwe.mitre.org/data/definitions/829.html',
|
|
24
|
-
})
|
|
25
|
-
},
|
|
26
|
-
schema: [
|
|
27
|
-
{
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: {
|
|
30
|
-
packageManager: {
|
|
31
|
-
type: 'string',
|
|
32
|
-
enum: ['npm', 'yarn', 'pnpm'], description: 'Package manager whose lock file is required'
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
additionalProperties: false,
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
defaultOptions: [{}],
|
|
40
|
-
create(context) {
|
|
41
|
-
const fs = require('node:fs');
|
|
42
|
-
const path = require('node:path');
|
|
43
|
-
let checked = false;
|
|
44
|
-
const options = context.options[0] || {};
|
|
45
|
-
const userPackageManager = options.packageManager;
|
|
46
|
-
const lockFiles = {
|
|
47
|
-
npm: 'package-lock.json',
|
|
48
|
-
yarn: 'yarn.lock',
|
|
49
|
-
pnpm: 'pnpm-lock.yaml',
|
|
50
|
-
};
|
|
51
|
-
const targetLockFiles = userPackageManager
|
|
52
|
-
? [lockFiles[userPackageManager]]
|
|
53
|
-
: Object.values(lockFiles);
|
|
54
|
-
const targetLockFile = userPackageManager
|
|
55
|
-
? lockFiles[userPackageManager]
|
|
56
|
-
: 'package-lock.json | yarn.lock | pnpm-lock.yaml';
|
|
57
|
-
const reportedManager = userPackageManager ?? 'any';
|
|
58
|
-
return {
|
|
59
|
-
Program(node) {
|
|
60
|
-
if (checked)
|
|
61
|
-
return;
|
|
62
|
-
checked = true;
|
|
63
|
-
let dir = path.dirname(context.filename);
|
|
64
|
-
let found = false;
|
|
65
|
-
for (let i = 0; i < 10; i++) {
|
|
66
|
-
for (const lf of targetLockFiles) {
|
|
67
|
-
if (fs.existsSync(path.join(dir, lf))) {
|
|
68
|
-
found = true;
|
|
69
|
-
break;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (found)
|
|
73
|
-
break;
|
|
74
|
-
const parentDir = path.dirname(dir);
|
|
75
|
-
if (parentDir === dir)
|
|
76
|
-
break;
|
|
77
|
-
dir = parentDir;
|
|
78
|
-
}
|
|
79
|
-
if (!found) {
|
|
80
|
-
context.report({
|
|
81
|
-
node,
|
|
82
|
-
messageId: 'violationDetected',
|
|
83
|
-
data: {
|
|
84
|
-
packageManager: reportedManager,
|
|
85
|
-
lockFile: targetLockFile,
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.lockFile=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.lockFile=(0,eslint_devkit_1.createRule)({name:"lock-file",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/lock-file.md",description:"Ensure package lock file exists for the configured package manager",cwe:"CWE-829",cvss:7.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Lock File Missing",cwe:"CWE-829",description:"Package lock file missing ({{ lockFile }}) for {{ packageManager }}. Commit the lock file to ensure supply chain integrity.",severity:"HIGH",fix:"Generate and commit the {{ lockFile }} file.",documentationLink:"https://cwe.mitre.org/data/definitions/829.html"})},schema:[{type:"object",properties:{packageManager:{type:"string",enum:["npm","yarn","pnpm"],description:"Package manager whose lock file is required"}},additionalProperties:false}]},defaultOptions:[{}],create(context){const fs=require("node:fs");const path=require("node:path");let checked=false;const options=context.options[0]||{};const userPackageManager=options.packageManager;const lockFiles={npm:"package-lock.json",yarn:"yarn.lock",pnpm:"pnpm-lock.yaml"};const targetLockFiles=userPackageManager?[lockFiles[userPackageManager]]:Object.values(lockFiles);const targetLockFile=userPackageManager?lockFiles[userPackageManager]:"package-lock.json | yarn.lock | pnpm-lock.yaml";const reportedManager=userPackageManager??"any";return{Program(node){if(checked)return;checked=true;let dir=path.dirname(context.filename);let found=false;for(let i=0;i<10;i++){for(const lf of targetLockFiles){if(fs.existsSync(path.join(dir,lf))){found=true;break}}if(found)break;const parentDir=path.dirname(dir);if(parentDir===dir)break;dir=parentDir}if(!found){context.report({node,messageId:"violationDetected",data:{packageManager:reportedManager,lockFile:targetLockFile}})}}}}});
|
|
@@ -1,152 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.noArbitraryFileAccess = void 0;
|
|
4
|
-
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
|
-
exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
6
|
-
name: 'no-arbitrary-file-access',
|
|
7
|
-
meta: {
|
|
8
|
-
type: 'problem',
|
|
9
|
-
docs: {
|
|
10
|
-
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-arbitrary-file-access.md',
|
|
11
|
-
description: 'Prevent file access from user input',
|
|
12
|
-
cwe: 'CWE-22',
|
|
13
|
-
cvss: 7.5,
|
|
14
|
-
},
|
|
15
|
-
messages: {
|
|
16
|
-
violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
|
|
17
|
-
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
18
|
-
issueName: 'Arbitrary File Access',
|
|
19
|
-
cwe: 'CWE-22',
|
|
20
|
-
description: 'File path from user input - path traversal vulnerability',
|
|
21
|
-
severity: 'HIGH',
|
|
22
|
-
fix: 'Validate and sanitize file paths, use allowlists',
|
|
23
|
-
documentationLink: 'https://cwe.mitre.org/data/definitions/22.html',
|
|
24
|
-
})
|
|
25
|
-
},
|
|
26
|
-
schema: [],
|
|
27
|
-
},
|
|
28
|
-
defaultOptions: [],
|
|
29
|
-
create(context) {
|
|
30
|
-
const sourceCode = context.sourceCode;
|
|
31
|
-
function report(node) {
|
|
32
|
-
context.report({ node, messageId: 'violationDetected' });
|
|
33
|
-
}
|
|
34
|
-
const fsReadMethods = ['readFile', 'readFileSync', 'readdir', 'readdirSync', 'stat', 'statSync'];
|
|
35
|
-
const fsWriteMethods = ['writeFile', 'writeFileSync', 'appendFile', 'appendFileSync'];
|
|
36
|
-
const userInputSources = new Set(['req', 'request', 'params', 'query', 'body']);
|
|
37
|
-
const sanitizedVariables = new Set();
|
|
38
|
-
const validatedVariables = new Set();
|
|
39
|
-
function checkVariableDeclaration(node) {
|
|
40
|
-
if (node.id.type !== 'Identifier' || !node.init) {
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const varName = node.id.name;
|
|
44
|
-
const init = node.init;
|
|
45
|
-
if (init.type === 'CallExpression' &&
|
|
46
|
-
init.callee.type === 'MemberExpression' &&
|
|
47
|
-
init.callee.object.type === 'Identifier' &&
|
|
48
|
-
init.callee.object.name === 'path' &&
|
|
49
|
-
init.callee.property.type === 'Identifier' &&
|
|
50
|
-
init.callee.property.name === 'basename') {
|
|
51
|
-
sanitizedVariables.add(varName);
|
|
52
|
-
}
|
|
53
|
-
if (init.type === 'CallExpression' &&
|
|
54
|
-
init.callee.type === 'MemberExpression' &&
|
|
55
|
-
init.callee.object.type === 'Identifier' &&
|
|
56
|
-
init.callee.object.name === 'path' &&
|
|
57
|
-
init.callee.property.type === 'Identifier' &&
|
|
58
|
-
init.callee.property.name === 'join') {
|
|
59
|
-
const hasSanitizedArg = init.arguments.some((arg) => arg.type === 'Identifier' && sanitizedVariables.has(arg.name));
|
|
60
|
-
const firstArg = init.arguments[0];
|
|
61
|
-
const hasSafeBase = firstArg && (firstArg.type === 'Literal' ||
|
|
62
|
-
(firstArg.type === 'Identifier' && /^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name)));
|
|
63
|
-
if (hasSanitizedArg && hasSafeBase) {
|
|
64
|
-
sanitizedVariables.add(varName);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
function hasStartsWithGuard(node, varName) {
|
|
69
|
-
if (validatedVariables.has(varName)) {
|
|
70
|
-
return true;
|
|
71
|
-
}
|
|
72
|
-
let current = node.parent;
|
|
73
|
-
while (current) {
|
|
74
|
-
if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement) {
|
|
75
|
-
const statements = current.body;
|
|
76
|
-
for (const stmt of statements) {
|
|
77
|
-
if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
78
|
-
const testText = sourceCode.getText(stmt.test).toLowerCase();
|
|
79
|
-
if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
|
|
80
|
-
const consequent = stmt.consequent;
|
|
81
|
-
if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement && consequent.body.length > 0) {
|
|
82
|
-
const firstStmt = consequent.body[0];
|
|
83
|
-
if (firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
|
|
84
|
-
validatedVariables.add(varName);
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || consequent.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
|
|
89
|
-
validatedVariables.add(varName);
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
97
|
-
const testText = sourceCode.getText(current.test).toLowerCase();
|
|
98
|
-
if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
|
|
99
|
-
validatedVariables.add(varName);
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
current = current.parent;
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
}
|
|
107
|
-
function isVariableSafe(varName, node) {
|
|
108
|
-
if (sanitizedVariables.has(varName)) {
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
if (hasStartsWithGuard(node, varName)) {
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
if (/^(safe|sanitized|validated|clean)/i.test(varName)) {
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
return false;
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
VariableDeclarator(node) {
|
|
121
|
-
checkVariableDeclaration(node);
|
|
122
|
-
},
|
|
123
|
-
CallExpression(node) {
|
|
124
|
-
if (node.callee.type === 'MemberExpression' &&
|
|
125
|
-
node.callee.object.type === 'Identifier' &&
|
|
126
|
-
node.callee.object.name === 'fs' &&
|
|
127
|
-
node.callee.property.type === 'Identifier' &&
|
|
128
|
-
[...fsReadMethods, ...fsWriteMethods].includes(node.callee.property.name)) {
|
|
129
|
-
const pathArg = node.arguments[0];
|
|
130
|
-
if (pathArg && pathArg.type === 'Literal') {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (pathArg && pathArg.type === 'Identifier') {
|
|
134
|
-
const varName = pathArg.name;
|
|
135
|
-
if (isVariableSafe(varName, node)) {
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
report(node);
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
if (pathArg?.type === 'MemberExpression' &&
|
|
142
|
-
pathArg.object.type === 'Identifier') {
|
|
143
|
-
const objName = pathArg.object.name.toLowerCase();
|
|
144
|
-
if (userInputSources.has(objName)) {
|
|
145
|
-
report(node);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
},
|
|
152
|
-
});
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noArbitraryFileAccess=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noArbitraryFileAccess=(0,eslint_devkit_1.createRule)({name:"no-arbitrary-file-access",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-arbitrary-file-access.md",description:"Prevent file access from user input",cwe:"CWE-22",cvss:7.5},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Arbitrary File Access",cwe:"CWE-22",description:"File path from user input - path traversal vulnerability",severity:"HIGH",fix:"Validate and sanitize file paths, use allowlists",documentationLink:"https://cwe.mitre.org/data/definitions/22.html"})},schema:[]},defaultOptions:[],create(context){const sourceCode=context.sourceCode;function report(node){context.report({node,messageId:"violationDetected"})}const fsReadMethods=["readFile","readFileSync","readdir","readdirSync","stat","statSync"];const fsWriteMethods=["writeFile","writeFileSync","appendFile","appendFileSync"];const userInputSources=new Set(["req","request","params","query","body"]);const sanitizedVariables=new Set;const validatedVariables=new Set;function checkVariableDeclaration(node){if(node.id.type!=="Identifier"||!node.init){return}const varName=node.id.name;const init=node.init;if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&init.callee.object.name==="path"&&init.callee.property.type==="Identifier"&&init.callee.property.name==="basename"){sanitizedVariables.add(varName)}if(init.type==="CallExpression"&&init.callee.type==="MemberExpression"&&init.callee.object.type==="Identifier"&&init.callee.object.name==="path"&&init.callee.property.type==="Identifier"&&init.callee.property.name==="join"){const hasSanitizedArg=init.arguments.some(arg=>arg.type==="Identifier"&&sanitizedVariables.has(arg.name));const firstArg=init.arguments[0];const hasSafeBase=firstArg&&(firstArg.type==="Literal"||firstArg.type==="Identifier"&&/^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name));if(hasSanitizedArg&&hasSafeBase){sanitizedVariables.add(varName)}}}function hasStartsWithGuard(node,varName){if(validatedVariables.has(varName)){return true}let current=node.parent;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement){const statements=current.body;for(const stmt of statements){if(stmt.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const testText=sourceCode.getText(stmt.test).toLowerCase();if(testText.includes("startswith")&&testText.includes(varName.toLowerCase())){const consequent=stmt.consequent;if(consequent.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement&&consequent.body.length>0){const firstStmt=consequent.body[0];if(firstStmt.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement||firstStmt.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){validatedVariables.add(varName);return true}}if(consequent.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement||consequent.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement){validatedVariables.add(varName);return true}}}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const testText=sourceCode.getText(current.test).toLowerCase();if(testText.includes("startswith")&&testText.includes(varName.toLowerCase())){validatedVariables.add(varName);return true}}current=current.parent}return false}function isVariableSafe(varName,node){if(sanitizedVariables.has(varName)){return true}if(hasStartsWithGuard(node,varName)){return true}if(/^(safe|sanitized|validated|clean)/i.test(varName)){return true}return false}return{VariableDeclarator(node){checkVariableDeclaration(node)},CallExpression(node){if(node.callee.type==="MemberExpression"&&node.callee.object.type==="Identifier"&&node.callee.object.name==="fs"&&node.callee.property.type==="Identifier"&&[...fsReadMethods,...fsWriteMethods].includes(node.callee.property.name)){const pathArg=node.arguments[0];if(pathArg&&pathArg.type==="Literal"){return}if(pathArg&&pathArg.type==="Identifier"){const varName=pathArg.name;if(isVariableSafe(varName,node)){return}report(node);return}if(pathArg?.type==="MemberExpression"&&pathArg.object.type==="Identifier"){const objName=pathArg.object.name.toLowerCase();if(userInputSources.has(objName)){report(node)}}}}}}});
|