From ea07c9907e4e16aa50964cd651deb5a63e9da330 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:02:45 +0200 Subject: [PATCH] github-builder: reject duplicate Bake secret IDs Reject flat and target-scoped entries that resolve to the same Bake secret before creating temporary files. Keep the same ID valid across different targets. Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- __tests__/github-builder/build-secrets.test.ts | 12 +++++++++--- src/github-builder/build-secrets.ts | 8 +++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/__tests__/github-builder/build-secrets.test.ts b/__tests__/github-builder/build-secrets.test.ts index 812a70f5..86534479 100644 --- a/__tests__/github-builder/build-secrets.test.ts +++ b/__tests__/github-builder/build-secrets.test.ts @@ -88,10 +88,16 @@ describe('BuildSecrets', () => { expect(mkdir).not.toHaveBeenCalled(); }); - it('preserves override order for duplicate canonical Bake IDs', () => { - const result = BuildSecrets.prepareBake('token: first\napp:\n token: second', 'app', ['app']); + it.each(['token: first\napp:\n token: second', 'app:\n token: first\ntoken: second'])('rejects duplicate canonical Bake IDs before writing files for %j', input => { + const mkdir = vi.spyOn(fs, 'mkdtempSync'); + expect(() => BuildSecrets.prepareBake(input, 'app', ['app'])).toThrow('Duplicate build secret ID "token" for Bake target "app"'); + expect(mkdir).not.toHaveBeenCalled(); + }); + + it('allows the same Bake secret ID on different targets', () => { + const result = BuildSecrets.prepareBake('token: first\nrelease:\n token: second', 'app', ['app', 'release']); directories.push(result.directory); - expect(result.inputs).toEqual([0, 1].map(index => `app.secret.token=src=${path.join(result.directory, String(index))}`)); + expect(result.inputs).toEqual([`app.secret.token=src=${path.join(result.directory, '0')}`, `release.secret.token=src=${path.join(result.directory, '1')}`]); }); it('uses private files, unique directories and idempotent cleanup', () => { diff --git a/src/github-builder/build-secrets.ts b/src/github-builder/build-secrets.ts index 26630a6c..2c43f07b 100644 --- a/src/github-builder/build-secrets.ts +++ b/src/github-builder/build-secrets.ts @@ -55,6 +55,7 @@ export class BuildSecrets { public static prepareBake(input: string, defaultTarget: string, targets: Array): PreparedBuildSecrets { const allowedTargets = new Set(targets); + const seen = new Set(); const secrets = Object.entries(BuildSecrets.parse(input)).flatMap(([key, value]) => { if (typeof value !== 'string' && (!value || typeof value !== 'object' || Array.isArray(value))) { throw new Error('build-secrets entries must be secret strings or target mappings'); @@ -75,7 +76,12 @@ export class BuildSecrets { if (typeof secret !== 'string') { throw new Error('build-secrets values within target mappings must be strings'); } - return {key: `${target}.secret.${id}`, value: secret}; + const overrideKey = `${target}.secret.${id}`; + if (seen.has(overrideKey)) { + throw new Error(`Duplicate build secret ID "${id}" for Bake target "${target}"`); + } + seen.add(overrideKey); + return {key: overrideKey, value: secret}; }); }); return BuildSecrets.write(secrets, 'src=');