Skip to content

Commit 0300fb0

Browse files
committed
fix(import): validate ARN format, region, and account before extracting resource ID
Previously, --arn was parsed with a blind split('/').pop() with no validation. Now parseAndValidateArn checks the ARN matches the expected format, resource type, and that region/account match the deployment target.
1 parent 3f559f2 commit 0300fb0

4 files changed

Lines changed: 72 additions & 3 deletions

File tree

src/cli/commands/import/__tests__/import-runtime-handler.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ const mockUpdateDeployedState = vi.fn();
2020
const mockCopyAgentSource = vi.fn();
2121
const mockToStackName = vi.fn();
2222

23+
const mockParseAndValidateArn = vi.fn();
24+
2325
vi.mock('../import-utils', () => ({
2426
resolveProjectContext: (...args: unknown[]) => mockResolveProjectContext(...args),
2527
resolveImportTarget: (...args: unknown[]) => mockResolveImportTarget(...args),
2628
updateDeployedState: (...args: unknown[]) => mockUpdateDeployedState(...args),
2729
copyAgentSource: (...args: unknown[]) => mockCopyAgentSource(...args),
2830
toStackName: (...args: unknown[]) => mockToStackName(...args),
31+
parseAndValidateArn: (...args: unknown[]) => mockParseAndValidateArn(...args),
2932
}));
3033

3134
const mockGetAgentRuntimeDetail = vi.fn();
@@ -118,6 +121,13 @@ function setupDefaultMocks() {
118121
account: '123456789012',
119122
});
120123

124+
mockParseAndValidateArn.mockReturnValue({
125+
region: 'us-east-1',
126+
account: '123',
127+
resourceType: 'runtime',
128+
resourceId: 'rt-123',
129+
});
130+
121131
mockConfigIO.readProjectSpec.mockResolvedValue({ ...defaultProjectSpec, runtimes: [] });
122132
}
123133

src/cli/commands/import/import-memory.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { LocalCdkProject } from '../../cdk/local-cdk-project';
55
import { silentIoHost } from '../../cdk/toolkit-lib';
66
import { ExecLogger } from '../../logging';
77
import { bootstrapEnvironment, buildCdkProject, checkBootstrapNeeded, synthesizeCdk } from '../../operations/deploy';
8-
import { resolveImportTarget, resolveProjectContext, toStackName, updateDeployedState } from './import-utils';
8+
import {
9+
parseAndValidateArn,
10+
resolveImportTarget,
11+
resolveProjectContext,
12+
toStackName,
13+
updateDeployedState,
14+
} from './import-utils';
915
import { executePhase1, getDeployedTemplate } from './phase1-update';
1016
import { executePhase2, publishCdkAssets } from './phase2-import';
1117
import type { CfnTemplate } from './template-utils';
@@ -91,7 +97,8 @@ export async function handleImportMemory(options: ImportResourceOptions): Promis
9197
let memoryId: string;
9298

9399
if (options.arn) {
94-
memoryId = options.arn.split('/').pop()!;
100+
const parsed = parseAndValidateArn(options.arn, 'memory', target);
101+
memoryId = parsed.resourceId;
95102
} else {
96103
// List memories and show to user
97104
onProgress('Listing memories in your account...');

src/cli/commands/import/import-runtime.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ExecLogger } from '../../logging';
77
import { bootstrapEnvironment, buildCdkProject, checkBootstrapNeeded, synthesizeCdk } from '../../operations/deploy';
88
import {
99
copyAgentSource,
10+
parseAndValidateArn,
1011
resolveImportTarget,
1112
resolveProjectContext,
1213
toStackName,
@@ -105,7 +106,8 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
105106
let runtimeId: string;
106107

107108
if (options.arn) {
108-
runtimeId = options.arn.split('/').pop()!;
109+
const parsed = parseAndValidateArn(options.arn, 'runtime', target);
110+
runtimeId = parsed.resourceId;
109111
} else {
110112
// List runtimes and let user pick
111113
onProgress('Listing runtimes in your account...');

src/cli/commands/import/import-utils.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,56 @@ export async function resolveImportTarget(options: ResolveTargetOptions): Promis
9999
return target;
100100
}
101101

102+
// ============================================================================
103+
// ARN Validation
104+
// ============================================================================
105+
106+
export interface ParsedArn {
107+
region: string;
108+
account: string;
109+
resourceType: string;
110+
resourceId: string;
111+
}
112+
113+
const ARN_PATTERN = /^arn:aws:bedrock-agentcore:([^:]+):([^:]+):(runtime|memory)\/(.+)$/;
114+
115+
/**
116+
* Parse and validate a BedrockAgentCore ARN.
117+
* Validates format, region, and account against the deployment target.
118+
*/
119+
export function parseAndValidateArn(
120+
arn: string,
121+
expectedResourceType: 'runtime' | 'memory',
122+
target: { region: string; account: string }
123+
): ParsedArn {
124+
const match = ARN_PATTERN.exec(arn);
125+
if (!match) {
126+
throw new Error(
127+
`Invalid ARN format: "${arn}". Expected format: arn:aws:bedrock-agentcore:<region>:<account>:${expectedResourceType}/<id>`
128+
);
129+
}
130+
131+
const [, region, account, resourceType, resourceId] = match;
132+
133+
if (resourceType !== expectedResourceType) {
134+
throw new Error(`ARN resource type "${resourceType}" does not match expected type "${expectedResourceType}".`);
135+
}
136+
137+
if (region !== target.region) {
138+
throw new Error(
139+
`ARN region "${region}" does not match target region "${target.region}". Use --target to select a different deployment target.`
140+
);
141+
}
142+
143+
if (account !== target.account) {
144+
throw new Error(
145+
`ARN account "${account}" does not match target account "${target.account}". Ensure the ARN belongs to the correct account.`
146+
);
147+
}
148+
149+
return { region, account, resourceType, resourceId: resourceId! };
150+
}
151+
102152
// ============================================================================
103153
// Stack Name
104154
// ============================================================================

0 commit comments

Comments
 (0)