Skip to content

Commit 64ae529

Browse files
authored
feat: improve detect-child-process rule (#108)
* improve detect-child-process rule * refactor * add tests
1 parent d699c30 commit 64ae529

File tree

2 files changed

+123
-62
lines changed

2 files changed

+123
-62
lines changed

rules/detect-child-process.js

Lines changed: 27 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
'use strict';
77

8+
const { getImportAccessPath } = require('../utils/import-utils');
9+
const childProcessPackageNames = ['child_process', 'node:child_process'];
10+
811
//------------------------------------------------------------------------------
912
// Rule Definition
1013
//------------------------------------------------------------------------------
@@ -20,54 +23,37 @@ module.exports = {
2023
},
2124
},
2225
create: function (context) {
23-
/*
24-
* Stores variable identifiers pointing to child_process to check (child_process).exec()
25-
*/
26-
const childProcessIdentifiers = new Set();
27-
28-
/**
29-
* Extract identifiers assigned the expression `require("child_process")`.
30-
* @param {Pattern} node
31-
*/
32-
function extractChildProcessIdentifiers(node) {
33-
if (node.type !== 'Identifier') {
34-
return;
35-
}
36-
const variable = context.getScope().set.get(node.name);
37-
if (!variable) {
38-
return;
39-
}
40-
for (const reference of variable.references) {
41-
childProcessIdentifiers.add(reference.identifier);
42-
}
43-
}
44-
4526
return {
4627
CallExpression: function (node) {
4728
if (node.callee.name === 'require') {
4829
const args = node.arguments[0];
49-
if (args && args.type === 'Literal' && args.value === 'child_process') {
50-
let pattern;
51-
if (node.parent.type === 'VariableDeclarator') {
52-
pattern = node.parent.id;
53-
} else if (node.parent.type === 'AssignmentExpression' && node.parent.operator === '=') {
54-
pattern = node.parent.left;
55-
}
56-
if (pattern) {
57-
extractChildProcessIdentifiers(pattern);
58-
}
59-
if (!pattern || pattern.type === 'Identifier') {
60-
return context.report({ node: node, message: 'Found require("child_process")' });
61-
}
30+
if (
31+
args &&
32+
args.type === 'Literal' &&
33+
childProcessPackageNames.includes(args.value) &&
34+
node.parent.type !== 'VariableDeclarator' &&
35+
node.parent.type !== 'AssignmentExpression' &&
36+
node.parent.type !== 'MemberExpression'
37+
) {
38+
context.report({ node: node, message: 'Found require("' + args.value + '")' });
6239
}
40+
return;
6341
}
64-
},
65-
MemberExpression: function (node) {
66-
if (node.property.name === 'exec' && childProcessIdentifiers.has(node.object)) {
67-
if (node.parent && node.parent.arguments && node.parent.arguments.length && node.parent.arguments[0].type !== 'Literal') {
68-
return context.report({ node: node, message: 'Found child_process.exec() with non Literal first argument' });
69-
}
42+
43+
// Reports non-literal `exec()` calls.
44+
if (!node.arguments.length || node.arguments[0].type === 'Literal') {
45+
return;
46+
}
47+
const pathInfo = getImportAccessPath({
48+
node: node.callee,
49+
scope: context.getScope(),
50+
packageNames: childProcessPackageNames,
51+
});
52+
const fnName = pathInfo && pathInfo.path.length === 1 && pathInfo.path[0];
53+
if (fnName !== 'exec') {
54+
return;
7055
}
56+
context.report({ node: node, message: 'Found child_process.exec() with non Literal first argument' });
7157
},
7258
};
7359
},

test/detect-child-process.js

Lines changed: 96 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,136 @@
11
'use strict';
22

33
const RuleTester = require('eslint').RuleTester;
4-
const tester = new RuleTester();
4+
const tester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 6,
7+
sourceType: 'module',
8+
},
9+
});
510

611
const ruleName = 'detect-child-process';
712
const rule = require(`../rules/${ruleName}`);
813

