Skip to content

Commit d682314

Browse files
authored
build(lint): disallow node process api, favor Toolkit ChildProcess api #6113
## Problem contributors may use the low-level node `child_process` library, which lacks the instrumentation and robustness of the toolkit `ChildProcess` utility. Example: #5925 (comment) ## Solution - add the node `child_process` library as a "banned import" with a message pointing to our `ChildProcess` utility. - Migrate simple cases to use our `ChildProcess`, mark more complex ones as exceptions. ## Future Work - To migrate the existing cases marked as exceptions, we will need to add functionality to our `ChildProcess` api, such as enforcing a `maxBufferSize` on the output. - Additionally, some of the current uses of `child_process` are tied to implementation details in the `child_process` library that make it hard to change without a larger-scale refactor. These cases were marked as exceptions.
1 parent d9ba5f1 commit d682314

File tree

24 files changed

+70
-51
lines changed

24 files changed

+70
-51
lines changed

.eslintrc.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ module.exports = {
199199
name: 'fs',
200200
message: 'Avoid node:fs and use shared/fs/fs.ts when possible.',
201201
},
202+
{
203+
name: 'child_process',
204+
message:
205+
'Avoid child_process, use ChildProcess from `shared/utilities/processUtils.ts` instead.',
206+
},
202207
],
203208
},
204209
],

packages/core/scripts/build/generateServiceClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import * as proc from 'child_process'
6+
import * as proc from 'child_process' // eslint-disable-line no-restricted-imports
77
import * as nodefs from 'fs' // eslint-disable-line no-restricted-imports
88
import * as path from 'path'
99

packages/core/scripts/test/launchTestUtilities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import * as proc from 'child_process'
6+
import * as proc from 'child_process' // eslint-disable-line no-restricted-imports
77
import packageJson from '../../package.json'
88
import { downloadAndUnzipVSCode, resolveCliArgsFromVSCodeExecutablePath } from '@vscode/test-electron'
99
import { join, resolve } from 'path'

packages/core/src/amazonq/lsp/lspClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import * as vscode from 'vscode'
1111
import * as path from 'path'
1212
import * as nls from 'vscode-nls'
13-
import * as cp from 'child_process'
13+
import { spawn } from 'child_process' // eslint-disable-line no-restricted-imports
1414
import * as crypto from 'crypto'
1515
import * as jose from 'jose'
1616

