Skip to content

Commit e2af68c

Browse files
committed
fix(import): add early name validation and allow re-import with --name
Bug 5: Validate --name against the AgentNameSchema regex before any file I/O operations. Previously, a malicious --name like '../../../etc/pwned' would copy files outside the project directory and set up a Python venv there before schema validation rejected it. Now invalid names are caught immediately with a clear error message. Applied to both import-runtime and import-memory. Bug 6: Allow re-importing the same cloud resource under a different local name when --name is provided. Previously, the deployed-state duplicate check blocked all re-imports by resource ID regardless of --name. Now it only blocks when --name is not provided, and suggests using --name in the error message. When --name is provided, it warns and proceeds. Applied to both import-runtime and import-memory.
1 parent 5cb218e commit e2af68c

2 files changed

Lines changed: 70 additions & 28 deletions

File tree

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { AgentCoreProjectSpec, Memory } from '../../../schema';
21
import type { ConfigIO } from '../../../lib';
2+
import type { AgentCoreProjectSpec, Memory } from '../../../schema';
33
import type { MemoryDetail } from '../../aws/agentcore-control';
44
import { getMemoryDetail, listAllMemories } from '../../aws/agentcore-control';
55
import { LocalCdkProject } from '../../cdk/local-cdk-project';
@@ -88,9 +88,11 @@ function toMemorySpec(memory: MemoryDetail, localName: string): Memory {
8888
*/
8989
export async function handleImportMemory(options: ImportResourceOptions): Promise<ImportResourceResult> {
9090
const logger = new ExecLogger({ command: 'import-memory' });
91-
const onProgress = options.onProgress ?? ((message: string) => {
92-
console.log(`${green}[done]${reset} ${message}`);
93-
});
91+
const onProgress =
92+
options.onProgress ??
93+
((message: string) => {
94+
console.log(`${green}[done]${reset} ${message}`);
95+
});
9496

9597
// Rollback state
9698
let configSnapshot: AgentCoreProjectSpec | undefined;
@@ -184,6 +186,20 @@ export async function handleImportMemory(options: ImportResourceOptions): Promis
184186
}
185187

186188
const localName = options.name ?? memoryDetail.name;
189+
// Validate name early to prevent path traversal before any file I/O
190+
const NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/;
191+
if (!NAME_REGEX.test(localName)) {
192+
const error = `Invalid name "${localName}". Name must start with a letter and contain only letters, numbers, and underscores (max 48 chars).`;
193+
logger.endStep('error', error);
194+
logger.finalize(false);
195+
return {
196+
success: false,
197+
error,
198+
resourceType: 'memory',
199+
resourceName: localName,
200+
logPath: logger.getRelativeLogPath(),
201+
};
202+
}
187203
onProgress(`Memory: ${memoryDetail.name} → local name: ${localName}`);
188204
logger.endStep('success');
189205

@@ -206,16 +222,21 @@ export async function handleImportMemory(options: ImportResourceOptions): Promis
206222
const targetName = target.name ?? 'default';
207223
const existingResource = await findResourceInDeployedState(ctx.configIO, targetName, 'memory', memoryId);
208224
if (existingResource) {
209-
const error = `Memory "${memoryId}" is already imported in this project as "${existingResource}". Remove it first before re-importing.`;
210-
logger.endStep('error', error);
211-
logger.finalize(false);
212-
return {
213-
success: false,
214-
error,
215-
resourceType: 'memory',
216-
resourceName: localName,
217-
logPath: logger.getRelativeLogPath(),
218-
};
225+
if (!options.name) {
226+
const error = `Memory "${memoryId}" is already imported in this project as "${existingResource}". Remove it first before re-importing, or use --name to import under a different name.`;
227+
logger.endStep('error', error);
228+
logger.finalize(false);
229+
return {
230+
success: false,
231+
error,
232+
resourceType: 'memory',
233+
resourceName: localName,
234+
logPath: logger.getRelativeLogPath(),
235+
};
236+
}
237+
onProgress(
238+
`Warning: Memory "${memoryId}" already imported as "${existingResource}". Re-importing as "${localName}".`
239+
);
219240
}
220241
logger.endStep('success');
221242

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

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { AgentCoreProjectSpec, AgentEnvSpec } from '../../../schema';
21
import type { ConfigIO } from '../../../lib';
2+
import type { AgentCoreProjectSpec, AgentEnvSpec } from '../../../schema';
33
import type { AgentRuntimeDetail } from '../../aws/agentcore-control';
44
import { getAgentRuntimeDetail, listAllAgentRuntimes } from '../../aws/agentcore-control';
55
import { LocalCdkProject } from '../../cdk/local-cdk-project';
@@ -103,9 +103,11 @@ function toAgentEnvSpec(
103103
*/
104104
export async function handleImportRuntime(options: ImportResourceOptions): Promise<ImportResourceResult> {
105105
const logger = new ExecLogger({ command: 'import-runtime' });
106-
const onProgress = options.onProgress ?? ((message: string) => {
107-
console.log(`${green}[done]${reset} ${message}`);
108-
});
106+
const onProgress =
107+
options.onProgress ??
108+
((message: string) => {
109+
console.log(`${green}[done]${reset} ${message}`);
110+
});
109111

110112
// Rollback state
111113
let configSnapshot: AgentCoreProjectSpec | undefined;
@@ -216,6 +218,20 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
216218
if (localName.startsWith(prefix)) {
217219
localName = localName.slice(prefix.length);
218220
}
221+
// Validate name early to prevent path traversal before any file I/O
222+
const NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_]{0,47}$/;
223+
if (!NAME_REGEX.test(localName)) {
224+
const error = `Invalid name "${localName}". Name must start with a letter and contain only letters, numbers, and underscores (max 48 chars).`;
225+
logger.endStep('error', error);
226+
logger.finalize(false);
227+
return {
228+
success: false,
229+
error,
230+
resourceType: 'runtime',
231+
resourceName: localName,
232+
logPath: logger.getRelativeLogPath(),
233+
};
234+
}
219235
onProgress(`Runtime: ${runtimeDetail.agentRuntimeName} → local name: ${localName}`);
220236
logger.endStep('success');
221237

@@ -302,16 +318,21 @@ export async function handleImportRuntime(options: ImportResourceOptions): Promi
302318
const targetName = target.name ?? 'default';
303319
const existingResource = await findResourceInDeployedState(ctx.configIO, targetName, 'runtime', runtimeId);
304320
if (existingResource) {
305-
const error = `Runtime "${runtimeId}" is already imported in this project as "${existingResource}". Remove it first before re-importing.`;
306-
logger.endStep('error', error);
307-
logger.finalize(false);
308-
return {
309-
success: false,
310-
error,
311-
resourceType: 'runtime',
312-
resourceName: localName,
313-
logPath: logger.getRelativeLogPath(),
314-
};
321+
if (!options.name) {
322+
const error = `Runtime "${runtimeId}" is already imported in this project as "${existingResource}". Remove it first before re-importing, or use --name to import under a different name.`;
323+
logger.endStep('error', error);
324+
logger.finalize(false);
325+
return {
326+
success: false,
327+
error,
328+
resourceType: 'runtime',
329+
resourceName: localName,
330+
logPath: logger.getRelativeLogPath(),
331+
};
332+
}
333+
onProgress(
334+
`Warning: Runtime "${runtimeId}" already imported as "${existingResource}". Re-importing as "${localName}".`
335+
);
315336
}
316337
logger.endStep('success');
317338

0 commit comments

Comments
 (0)