914
tester.run(ruleName, rule, {
1015
valid: [
1116
"child_process.exec('ls')",
12-
{
13-
code: `
14-
var {} = require('child_process');
15-
var result = /hello/.exec(str);`,
16-
parserOptions: { ecmaVersion: 6 },
17-
},
18-
{
19-
code: "var { spawn } = require('child_process'); spawn(str);",
20-
parserOptions: { ecmaVersion: 6 },
21-
},
17+
`
18+
var {} = require('child_process');
19+
var result = /hello/.exec(str);`,
20+
`
21+
var {} = require('node:child_process');
22+
var result = /hello/.exec(str);`,
23+
`
24+
import {} from 'child_process';
25+
var result = /hello/.exec(str);`,
26+
`
27+
import {} from 'node:child_process';
28+
var result = /hello/.exec(str);`,
29+
"var { spawn } = require('child_process'); spawn(str);",
30+
"var { spawn } = require('node:child_process'); spawn(str);",
31+
"import { spawn } from 'child_process'; spawn(str);",
32+
"import { spawn } from 'node:child_process'; spawn(str);",
33+
`
34+
var foo = require('child_process');
35+
function fn () {
36+
var foo = /hello/;
37+
var result = foo.exec(str);
38+
}`,
39+
"var child = require('child_process'); child.spawn(str)",
40+
"var child = require('node:child_process'); child.spawn(str)",
41+
"import child from 'child_process'; child.spawn(str)",
42+
"import child from 'node:child_process'; child.spawn(str)",
43+
`
44+
var foo = require('child_process');
45+
function fn () {
46+
var result = foo.spawn(str);
47+
}`,
48+
"require('child_process').spawn(str)",
49+
`
50+
function fn () {
51+
require('child_process').spawn(str)
52+
}`,
2253
],
2354
invalid: [
2455
{
2556
code: "require('child_process')",
2657
errors: [{ message: 'Found require("child_process")' }],
2758
},
59+
{
60+
code: "require('node:child_process')",
61+
errors: [{ message: 'Found require("node:child_process")' }],
62+
},
2863
{
2964
code: "var child = require('child_process'); child.exec(com)",
30-
errors: [{ message: 'Found require("child_process")' }, { message: 'Found child_process.exec() with non Literal first argument' }],
65+
errors: [{ message: 'Found child_process.exec() with non Literal first argument' }],
3166
},
3267
{
33-
code: "var child = require('child_process'); child.exec()",
34-
errors: [{ message: 'Found require("child_process")' }],
68+
code: "var child = require('node:child_process'); child.exec(com)",
69+
errors: [{ message: 'Found child_process.exec() with non Literal first argument' }],
70+
},
71+
{
72+
code: "import child from 'child_process'; child.exec(com)",
73+
errors: [{ message: 'Found child_process.exec() with non Literal first argument' }],
74+
},
75+
{
76+
code: "import child from 'node:child_process'; child.exec(com)",
77+
errors: [{ message: 'Found child_process.exec() with non Literal first argument' }],
3578
},
3679
{
3780
code: "var child = sinon.stub(require('child_process')); child.exec.returns({});",
3881
errors: [{ message: 'Found require("child_process")' }],
3982
},
83+
{
84+
code: "var child = sinon.stub(require('node:child_process')); child.exec.returns({});",
85+
errors: [{ message: 'Found require("node:child_process")' }],
86+
},
4087
{
4188
code: `
4289
var foo = require('child_process');
4390
function fn () {
4491
var result = foo.exec(str);
4592
}`,
46-
errors: [
47-
{ message: 'Found require("child_process")', line: 2 },
48-
{ message: 'Found child_process.exec() with non Literal first argument', line: 4 },
49-
],
93+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 4 }],
5094
},
5195
{
5296
code: `
53-
var foo = require('child_process');
97+
import foo from 'child_process';
98+
function fn () {
99+
var result = foo.exec(str);
100+
}`,
101+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 4 }],
102+
},
103+
{
104+
code: `
105+
import foo from 'node:child_process';
54106
function fn () {
55-
var foo = /hello/;
56107
var result = foo.exec(str);
57108
}`,
58-
errors: [{ message: 'Found require("child_process")', line: 2 }],
109+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 4 }],
110+
},
111+
{
112+
code: `
113+
require('child_process').exec(str)`,
114+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 2 }],
115+
},
116+
{
117+
code: `
118+
function fn () {
119+
require('child_process').exec(str)
120+
}`,
121+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 3 }],
122+
},
123+
{
124+
code: `
125+
const {exec} = require('child_process');
126+
exec(str)`,
127+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 3 }],
128+
},
129+
{
130+
code: `
131+
const {exec} = require('node:child_process');
132+
exec(str)`,
133+
errors: [{ message: 'Found child_process.exec() with non Literal first argument', line: 3 }],
59134
},
60135
],
61136
});

0 commit comments

Comments
 (0)