@@ -199,7 +199,7 @@ export async function activate(extensionContext: ExtensionContext) {
199199

200200
const nodename = process.platform === 'win32' ? 'node.exe' : 'node'
201201

202-
const child = cp.spawn(extensionContext.asAbsolutePath(path.join('resources', nodename)), [
202+
const child = spawn(extensionContext.asAbsolutePath(path.join('resources', nodename)), [
203203
serverModule,
204204
...debugOptions.execArgv,
205205
])

packages/core/src/awsService/accessanalyzer/vue/iamPolicyChecks.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { VueWebview, VueWebviewPanel } from '../../../webviews/main'
1212
import { ExtContext } from '../../../shared/extensions'
1313
import { telemetry } from '../../../shared/telemetry/telemetry'
1414
import { AccessAnalyzer, SharedIniFileCredentials } from 'aws-sdk'
15-
import { execFileSync } from 'child_process'
1615
import { ToolkitError } from '../../../shared/errors'
1716
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../../../shared/filesystemUtilities'
1817
import { globals } from '../../../shared'
@@ -28,6 +27,7 @@ import {
2827
} from './constants'
2928
import { DefaultS3Client, parseS3Uri } from '../../../shared/clients/s3Client'
3029
import { ExpiredTokenException } from '@aws-sdk/client-sso-oidc'
30+
import { ChildProcess } from '../../../shared/utilities/processUtils'
3131

3232
const defaultTerraformConfigPath = 'resources/policychecks-tf-default.yaml'
3333
// Diagnostics for Custom checks are shared
@@ -277,7 +277,7 @@ export class IamPolicyChecksWebview extends VueWebview {
277277
'--config',
278278
`${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`,
279279
]
280-
this.executeValidatePolicyCommand({
280+
await this.executeValidatePolicyCommand({
281281
command,
282282
args,
283283
cfnParameterPathExists: !!cfnParameterPath,
@@ -300,7 +300,7 @@ export class IamPolicyChecksWebview extends VueWebview {
300300
if (cfnParameterPath !== '') {
301301
args.push('--template-configuration-file', `${cfnParameterPath}`)
302302
}
303-
this.executeValidatePolicyCommand({
303+
await this.executeValidatePolicyCommand({
304304
command,
305305
args,
306306
cfnParameterPathExists: !!cfnParameterPath,
@@ -357,7 +357,7 @@ export class IamPolicyChecksWebview extends VueWebview {
357357
'--reference-policy-type',
358358
`${policyType}`,
359359
]
360-
this.executeCustomPolicyChecksCommand({
360+
await this.executeCustomPolicyChecksCommand({
361361
command,
362362
args,
363363
cfnParameterPathExists: !!cfnParameterPath,
@@ -391,7 +391,7 @@ export class IamPolicyChecksWebview extends VueWebview {
391391
if (cfnParameterPath !== '') {
392392
args.push('--template-configuration-file', `${cfnParameterPath}`)
393393
}
394-
this.executeCustomPolicyChecksCommand({
394+
await this.executeCustomPolicyChecksCommand({
395395
command,
396396
args,
397397
cfnParameterPathExists: !!cfnParameterPath,
@@ -454,7 +454,7 @@ export class IamPolicyChecksWebview extends VueWebview {
454454
if (resources !== '') {
455455
args.push('--resources', `${resources}`)
456456
}
457-
this.executeCustomPolicyChecksCommand({
457+
await this.executeCustomPolicyChecksCommand({
458458
command,
459459
args,
460460
cfnParameterPathExists: !!cfnParameterPath,
@@ -489,7 +489,7 @@ export class IamPolicyChecksWebview extends VueWebview {
489489
if (cfnParameterPath !== '') {
490490
args.push('--template-configuration-file', `${cfnParameterPath}`)
491491
}
492-
this.executeCustomPolicyChecksCommand({
492+
await this.executeCustomPolicyChecksCommand({
493493
command,
494494
args,
495495
cfnParameterPathExists: !!cfnParameterPath,
@@ -525,7 +525,7 @@ export class IamPolicyChecksWebview extends VueWebview {
525525
'--config',
526526
`${globals.context.asAbsolutePath(defaultTerraformConfigPath)}`,
527527
]
528-
this.executeCustomPolicyChecksCommand({
528+
await this.executeCustomPolicyChecksCommand({
529529
command,
530530
args,
531531
cfnParameterPathExists: !!cfnParameterPath,
@@ -554,7 +554,7 @@ export class IamPolicyChecksWebview extends VueWebview {
554554
if (cfnParameterPath !== '') {
555555
args.push('--template-configuration-file', `${cfnParameterPath}`)
556556
}
557-
this.executeCustomPolicyChecksCommand({
557+
await this.executeCustomPolicyChecksCommand({
558558
command,
559559
args,
560560
cfnParameterPathExists: !!cfnParameterPath,
@@ -573,16 +573,16 @@ export class IamPolicyChecksWebview extends VueWebview {
573573
}
574574
}
575575

576-
public executeValidatePolicyCommand(opts: PolicyCommandOpts & { policyType?: PolicyChecksPolicyType }) {
577-
telemetry.accessanalyzer_iamPolicyChecksValidatePolicy.run((span) => {
576+
public async executeValidatePolicyCommand(opts: PolicyCommandOpts & { policyType?: PolicyChecksPolicyType }) {
577+
await telemetry.accessanalyzer_iamPolicyChecksValidatePolicy.run(async (span) => {
578578
try {
579579
span.record({
580580
cfnParameterFileUsed: opts.cfnParameterPathExists,
581581
documentType: opts.documentType,
582582
inputPolicyType: opts.policyType ?? 'None',
583583
})
584-
const resp = execFileSync(opts.command, opts.args)
585-
const findingsCount = this.handleValidatePolicyCliResponse(resp.toString())
584+
const result = await ChildProcess.run(opts.command, opts.args, { collect: true })
585+
const findingsCount = this.handleValidatePolicyCliResponse(result.stdout)
586586
span.record({
587587
findingsCount: findingsCount,
588588
})
@@ -633,10 +633,10 @@ export class IamPolicyChecksWebview extends VueWebview {
633633
return findingsCount
634634
}
635635

636-
public executeCustomPolicyChecksCommand(
636+
public async executeCustomPolicyChecksCommand(
637637
opts: PolicyCommandOpts & { checkType: PolicyChecksCheckType; referencePolicyType?: PolicyChecksPolicyType }
638638
) {
639-
telemetry.accessanalyzer_iamPolicyChecksCustomChecks.run((span) => {
639+
await telemetry.accessanalyzer_iamPolicyChecksCustomChecks.run(async (span) => {
640640
try {
641641
span.record({
642642
cfnParameterFileUsed: opts.cfnParameterPathExists,
@@ -645,8 +645,8 @@ export class IamPolicyChecksWebview extends VueWebview {
645645
inputPolicyType: 'None', // Note: This will change once JSON policy language is enabled for Custom policy checks
646646
referencePolicyType: opts.referencePolicyType ?? 'None',
647647
})
648-
const resp = execFileSync(opts.command, opts.args)
649-
const findingsCount = this.handleCustomPolicyChecksCliResponse(resp.toString())
648+
const resp = await ChildProcess.run(opts.command, opts.args)
649+
const findingsCount = this.handleCustomPolicyChecksCliResponse(resp.stdout)
650650
span.record({
651651
findingsCount: findingsCount,
652652
})
@@ -790,7 +790,7 @@ export async function renderIamPolicyChecks(context: ExtContext): Promise<VueWeb
790790
checkAccessNotGrantedResourcesTextArea,
791791
customChecksFileErrorMessage,
792792
cfnParameterPath: cfnParameterPath ? cfnParameterPath : '',
793-
pythonToolsInstalled: arePythonToolsInstalled(),
793+
pythonToolsInstalled: await arePythonToolsInstalled(),
794794
},
795795
client,
796796
context.regionProvider.defaultRegionId
@@ -828,20 +828,20 @@ export async function _readCustomChecksFile(input: string): Promise<string> {
828828
}
829829

830830
// Check if Cfn and Tf tools are installed
831-
export function arePythonToolsInstalled(): boolean {
831+
export async function arePythonToolsInstalled(): Promise<boolean> {
832832
const logger: Logger = getLogger()
833833
let cfnToolInstalled = true
834834
let tfToolInstalled = true
835835
try {
836-
execFileSync('tf-policy-validator')
836+
await ChildProcess.run('tf-policy-validator')
837837
} catch (err: any) {
838838
if (isProcessNotFoundErr(err.message)) {
839839
tfToolInstalled = false
840840
logger.error('Terraform Policy Validator is not found')
841841
}
842842
}
843843
try {
844-
execFileSync('cfn-policy-validator')
844+
await ChildProcess.run('cfn-policy-validator')
845845
} catch (err: any) {
846846
if (isProcessNotFoundErr(err.message)) {
847847
cfnToolInstalled = false

packages/core/src/codewhisperer/commands/startTestGeneration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
import path from 'path'
1818
import { testGenState } from '..'
1919
import { ChatSessionManager } from '../../amazonqTest/chat/storages/chatSession'
20-
import { ChildProcess, spawn } from 'child_process'
20+
import { ChildProcess, spawn } from 'child_process' // eslint-disable-line no-restricted-imports
2121
import { BuildStatus } from '../../amazonqTest/chat/session/session'
2222
import { fs } from '../../shared/fs/fs'
2323
import { TestGenerationJobStatus } from '../models/constants'

packages/core/src/codewhisperer/service/transformByQ/transformMavenHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import * as vscode from 'vscode'
66
import { FolderInfo, transformByQState } from '../../models/model'
77
import { getLogger } from '../../../shared/logger'
88
import * as CodeWhispererConstants from '../../models/constants'
9-
import { spawnSync } from 'child_process' // Consider using ChildProcess once we finalize all spawnSync calls
9+
// Consider using ChildProcess once we finalize all spawnSync calls
10+
import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports
1011
import { CodeTransformBuildCommand, telemetry } from '../../../shared/telemetry/telemetry'
1112
import { CodeTransformTelemetryState } from '../../../amazonqGumby/telemetry/codeTransformTelemetryState'
1213
import { ToolkitError } from '../../../shared/errors'

packages/core/src/codewhisperer/service/transformByQ/transformProjectValidationHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { BuildSystem, JDKVersion, TransformationCandidateProject } from '../../m
66
import { getLogger } from '../../../shared/logger'
77
import * as CodeWhispererConstants from '../../models/constants'
88
import * as vscode from 'vscode'
9-
import { spawnSync } from 'child_process' // Consider using ChildProcess once we finalize all spawnSync calls
9+
// Consider using ChildProcess once we finalize all spawnSync calls
10+
import { spawnSync } from 'child_process' // eslint-disable-line no-restricted-imports
1011
import {
1112
NoJavaProjectsFoundError,
1213
NoMavenJavaProjectsFoundError,

packages/core/src/lambda/commands/createNewSamApp.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ import {
4545
isCloud9,
4646
getLaunchConfigDocUrl,
4747
} from '../../shared/extensionUtilities'
48-
import { execFileSync } from 'child_process'
4948
import { checklogs } from '../../shared/localizedText'
5049
import globals from '../../shared/extensionGlobals'
5150
import { telemetry } from '../../shared/telemetry/telemetry'
5251
import { LambdaArchitecture, Result, Runtime } from '../../shared/telemetry/telemetry'
5352
import { getTelemetryReason, getTelemetryResult } from '../../shared/errors'
5453
import { openUrl, replaceVscodeVars } from '../../shared/utilities/vsCodeUtils'
5554
import { fs } from '../../shared'
55+
import { ChildProcess } from '../../shared/utilities/processUtils'
5656

5757
export const samInitTemplateFiles: string[] = ['template.yaml', 'template.yml']
5858
export const samInitReadmeFile: string = 'README.TOOLKIT.md'
@@ -218,7 +218,9 @@ export async function createNewSamApplication(
218218
// Needs to be done or else gopls won't start
219219
if (goRuntimes.includes(createRuntime)) {
220220
try {
221-
execFileSync('go', ['mod', 'tidy'], { cwd: path.join(path.dirname(templateUri.fsPath), 'hello-world') })
221+
await ChildProcess.run('go', ['mod', 'tidy'], {
222+
spawnOptions: { cwd: path.join(path.dirname(templateUri.fsPath), 'hello-world') },
223+
})
222224
} catch (err) {
223225
getLogger().warn(
224226
localize(

packages/core/src/shared/extensions/git.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import * as vscode from 'vscode'
77
import * as GitTypes from '../../../types/git.d'
88
import { SemVer, parse as semverParse } from 'semver'
9-
import { execFile } from 'child_process'
9+
import { execFile } from 'child_process' // eslint-disable-line no-restricted-imports
1010
import { promisify } from 'util'
1111
import { VSCODE_EXTENSION_ID } from '../extensions'
1212
import { makeTemporaryToolkitFolder, tryRemoveFolder } from '../filesystemUtilities'

0 commit comments

Comments
 (0)