Skip to content
Merged
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
20 changes: 20 additions & 0 deletions packages/targets/pkg-docker/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,24 @@ describe('Docker buildx planning', () => {
registries: [{ kind: 'custom' }],
})).rejects.toThrow('pkg-docker requires a host for custom registry');
});

it('rejects unsafe Dockerfile and context paths before writing a plan', async () => {
await expect(adapter.build(fakeBuildContext() as any, {
image: 'acme/api',
registries: [{ kind: 'ghcr' }],
dockerfile: '../Dockerfile',
})).rejects.toThrow('pkg-docker dockerfile must not contain empty or parent path segments');

await expect(adapter.build(fakeBuildContext() as any, {
image: 'acme/api',
registries: [{ kind: 'ghcr' }],
context: '/tmp/project',
})).rejects.toThrow('pkg-docker context must be relative');

await expect(adapter.build(fakeBuildContext() as any, {
image: 'acme/api',
registries: [{ kind: 'ghcr' }],
context: 'apps\\api',
})).rejects.toThrow('pkg-docker context must use forward slashes');
});
});
24 changes: 20 additions & 4 deletions packages/targets/pkg-docker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,22 @@ function tagValue(tag: string, version: string): string {
.replaceAll('$version', normalized);
}

function safeRelativePath(value: string | undefined, fallback: string, label: string): string {
const path = value ?? fallback;
if (path.length === 0) throw new Error(`pkg-docker ${label} must not be empty`);
if (path.startsWith('/')) throw new Error(`pkg-docker ${label} must be relative`);
if (path.includes('\\')) throw new Error(`pkg-docker ${label} must use forward slashes`);
if (path.includes('\0')) throw new Error(`pkg-docker ${label} contains invalid null bytes`);
const segments = path.split('/');
if (segments.some((segment) => segment === '' || segment === '..')) {
throw new Error(`pkg-docker ${label} must not contain empty or parent path segments`);
}
if (label !== 'context' && segments.some((segment) => segment === '.')) {
throw new Error(`pkg-docker ${label} must not contain current-directory segments`);
}
Comment on lines +67 to +69
return path;
}

function imageRefs(ctx: { version: string }, config: Config): string[] {
const tags = [versionTag(ctx.version), 'latest', ...(config.tags ?? []).map((tag) => tagValue(tag, ctx.version))];
const uniqueTags = [...new Set(tags.filter(Boolean))];
Expand All @@ -69,8 +85,8 @@ function buildxArgs(
opts: { push: boolean },
): string[] {
const platforms = config.platforms ?? ['linux/amd64', 'linux/arm64'];
const context = config.context ?? '.';
const dockerfile = config.dockerfile ?? 'Dockerfile';
const context = safeRelativePath(config.context, '.', 'context');
const dockerfile = safeRelativePath(config.dockerfile, 'Dockerfile', 'dockerfile');
const args = [
'buildx',
'build',
Expand Down Expand Up @@ -115,8 +131,8 @@ export default defineTarget<Config>({
const plan = {
image: config.image,
version: versionTag(ctx.version),
context: config.context ?? '.',
dockerfile: config.dockerfile ?? 'Dockerfile',
context: safeRelativePath(config.context, '.', 'context'),
dockerfile: safeRelativePath(config.dockerfile, 'Dockerfile', 'dockerfile'),
platforms,
refs,
commands: {
Expand Down
Loading