Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Dockerfile enableOtel rendering > renders opentelemetry-instrument CMD when enableOtel is true > Dockerfile-enableOtel-true 1`] = `
"FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm
"FROM public.ecr.aws/docker/library/python:3.12-slim-trixie

RUN pip install --no-cache-dir uv

Expand Down Expand Up @@ -41,7 +41,7 @@ CMD ["opentelemetry-instrument", "python", "-m", "main"]
`;

exports[`Dockerfile enableOtel rendering > renders plain python CMD when enableOtel is false > Dockerfile-enableOtel-false 1`] = `
"FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm
"FROM public.ecr.aws/docker/library/python:3.12-slim-trixie

RUN pip install --no-cache-dir uv

Expand Down
2 changes: 1 addition & 1 deletion src/assets/container/python/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm
FROM public.ecr.aws/docker/library/python:3.12-slim-trixie
Comment thread
notgitika marked this conversation as resolved.

RUN pip install --no-cache-dir uv

Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/import/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
} from '../../../schema';
import { validateAwsCredentials } from '../../aws/account';
import { arnPrefix } from '../../aws/partition';
import { PYTHON_BASE_IMAGE } from '../../constants';
import { ExecLogger } from '../../logging';
import { setupPythonProject } from '../../operations/python/setup';
import { executeCdkImportPipeline } from './import-pipeline';
Expand Down Expand Up @@ -383,7 +384,7 @@ export async function handleImport(options: ImportOptions): Promise<ImportResult
fs.writeFileSync(
destDockerfile,
[
'FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm',
`FROM ${PYTHON_BASE_IMAGE}`,
'RUN pip install --no-cache-dir uv',
'WORKDIR /app',
'',
Expand Down
5 changes: 5 additions & 0 deletions src/cli/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ export const SCHEMA_VERSION = 1;
* Default runtime endpoint name used in log group paths and console URLs.
*/
export const DEFAULT_ENDPOINT_NAME = 'DEFAULT';

/**
* Base image for generated Python Dockerfiles.
*/
export const PYTHON_BASE_IMAGE = 'public.ecr.aws/docker/library/python:3.12-slim-trixie';
58 changes: 57 additions & 1 deletion src/cli/operations/deploy/__tests__/preflight-container.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { AgentCoreProjectSpec, DirectoryPath } from '../../../../schema';
import { validateContainerAgents } from '../preflight.js';
import { existsSync } from 'node:fs';
import { existsSync, readFileSync } from 'node:fs';
import { afterEach, describe, expect, it, vi } from 'vitest';

vi.mock('node:fs', () => ({
existsSync: vi.fn(),
readFileSync: vi.fn(),
}));

vi.mock('../../../../lib', () => ({
Expand All @@ -26,6 +27,7 @@ vi.mock('../../../../lib', () => ({
}));

const mockedExistsSync = vi.mocked(existsSync);
const mockedReadFileSync = vi.mocked(readFileSync);

const CONFIG_ROOT = '/project/agentcore';

Expand All @@ -44,6 +46,11 @@ describe('validateContainerAgents', () => {
vi.clearAllMocks();
});

// Default readFileSync to return a safe Dockerfile so the warning check doesn't fail on unrelated tests
function mockValidDockerfile(): void {
mockedReadFileSync.mockReturnValue('FROM public.ecr.aws/docker/library/python:3.12-slim-trixie\n');
}

it('does nothing when there are no Container agents', () => {
const spec = makeSpec([{ name: 'zip-agent', build: 'CodeZip', codeLocation: dir('agents/zip-agent') }]);

Expand All @@ -53,6 +60,7 @@ describe('validateContainerAgents', () => {

it('does nothing when Container agent has a valid Dockerfile', () => {
mockedExistsSync.mockReturnValue(true);
mockValidDockerfile();

const spec = makeSpec([
{ name: 'container-agent', build: 'Container', codeLocation: dir('agents/container-agent') },
Expand All @@ -72,6 +80,7 @@ describe('validateContainerAgents', () => {

it('only validates Container agents and skips CodeZip agents', () => {
mockedExistsSync.mockReturnValue(true);
mockValidDockerfile();

const spec = makeSpec([
{ name: 'zip-agent', build: 'CodeZip', codeLocation: dir('agents/zip-agent') },
Expand Down Expand Up @@ -104,6 +113,7 @@ describe('validateContainerAgents', () => {

it('checks for custom dockerfile name when specified', () => {
mockedExistsSync.mockReturnValue(true);
mockValidDockerfile();

const spec = makeSpec([
{ name: 'gpu-agent', build: 'Container', codeLocation: dir('agents/gpu'), dockerfile: 'Dockerfile.gpu' },
Expand All @@ -124,4 +134,50 @@ describe('validateContainerAgents', () => {

expect(() => validateContainerAgents(spec, CONFIG_ROOT)).toThrow(/Dockerfile\.gpu not found/);
});

it('warns when Dockerfile uses deprecated bookworm base image', () => {
mockedExistsSync.mockReturnValue(true);
mockedReadFileSync.mockReturnValue(
'FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm\nRUN pip install uv'
);
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

const spec = makeSpec([{ name: 'my-agent', build: 'Container', codeLocation: dir('agents/my-agent') }]);

expect(() => validateContainerAgents(spec, CONFIG_ROOT)).not.toThrow();
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('CVE-2026-42010'));
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('my-agent'));

warnSpy.mockRestore();
});

it('does not warn when Dockerfile uses trixie base image', () => {
mockedExistsSync.mockReturnValue(true);
mockedReadFileSync.mockReturnValue(
'FROM public.ecr.aws/docker/library/python:3.12-slim-trixie\nRUN pip install uv'
);
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

const spec = makeSpec([{ name: 'my-agent', build: 'Container', codeLocation: dir('agents/my-agent') }]);

expect(() => validateContainerAgents(spec, CONFIG_ROOT)).not.toThrow();
expect(warnSpy).not.toHaveBeenCalled();

warnSpy.mockRestore();
});

it('does not warn when bookworm appears in a non-FROM line', () => {
mockedExistsSync.mockReturnValue(true);
mockedReadFileSync.mockReturnValue(
'FROM public.ecr.aws/docker/library/python:3.12-slim-trixie\n# migrated from slim-bookworm\nRUN pip install uv'
);
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);

const spec = makeSpec([{ name: 'my-agent', build: 'Container', codeLocation: dir('agents/my-agent') }]);

expect(() => validateContainerAgents(spec, CONFIG_ROOT)).not.toThrow();
expect(warnSpy).not.toHaveBeenCalled();

warnSpy.mockRestore();
});
});
25 changes: 24 additions & 1 deletion src/cli/operations/deploy/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { CdkToolkitWrapper, createCdkToolkitWrapper, silentIoHost } from '../../
import { checkBootstrapStatus, checkStacksStatus, formatCdkEnvironment } from '../../cloudformation';
import { cleanupStaleLockFiles } from '../../tui/utils';
import type { IIoHost } from '@aws-cdk/toolkit-lib';
import { existsSync } from 'node:fs';
import { existsSync, readFileSync } from 'node:fs';
import * as path from 'node:path';

export interface PreflightContext {
Expand Down Expand Up @@ -198,6 +198,8 @@ export function validateContainerAgents(projectSpec: AgentCoreProjectSpec, confi
errors.push(
`Agent "${agent.name}": ${agent.dockerfile ?? DOCKERFILE_NAME} not found at ${dockerfilePath}. Container agents require a Dockerfile.`
);
} else {
warnDeprecatedBaseImage(dockerfilePath, agent.name);
}
}
}
Expand All @@ -206,6 +208,27 @@ export function validateContainerAgents(projectSpec: AgentCoreProjectSpec, confi
}
}

const DEPRECATED_BASE_IMAGES = ['slim-bookworm'];

function warnDeprecatedBaseImage(dockerfilePath: string, agentName: string): void {
try {
const content = readFileSync(dockerfilePath, 'utf-8');
for (const line of content.split('\n')) {
if (!/^\s*FROM\s+/i.test(line)) continue;
for (const image of DEPRECATED_BASE_IMAGES) {
if (line.includes(image)) {
console.warn(
`Warning: Agent "${agentName}" Dockerfile uses a base image containing "${image}" which is affected by ` +
`CVE-2026-42010 (GnuTLS authentication bypass). Update the FROM line to use a Trixie-based variant.`
);
}
}
}
} catch {
// Non-fatal — if we can't read the file, the existing validation will handle it
}
}

/**
* Builds the CDK project.
*/
Expand Down
Loading