From e1bf19f65ea812dec1f9d12155118b9e7afaf4e1 Mon Sep 17 00:00:00 2001 From: Julien Dubois Date: Wed, 5 Aug 2026 15:14:31 +0200 Subject: [PATCH 1/4] Isolate Maven signing keys Import signing keys into an action-owned temporary GPG home, export GNUPGHOME, and remove the owned directory in the post action. Cover import failure, multiple keys and invocations, unrelated keyrings, missing state, and Windows path conversion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 2 +- __tests__/auth.test.ts | 56 ++++- __tests__/cleanup-java.test.ts | 53 +++++ .../distributors/microsoft-installer.test.ts | 2 +- .../distributors/temurin-installer.test.ts | 2 +- __tests__/gpg.test.ts | 215 +++++++++++++----- action.yml | 2 +- docs/advanced-usage.md | 2 +- src/auth.ts | 10 +- src/cleanup-java.ts | 28 +-- src/constants.ts | 2 +- src/gpg.ts | 99 ++++---- 12 files changed, 353 insertions(+), 120 deletions(-) diff --git a/README.md b/README.md index 563a21e39..619e62e1d 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ steps: | `server-password-env-var` | Environment variable name for Maven repository password or token. | `GITHUB_TOKEN` | | `settings-path` | Directory where `settings.xml` is written. | `~/.m2` | | `overwrite-settings` | Overwrite an existing `settings.xml`. | `true` | -| `gpg-private-key` | GPG private key to import. | | +| `gpg-private-key` | GPG private key to import into an isolated temporary keyring. | | | `gpg-passphrase-env-var` | Environment variable name for the GPG private key passphrase. | `GPG_PASSPHRASE` when a key is set | | `mvn-toolchain-id` | Maven Toolchain ID. When multiple Java versions are installed, the number of IDs must match the number of versions. | `${mvn-toolchain-vendor}_${java-version}` | | `mvn-toolchain-vendor` | Maven Toolchain vendor value. | `${distribution}` | diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index c3303aa55..36bbcc79b 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -41,10 +41,17 @@ jest.unstable_mockModule('@actions/core', () => ({ toPosixPath: jest.fn((p: string) => p) })); +jest.unstable_mockModule('../src/gpg.js', () => ({ + importKey: jest.fn(), + removeGpgHome: jest.fn() +})); + // Dynamic imports after mocking const core = await import('@actions/core'); +const gpg = await import('../src/gpg.js'); const auth = await import('../src/auth.js'); -const {M2_DIR, MVN_SETTINGS_FILE} = await import('../src/constants.js'); +const {M2_DIR, MVN_SETTINGS_FILE, STATE_GPG_HOME} = + await import('../src/constants.js'); const __dirname = path.dirname(fileURLToPath(import.meta.url)); const m2Dir = path.join(__dirname, M2_DIR); @@ -62,6 +69,13 @@ describe('auth tests', () => { spyInfo.mockImplementation(() => null); }, 300000); + afterEach(() => { + (core.getInput as jest.Mock).mockReset(); + (core.exportVariable as jest.Mock).mockReset(); + (gpg.importKey as jest.Mock).mockReset(); + (gpg.removeGpgHome as jest.Mock).mockReset(); + }); + afterAll(async () => { try { await io.rmRF(m2Dir); @@ -144,6 +158,46 @@ describe('auth tests', () => { ); }, 100000); + it('exports and persists the isolated GPG home', async () => { + const gpgHome = path.join(__dirname, 'runner', 'temp', 'setup-java-gpg-1'); + (gpg.importKey as jest.Mock).mockResolvedValue(gpgHome); + (core.getInput as jest.Mock).mockImplementation((name: string) => { + const inputs: Record = { + 'server-id': 'packages', + 'server-username-env-var': 'USERNAME', + 'server-password-env-var': 'PASSWORD', + 'settings-path': m2Dir, + 'gpg-private-key': 'KEY ONE\nKEY TWO' + }; + return inputs[name] ?? ''; + }); + + await auth.configureAuthentication(); + + expect(gpg.importKey).toHaveBeenCalledWith('KEY ONE\nKEY TWO'); + expect(core.saveState).toHaveBeenCalledWith(STATE_GPG_HOME, gpgHome); + expect(core.exportVariable).toHaveBeenCalledWith('GNUPGHOME', gpgHome); + }); + + it('removes the isolated GPG home when environment export fails', async () => { + const gpgHome = path.join(__dirname, 'runner', 'temp', 'setup-java-gpg-2'); + (gpg.importKey as jest.Mock).mockResolvedValue(gpgHome); + (core.exportVariable as jest.Mock).mockImplementation(() => { + throw new Error('environment file unavailable'); + }); + (core.getInput as jest.Mock).mockImplementation((name: string) => { + if (name === 'gpg-private-key') return 'KEY CONTENTS'; + if (name === 'settings-path') return m2Dir; + return ''; + }); + + await expect(auth.configureAuthentication()).rejects.toThrow( + 'environment file unavailable' + ); + + expect(gpg.removeGpgHome).toHaveBeenCalledWith(gpgHome); + }); + it('overwrites existing settings.xml files', async () => { const id = 'packages'; const username = 'USERNAME'; diff --git a/__tests__/cleanup-java.test.ts b/__tests__/cleanup-java.test.ts index 82f7b3671..44ad451a3 100644 --- a/__tests__/cleanup-java.test.ts +++ b/__tests__/cleanup-java.test.ts @@ -63,6 +63,8 @@ const core = await import('@actions/core'); const cache = await import('@actions/cache'); const {run: cleanup} = await import('../src/cleanup-java.js'); const util = await import('../src/util.js'); +const constants = await import('../src/constants.js'); +const {GPG_HOME_PREFIX} = await import('../src/gpg.js'); const {registerJdk, buildJdkCacheKey} = await import('../src/jdk-cache.js'); const jdkTempRoots: string[] = []; @@ -114,11 +116,62 @@ describe('cleanup', () => { (core.getInput as jest.Mock).mockImplementation((name: string) => { return name === 'cache' ? 'gradle' : ''; }); + await cleanup(); expect(spyCacheSave).toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled(); }); + it('removes the isolated GPG home without touching unrelated key material', async () => { + const tempDir = util.getTempDir(); + fs.mkdirSync(tempDir, {recursive: true}); + const gpgHome = fs.mkdtempSync(path.join(tempDir, GPG_HOME_PREFIX)); + const unrelatedGpgHome = fs.mkdtempSync( + path.join(tempDir, 'user-gpg-home-') + ); + fs.writeFileSync(path.join(unrelatedGpgHome, 'private.key'), 'pre-existing'); + (core.getInput as jest.Mock).mockReturnValue(''); + (core.getState as jest.Mock).mockImplementation((name: string) => + name === constants.STATE_GPG_HOME ? gpgHome : '' + ); + + await cleanup(); + + expect(fs.existsSync(gpgHome)).toBe(false); + expect( + fs.readFileSync(path.join(unrelatedGpgHome, 'private.key'), 'utf8') + ).toBe('pre-existing'); + fs.rmSync(unrelatedGpgHome, {recursive: true, force: true}); + }); + + it('makes repeated cleanup of the same GPG home idempotent', async () => { + const tempDir = util.getTempDir(); + fs.mkdirSync(tempDir, {recursive: true}); + const gpgHome = fs.mkdtempSync(path.join(tempDir, GPG_HOME_PREFIX)); + (core.getInput as jest.Mock).mockReturnValue(''); + (core.getState as jest.Mock).mockImplementation((name: string) => + name === constants.STATE_GPG_HOME ? gpgHome : '' + ); + + await cleanup(); + await cleanup(); + + expect(fs.existsSync(gpgHome)).toBe(false); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + + it('skips GPG cleanup when no home was persisted', async () => { + (core.getInput as jest.Mock).mockReturnValue(''); + (core.getState as jest.Mock).mockReturnValue(''); + + await cleanup(); + + expect(spyInfo).not.toHaveBeenCalledWith( + 'Removing private key from isolated GPG home' + ); + expect(core.setFailed).not.toHaveBeenCalled(); + }); + it('does not fail even though the save process throws error', async () => { spyCacheSave.mockImplementation((paths: string[], key: string) => Promise.reject(new Error('Unexpected error')) diff --git a/__tests__/distributors/microsoft-installer.test.ts b/__tests__/distributors/microsoft-installer.test.ts index 0dc0ea532..8e0747ac5 100644 --- a/__tests__/distributors/microsoft-installer.test.ts +++ b/__tests__/distributors/microsoft-installer.test.ts @@ -86,7 +86,7 @@ jest.unstable_mockModule('../../src/util.js', () => ({ jest.unstable_mockModule('../../src/gpg.js', () => ({ importKey: jest.fn(), - deleteKey: jest.fn(), + removeGpgHome: jest.fn(), verifyPackageSignature: jest.fn() })); diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index 793c3778b..830fdb943 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -72,7 +72,7 @@ jest.unstable_mockModule('../../src/util.js', () => ({ jest.unstable_mockModule('../../src/gpg.js', () => ({ importKey: jest.fn(), - deleteKey: jest.fn(), + removeGpgHome: jest.fn(), verifyPackageSignature: jest.fn() })); diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts index 239643277..84bf1631a 100644 --- a/__tests__/gpg.test.ts +++ b/__tests__/gpg.test.ts @@ -8,6 +8,7 @@ import { afterEach } from '@jest/globals'; import {fileURLToPath} from 'url'; +import * as fs from 'fs'; import * as path from 'path'; import * as io from '@actions/io'; @@ -30,7 +31,10 @@ process.env['RUNNER_TEMP'] = tempDir; describe('gpg tests', () => { beforeEach(async () => { + await io.rmRF(tempDir); await io.mkdirP(tempDir); + jest.clearAllMocks(); + (exec.exec as jest.Mock).mockResolvedValue(0); }); afterAll(async () => { @@ -71,74 +75,175 @@ describe('gpg tests', () => { }); describe('importKey', () => { - it('attempts to import private key and returns null key id on failure', async () => { + it('imports private keys into a unique isolated GPG home', async () => { const privateKey = 'KEY CONTENTS'; - const keyId = await gpg.importKey(privateKey); + let privateKeyFile = ''; + (exec.exec as jest.Mock).mockImplementation( + async (_command: string, _args: string[]) => { + const [createdGpgHome] = fs.readdirSync(tempDir); + privateKeyFile = path.join( + tempDir, + createdGpgHome, + fs + .readdirSync(path.join(tempDir, createdGpgHome)) + .find(file => file.startsWith('private-key-')) ?? '' + ); + expect(fs.readFileSync(privateKeyFile, 'utf8')).toBe(privateKey); + if (process.platform !== 'win32') { + expect(fs.statSync(privateKeyFile).mode & 0o777).toBe(0o600); + } + return 0; + } + ); - expect(keyId).toBeNull(); + const gpgHome = await gpg.importKey(privateKey); + expect(path.dirname(gpgHome)).toBe(tempDir); + expect(path.basename(gpgHome).startsWith(gpg.GPG_HOME_PREFIX)).toBe(true); + expect(fs.existsSync(gpgHome)).toBe(true); + expect(fs.existsSync(privateKeyFile)).toBe(false); + if (process.platform !== 'win32') { + expect(fs.statSync(gpgHome).mode & 0o777).toBe(0o700); + } expect(exec.exec).toHaveBeenCalledWith( 'gpg', - expect.anything(), - expect.anything() + [ + '--homedir', + gpg.toGpgPath(gpgHome), + '--batch', + '--import', + gpg.toGpgPath(privateKeyFile) + ], + {silent: true} + ); + }); + + it('removes the private-key file and isolated home when import fails', async () => { + let gpgHome = ''; + let privateKeyFile = ''; + (exec.exec as jest.Mock).mockImplementation( + async (_command: string, _args: string[]) => { + const [createdGpgHome] = fs.readdirSync(tempDir); + gpgHome = path.join(tempDir, createdGpgHome); + privateKeyFile = path.join( + gpgHome, + fs + .readdirSync(gpgHome) + .find(file => file.startsWith('private-key-')) ?? '' + ); + expect(fs.existsSync(privateKeyFile)).toBe(true); + throw new Error('invalid key'); + } ); + + await expect(gpg.importKey('INVALID KEY')).rejects.toThrow('invalid key'); + + expect(fs.existsSync(privateKeyFile)).toBe(false); + expect(fs.existsSync(gpgHome)).toBe(false); + }); + + it('imports multi-key input without parsing or deleting fingerprints', async () => { + const privateKeys = 'KEY ONE\nKEY TWO'; + (exec.exec as jest.Mock).mockImplementation( + async (_command: string, _args: string[]) => { + const [createdGpgHome] = fs.readdirSync(tempDir); + const keyFile = fs + .readdirSync(path.join(tempDir, createdGpgHome)) + .find(file => file.startsWith('private-key-')); + expect( + fs.readFileSync( + path.join(tempDir, createdGpgHome, keyFile ?? ''), + 'utf8' + ) + ).toBe(privateKeys); + return 0; + } + ); + + const gpgHome = await gpg.importKey(privateKeys); + + expect(gpgHome).toContain(gpg.GPG_HOME_PREFIX); + expect(exec.exec).toHaveBeenCalledTimes(1); + expect((exec.exec as jest.Mock).mock.calls[0][1]).not.toContain( + '--delete-secret-and-public-key' + ); + }); + + it('uses a separate GPG home for each invocation', async () => { + const firstGpgHome = await gpg.importKey('FIRST KEY'); + const secondGpgHome = await gpg.importKey('SECOND KEY'); + + expect(firstGpgHome).not.toBe(secondGpgHome); + expect(fs.existsSync(firstGpgHome)).toBe(true); + expect(fs.existsSync(secondGpgHome)).toBe(true); }); }); - describe('deleteKey', () => { - it('deletes private key', async () => { - const keyId = 'asdfhjkl'; - await gpg.deleteKey(keyId); + describe('removeGpgHome', () => { + it('removes only action-owned GPG homes and is idempotent', async () => { + const gpgHome = await gpg.importKey('KEY CONTENTS'); + const unrelatedGpgHome = path.join(tempDir, 'user-gpg-home'); + fs.mkdirSync(unrelatedGpgHome); - expect(exec.exec).toHaveBeenCalledWith( - 'gpg', - expect.anything(), - expect.anything() + await gpg.removeGpgHome(gpgHome); + await gpg.removeGpgHome(gpgHome); + + expect(fs.existsSync(gpgHome)).toBe(false); + expect(fs.existsSync(unrelatedGpgHome)).toBe(true); + }); + + it('refuses to remove a GPG home it does not own', async () => { + const unrelatedGpgHome = path.join(tempDir, 'user-gpg-home'); + fs.mkdirSync(unrelatedGpgHome, {recursive: true}); + + await expect(gpg.removeGpgHome(unrelatedGpgHome)).rejects.toThrow( + 'Refusing to remove unexpected GPG home' ); + expect(fs.existsSync(unrelatedGpgHome)).toBe(true); }); + }); - describe('verifyPackageSignature', () => { - it('imports bundled key and verifies package', async () => { - const publicKeyContent = - '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----'; - (tc.downloadTool as jest.Mock).mockResolvedValue( - '/tmp/jdk.tar.gz.sig' - ); - await gpg.verifyPackageSignature( - '/tmp/jdk.tar.gz', - 'https://example.com/jdk.tar.gz.sig', - publicKeyContent - ); - - expect(tc.downloadTool).toHaveBeenCalledWith( - 'https://example.com/jdk.tar.gz.sig' - ); - expect(exec.exec).toHaveBeenNthCalledWith( - 1, - 'gpg', - [ - '--homedir', - expect.any(String), - '--batch', - '--import', - expect.stringContaining('public-key.asc') - ], - expect.objectContaining({silent: true}) - ); - expect(exec.exec).toHaveBeenNthCalledWith( - 2, - 'gpg', - [ - '--homedir', - expect.any(String), - '--batch', - '--verify', - '/tmp/jdk.tar.gz.sig', - '/tmp/jdk.tar.gz' - ], - expect.objectContaining({silent: true}) - ); - }); + describe('verifyPackageSignature', () => { + it('imports bundled key and verifies package', async () => { + const publicKeyContent = + '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----'; + (tc.downloadTool as jest.Mock).mockResolvedValue( + '/tmp/jdk.tar.gz.sig' + ); + await gpg.verifyPackageSignature( + '/tmp/jdk.tar.gz', + 'https://example.com/jdk.tar.gz.sig', + publicKeyContent + ); + + expect(tc.downloadTool).toHaveBeenCalledWith( + 'https://example.com/jdk.tar.gz.sig' + ); + expect(exec.exec).toHaveBeenNthCalledWith( + 1, + 'gpg', + [ + '--homedir', + expect.any(String), + '--batch', + '--import', + expect.stringContaining('public-key.asc') + ], + expect.objectContaining({silent: true}) + ); + expect(exec.exec).toHaveBeenNthCalledWith( + 2, + 'gpg', + [ + '--homedir', + expect.any(String), + '--batch', + '--verify', + '/tmp/jdk.tar.gz.sig', + '/tmp/jdk.tar.gz' + ], + expect.objectContaining({silent: true}) + ); }); }); }); diff --git a/action.yml b/action.yml index d27c23aca..60c1a00e9 100644 --- a/action.yml +++ b/action.yml @@ -72,7 +72,7 @@ inputs: required: false default: true gpg-private-key: - description: 'GPG private key to import. Default is empty string.' + description: 'GPG private key to import into an isolated temporary keyring. Default is empty string.' required: false default: '' gpg-passphrase-env-var: diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index ffe700c79..4a6ec8975 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -873,7 +873,7 @@ See the help docs on [Publishing a Package](https://help.github.com/en/github/ma #### Legacy / alternative: let setup-java import the key -If you prefer signing with the `gpg` executable (for example because you are using `maven-gpg-plugin` older than 3.2.0), you can let setup-java import the key instead by providing the `gpg-private-key` and `gpg-passphrase-env-var` inputs. The private key is written to a file in the runner's temp directory, imported into the GPG keychain, and the file is promptly removed before proceeding with the rest of the setup process. A cleanup step removes the imported private key from the GPG keychain after the job completes regardless of the job status. This ensures that the private key is no longer accessible on self-hosted runners and cannot "leak" between jobs (hosted runners are always clean instances). +If you prefer signing with the `gpg` executable (for example because you are using `maven-gpg-plugin` older than 3.2.0), you can let setup-java import the key instead by providing the `gpg-private-key` and `gpg-passphrase-env-var` inputs. setup-java creates a uniquely named, permission-restricted GPG home in the runner's temp directory, imports the key only into that isolated keyring, and exports `GNUPGHOME` for subsequent Maven and GPG commands. The temporary key file is permission-restricted and removed whether the import succeeds or fails. A cleanup step removes the complete action-owned GPG home after the job regardless of job status, without modifying the runner user's default keyring. Each setup-java invocation owns a separate keyring, including on persistent self-hosted runners. setup-java imports the key independently of the plugin version, but the generated passphrase profile described below uses `gpg.passphraseEnvName`, which requires `maven-gpg-plugin` 3.2.0 or newer. Since `gpg-passphrase-env-var` defaults to `GPG_PASSPHRASE`, setup-java writes that profile unless you override the input to `MAVEN_GPG_PASSPHRASE`. diff --git a/src/auth.ts b/src/auth.ts index 277342c55..fcad90fea 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -52,8 +52,14 @@ export async function configureAuthentication() { if (gpgPrivateKey) { core.info('Importing private gpg key'); - const keyFingerprint = (await gpg.importKey(gpgPrivateKey)) || ''; - core.saveState(constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT, keyFingerprint); + const gpgHome = await gpg.importKey(gpgPrivateKey); + try { + core.saveState(constants.STATE_GPG_HOME, gpgHome); + core.exportVariable('GNUPGHOME', gpgHome); + } catch (error) { + await gpg.removeGpgHome(gpgHome); + throw error; + } } } diff --git a/src/cleanup-java.ts b/src/cleanup-java.ts index b9fae6b2f..cf333d00c 100644 --- a/src/cleanup-java.ts +++ b/src/cleanup-java.ts @@ -8,19 +8,19 @@ import { } from './util.js'; import {fileURLToPath} from 'url'; -async function removePrivateKeyFromKeychain() { - if (core.getInput(constants.INPUT_GPG_PRIVATE_KEY, {required: false})) { - core.info('Removing private key from keychain'); - try { - const keyFingerprint = core.getState( - constants.STATE_GPG_PRIVATE_KEY_FINGERPRINT - ); - await gpg.deleteKey(keyFingerprint); - } catch (error) { - core.setFailed( - `Failed to remove private key due to: ${(error as Error).message}` - ); - } +async function removeGpgHome() { + const gpgHome = core.getState(constants.STATE_GPG_HOME); + if (!gpgHome) { + return; + } + + core.info('Removing private key from isolated GPG home'); + try { + await gpg.removeGpgHome(gpgHome); + } catch (error) { + core.setFailed( + `Failed to remove isolated GPG home due to: ${(error as Error).message}` + ); } } @@ -73,7 +73,7 @@ async function ignoreError(promise: Promise) { } export async function run() { - await removePrivateKeyFromKeychain(); + await removeGpgHome(); await ignoreError(saveCaches()); } diff --git a/src/constants.ts b/src/constants.ts index 6e19a5873..83a2aaf00 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -43,7 +43,7 @@ export const INPUT_CACHE_PATH = 'cache-path'; export const INPUT_CACHE_READ_ONLY = 'cache-read-only'; export const INPUT_JOB_STATUS = 'job-status'; -export const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; +export const STATE_GPG_HOME = 'gpg-home'; export const M2_DIR = '.m2'; export const MVN_SETTINGS_FILE = 'settings.xml'; diff --git a/src/gpg.ts b/src/gpg.ts index 6eb78721f..9abd8d391 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -1,14 +1,14 @@ import * as fs from 'fs'; import * as path from 'path'; +import {randomUUID} from 'crypto'; import * as io from '@actions/io'; import * as exec from '@actions/exec'; import * as tc from '@actions/tool-cache'; import * as util from './util.js'; import {ExecOptions} from '@actions/exec'; -export const PRIVATE_KEY_FILE = path.join(util.getTempDir(), 'private-key.asc'); - -const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/; +export const GPG_HOME_PREFIX = 'setup-java-gpg-'; +const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions @@ -21,49 +21,66 @@ export function toGpgPath(p: string): string { .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } -export async function importKey(privateKey: string) { - fs.writeFileSync(PRIVATE_KEY_FILE, privateKey, { - encoding: 'utf-8', - flag: 'w' - }); +function createGpgHome(prefix: string): string { + const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix)); + if (process.platform !== 'win32') { + fs.chmodSync(gpgHome, 0o700); + } + return gpgHome; +} - let output = ''; +export async function importKey(privateKey: string): Promise { + const gpgHome = createGpgHome(GPG_HOME_PREFIX); + const privateKeyFile = path.join( + gpgHome, + `private-key-${randomUUID()}.asc` + ); - const options: ExecOptions = { - silent: true, - listeners: { - stdout: (data: Buffer) => { - output += data.toString(); - } + try { + fs.writeFileSync(privateKeyFile, privateKey, { + encoding: 'utf-8', + flag: 'wx', + mode: 0o600 + }); + + try { + await exec.exec( + 'gpg', + [ + '--homedir', + toGpgPath(gpgHome), + '--batch', + '--import', + toGpgPath(privateKeyFile) + ], + {silent: true} + ); + } finally { + fs.rmSync(privateKeyFile, {force: true}); } - }; - await exec.exec( - 'gpg', - [ - '--batch', - '--import-options', - 'import-show', - '--import', - PRIVATE_KEY_FILE - ], - options - ); + return gpgHome; + } catch (error) { + await io.rmRF(gpgHome); + throw error; + } +} - await io.rmRF(PRIVATE_KEY_FILE); +export async function removeGpgHome(gpgHome: string): Promise { + if (!gpgHome) { + return; + } - const match = output.match(PRIVATE_KEY_FINGERPRINT_REGEX); - return match && match[0]; -} + const resolvedGpgHome = path.resolve(gpgHome); + const resolvedTempDir = path.resolve(util.getTempDir()); + if ( + path.dirname(resolvedGpgHome) !== resolvedTempDir || + !path.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX) + ) { + throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); + } -export async function deleteKey(keyFingerprint: string) { - await exec.exec( - 'gpg', - ['--batch', '--yes', '--delete-secret-and-public-key', keyFingerprint], - { - silent: true - } - ); + await io.rmRF(resolvedGpgHome); } export async function verifyPackageSignature( @@ -74,9 +91,7 @@ export async function verifyPackageSignature( const signaturePath = await tc.downloadTool(signatureUrl); let gpgHome: string; try { - gpgHome = fs.mkdtempSync( - path.join(util.getTempDir(), 'verify-signature-gpg-home-') - ); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); } catch (error) { try { await io.rmRF(signaturePath); From ba6e05a7fa051de1551a58e5e5ce945fba76a1d9 Mon Sep 17 00:00:00 2001 From: Julien Dubois Date: Wed, 5 Aug 2026 15:17:15 +0200 Subject: [PATCH 2/4] Fix cleanup state assertion Account for isolated GPG-home cleanup when cache saving is disabled. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/cleanup-java.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/__tests__/cleanup-java.test.ts b/__tests__/cleanup-java.test.ts index 44ad451a3..0a22e24f3 100644 --- a/__tests__/cleanup-java.test.ts +++ b/__tests__/cleanup-java.test.ts @@ -201,7 +201,8 @@ describe('cleanup', () => { await cleanup(); expect(spyCacheSave).not.toHaveBeenCalled(); - expect(core.getState).not.toHaveBeenCalled(); + expect(core.getState).toHaveBeenCalledTimes(1); + expect(core.getState).toHaveBeenCalledWith(constants.STATE_GPG_HOME); expect(spyInfo).toHaveBeenCalledWith( 'Cache saving is skipped because cache-read-only is enabled.' ); From 9bb9dff64651cc9ae4346e1a0cdd4d659d8d3101 Mon Sep 17 00:00:00 2001 From: Julien Dubois Date: Wed, 5 Aug 2026 15:18:57 +0200 Subject: [PATCH 3/4] Update generated action bundles Apply repository formatting and commit the setup and cleanup bundles produced by the validated Node 24 build. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/cleanup-java.test.ts | 5 +- dist/cleanup/index.js | 124 +++++++++++++++++++-------------- dist/setup/220.index.js | 103 ++++++++++++++++----------- dist/setup/463.index.js | 103 ++++++++++++++++----------- dist/setup/81.index.js | 114 ++++++++++++++++++------------ dist/setup/index.js | 4 +- src/gpg.ts | 5 +- 7 files changed, 275 insertions(+), 183 deletions(-) diff --git a/__tests__/cleanup-java.test.ts b/__tests__/cleanup-java.test.ts index 0a22e24f3..63b0df8a0 100644 --- a/__tests__/cleanup-java.test.ts +++ b/__tests__/cleanup-java.test.ts @@ -129,7 +129,10 @@ describe('cleanup', () => { const unrelatedGpgHome = fs.mkdtempSync( path.join(tempDir, 'user-gpg-home-') ); - fs.writeFileSync(path.join(unrelatedGpgHome, 'private.key'), 'pre-existing'); + fs.writeFileSync( + path.join(unrelatedGpgHome, 'private.key'), + 'pre-existing' + ); (core.getInput as jest.Mock).mockReturnValue(''); (core.getState as jest.Mock).mockImplementation((name: string) => name === constants.STATE_GPG_HOME ? gpgHome : '' diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 8c7ebb341..d45ff029e 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -30769,13 +30769,12 @@ module.exports = { /* harmony export */ __nccwpck_require__.d(__webpack_exports__, { /* harmony export */ Ch: () => (/* binding */ INPUT_CACHE_READ_ONLY), +/* harmony export */ Fi: () => (/* binding */ STATE_GPG_HOME), /* harmony export */ GL: () => (/* binding */ INPUT_CACHE_JDK), /* harmony export */ gk: () => (/* binding */ INPUT_CACHE), -/* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS), -/* harmony export */ wm: () => (/* binding */ STATE_GPG_PRIVATE_KEY_FINGERPRINT), -/* harmony export */ wz: () => (/* binding */ INPUT_GPG_PRIVATE_KEY) +/* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS) /* harmony export */ }); -/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */ +/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */ const MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; const INPUT_JAVA_VERSION = 'java-version'; const INPUT_JAVA_VERSION_FILE = 'java-version-file'; @@ -30816,7 +30815,7 @@ const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; const INPUT_CACHE_PATH = 'cache-path'; const INPUT_CACHE_READ_ONLY = 'cache-read-only'; const INPUT_JOB_STATUS = 'job-status'; -const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; +const STATE_GPG_HOME = 'gpg-home'; const M2_DIR = '.m2'; const MVN_SETTINGS_FILE = 'settings.xml'; const MVN_TOOLCHAINS_FILE = 'toolchains.xml'; @@ -34150,10 +34149,11 @@ const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCa /* harmony export */ Ck: () => (/* binding */ mkdir), /* harmony export */ H8: () => (/* binding */ IS_WINDOWS), /* harmony export */ Qh: () => (/* binding */ isRooted), +/* harmony export */ rm: () => (/* binding */ rm), /* harmony export */ t2: () => (/* binding */ exists), /* harmony export */ vr: () => (/* binding */ tryGetExecutablePath) /* harmony export */ }); -/* unused harmony exports chmod, copyFile, lstat, open, readdir, rename, rm, rmdir, stat, symlink, unlink, readlink, UV_FS_O_EXLOCK, READONLY, isDirectory, getCmdPath */ +/* unused harmony exports chmod, copyFile, lstat, open, readdir, rename, rmdir, stat, symlink, unlink, readlink, UV_FS_O_EXLOCK, READONLY, isDirectory, getCmdPath */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(9896); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6928); var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -34343,9 +34343,10 @@ function getCmdPath() { /* harmony export */ __nccwpck_require__.d(__webpack_exports__, { /* harmony export */ K7: () => (/* binding */ which), -/* harmony export */ U$: () => (/* binding */ mkdirP) +/* harmony export */ U$: () => (/* binding */ mkdirP), +/* harmony export */ Yz: () => (/* binding */ rmRF) /* harmony export */ }); -/* unused harmony exports cp, mv, rmRF, findInPath */ +/* unused harmony exports cp, mv, findInPath */ /* harmony import */ var assert__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(2613); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6928); /* harmony import */ var _io_util_js__WEBPACK_IMPORTED_MODULE_2__ = __nccwpck_require__(90); @@ -34438,7 +34439,7 @@ function mv(source_1, dest_1) { */ function rmRF(inputPath) { return __awaiter(this, void 0, void 0, function* () { - if (ioUtil.IS_WINDOWS) { + if (_io_util_js__WEBPACK_IMPORTED_MODULE_2__/* .IS_WINDOWS */ .H8) { // Check for invalid characters // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file if (/[*"<>|]/.test(inputPath)) { @@ -34447,7 +34448,7 @@ function rmRF(inputPath) { } try { // note if path does not exist, error is silent - yield ioUtil.rm(inputPath, { + yield _io_util_js__WEBPACK_IMPORTED_MODULE_2__.rm(inputPath, { force: true, maxRetries: 3, recursive: true, @@ -35661,6 +35662,8 @@ var cleanup_java_core = __nccwpck_require__(3838); var external_fs_ = __nccwpck_require__(9896); // EXTERNAL MODULE: external "path" var external_path_ = __nccwpck_require__(6928); +// EXTERNAL MODULE: external "crypto" +var external_crypto_ = __nccwpck_require__(6982); // EXTERNAL MODULE: ./node_modules/@actions/io/lib/io.js var lib_io = __nccwpck_require__(8701); // EXTERNAL MODULE: ./node_modules/@actions/exec/lib/exec.js + 2 modules @@ -35676,8 +35679,9 @@ var src_util = __nccwpck_require__(4527); -const PRIVATE_KEY_FILE = external_path_.join(src_util/* getTempDir */.G4(), 'private-key.asc'); -const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/; + +const GPG_HOME_PREFIX = 'setup-java-gpg-'; +const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // internally. Passing Windows paths with backslashes can cause fatal GPG errors @@ -35689,41 +35693,58 @@ function toGpgPath(p) { .replace(/\\/g, '/') .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } +function createGpgHome(prefix) { + const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix)); + if (process.platform !== 'win32') { + fs.chmodSync(gpgHome, 0o700); + } + return gpgHome; +} async function importKey(privateKey) { - fs.writeFileSync(PRIVATE_KEY_FILE, privateKey, { - encoding: 'utf-8', - flag: 'w' - }); - let output = ''; - const options = { - silent: true, - listeners: { - stdout: (data) => { - output += data.toString(); - } + const gpgHome = createGpgHome(GPG_HOME_PREFIX); + const privateKeyFile = path.join(gpgHome, `private-key-${randomUUID()}.asc`); + try { + fs.writeFileSync(privateKeyFile, privateKey, { + encoding: 'utf-8', + flag: 'wx', + mode: 0o600 + }); + try { + await exec.exec('gpg', [ + '--homedir', + toGpgPath(gpgHome), + '--batch', + '--import', + toGpgPath(privateKeyFile) + ], { silent: true }); } - }; - await exec.exec('gpg', [ - '--batch', - '--import-options', - 'import-show', - '--import', - PRIVATE_KEY_FILE - ], options); - await io.rmRF(PRIVATE_KEY_FILE); - const match = output.match(PRIVATE_KEY_FINGERPRINT_REGEX); - return match && match[0]; -} -async function deleteKey(keyFingerprint) { - await lib_exec/* exec */.m('gpg', ['--batch', '--yes', '--delete-secret-and-public-key', keyFingerprint], { - silent: true - }); + finally { + fs.rmSync(privateKeyFile, { force: true }); + } + return gpgHome; + } + catch (error) { + await io.rmRF(gpgHome); + throw error; + } +} +async function removeGpgHome(gpgHome) { + if (!gpgHome) { + return; + } + const resolvedGpgHome = external_path_.resolve(gpgHome); + const resolvedTempDir = external_path_.resolve(src_util/* getTempDir */.G4()); + if (external_path_.dirname(resolvedGpgHome) !== resolvedTempDir || + !external_path_.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { + throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); + } + await lib_io/* rmRF */.Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { const signaturePath = await tc.downloadTool(signatureUrl); let gpgHome; try { - gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), 'verify-signature-gpg-home-')); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); } catch (error) { try { @@ -35770,16 +35791,17 @@ var external_url_ = __nccwpck_require__(7016); -async function removePrivateKeyFromKeychain() { - if (cleanup_java_core/* getInput */.V4(constants/* INPUT_GPG_PRIVATE_KEY */.wz, { required: false })) { - cleanup_java_core/* info */.pq('Removing private key from keychain'); - try { - const keyFingerprint = cleanup_java_core/* getState */.Gu(constants/* STATE_GPG_PRIVATE_KEY_FINGERPRINT */.wm); - await deleteKey(keyFingerprint); - } - catch (error) { - cleanup_java_core/* setFailed */.C1(`Failed to remove private key due to: ${error.message}`); - } +async function cleanup_java_removeGpgHome() { + const gpgHome = cleanup_java_core/* getState */.Gu(constants/* STATE_GPG_HOME */.Fi); + if (!gpgHome) { + return; + } + cleanup_java_core/* info */.pq('Removing private key from isolated GPG home'); + try { + await removeGpgHome(gpgHome); + } + catch (error) { + cleanup_java_core/* setFailed */.C1(`Failed to remove isolated GPG home due to: ${error.message}`); } } /** @@ -35827,7 +35849,7 @@ async function ignoreError(promise) { }); } async function run() { - await removePrivateKeyFromKeychain(); + await cleanup_java_removeGpgHome(); await ignoreError(saveCaches()); } if (process.argv[1] === (0,external_url_.fileURLToPath)(import.meta.url)) { diff --git a/dist/setup/220.index.js b/dist/setup/220.index.js index 64353cbc4..3f27b2b7f 100644 --- a/dist/setup/220.index.js +++ b/dist/setup/220.index.js @@ -170,25 +170,29 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Fh: () => (/* binding */ importKey), -/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature) +/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), +/* harmony export */ mS: () => (/* binding */ removeGpgHome) /* harmony export */ }); -/* unused harmony exports PRIVATE_KEY_FILE, toGpgPath, deleteKey */ +/* unused harmony exports GPG_HOME_PREFIX, toGpgPath */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6928); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8701); -/* harmony import */ var _actions_exec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5260); -/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9805); -/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6982); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8701); +/* harmony import */ var _actions_exec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5260); +/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9805); +/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527); -const PRIVATE_KEY_FILE = path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getTempDir */ .G4(), 'private-key.asc'); -const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/; + +const GPG_HOME_PREFIX = 'setup-java-gpg-'; +const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // internally. Passing Windows paths with backslashes can cause fatal GPG errors @@ -200,45 +204,62 @@ function toGpgPath(p) { .replace(/\\/g, '/') .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } +function createGpgHome(prefix) { + const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(), prefix)); + if (process.platform !== 'win32') { + fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); + } + return gpgHome; +} async function importKey(privateKey) { - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(PRIVATE_KEY_FILE, privateKey, { - encoding: 'utf-8', - flag: 'w' - }); - let output = ''; - const options = { - silent: true, - listeners: { - stdout: (data) => { - output += data.toString(); - } + const gpgHome = createGpgHome(GPG_HOME_PREFIX); + const privateKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `private-key-${(0,crypto__WEBPACK_IMPORTED_MODULE_2__.randomUUID)()}.asc`); + try { + fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(privateKeyFile, privateKey, { + encoding: 'utf-8', + flag: 'wx', + mode: 0o600 + }); + try { + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ + '--homedir', + toGpgPath(gpgHome), + '--batch', + '--import', + toGpgPath(privateKeyFile) + ], { silent: true }); } - }; - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ - '--batch', - '--import-options', - 'import-show', - '--import', - PRIVATE_KEY_FILE - ], options); - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(PRIVATE_KEY_FILE); - const match = output.match(PRIVATE_KEY_FINGERPRINT_REGEX); - return match && match[0]; + finally { + fs__WEBPACK_IMPORTED_MODULE_0__.rmSync(privateKeyFile, { force: true }); + } + return gpgHome; + } + catch (error) { + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); + throw error; + } } -async function deleteKey(keyFingerprint) { - await exec.exec('gpg', ['--batch', '--yes', '--delete-secret-and-public-key', keyFingerprint], { - silent: true - }); +async function removeGpgHome(gpgHome) { + if (!gpgHome) { + return; + } + const resolvedGpgHome = path__WEBPACK_IMPORTED_MODULE_1__.resolve(gpgHome); + const resolvedTempDir = path__WEBPACK_IMPORTED_MODULE_1__.resolve(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()); + if (path__WEBPACK_IMPORTED_MODULE_1__.dirname(resolvedGpgHome) !== resolvedTempDir || + !path__WEBPACK_IMPORTED_MODULE_1__.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { + throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); + } + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { - const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__/* .downloadTool */ .bq(signatureUrl); + const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); let gpgHome; try { - gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getTempDir */ .G4(), 'verify-signature-gpg-home-')); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); } catch (error) { try { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(signaturePath); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); } catch { // ignore cleanup failures @@ -249,14 +270,14 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); const options = { silent: true }; - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', toGpgPath(publicKeyFile) ], options); - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', @@ -266,8 +287,8 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(signaturePath); - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(gpgHome); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); } } diff --git a/dist/setup/463.index.js b/dist/setup/463.index.js index cd19ab248..9e85602b0 100644 --- a/dist/setup/463.index.js +++ b/dist/setup/463.index.js @@ -268,25 +268,29 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Fh: () => (/* binding */ importKey), -/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature) +/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), +/* harmony export */ mS: () => (/* binding */ removeGpgHome) /* harmony export */ }); -/* unused harmony exports PRIVATE_KEY_FILE, toGpgPath, deleteKey */ +/* unused harmony exports GPG_HOME_PREFIX, toGpgPath */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6928); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8701); -/* harmony import */ var _actions_exec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5260); -/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9805); -/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6982); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8701); +/* harmony import */ var _actions_exec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5260); +/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9805); +/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527); -const PRIVATE_KEY_FILE = path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getTempDir */ .G4(), 'private-key.asc'); -const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/; + +const GPG_HOME_PREFIX = 'setup-java-gpg-'; +const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // internally. Passing Windows paths with backslashes can cause fatal GPG errors @@ -298,45 +302,62 @@ function toGpgPath(p) { .replace(/\\/g, '/') .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } +function createGpgHome(prefix) { + const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(), prefix)); + if (process.platform !== 'win32') { + fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); + } + return gpgHome; +} async function importKey(privateKey) { - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(PRIVATE_KEY_FILE, privateKey, { - encoding: 'utf-8', - flag: 'w' - }); - let output = ''; - const options = { - silent: true, - listeners: { - stdout: (data) => { - output += data.toString(); - } + const gpgHome = createGpgHome(GPG_HOME_PREFIX); + const privateKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `private-key-${(0,crypto__WEBPACK_IMPORTED_MODULE_2__.randomUUID)()}.asc`); + try { + fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(privateKeyFile, privateKey, { + encoding: 'utf-8', + flag: 'wx', + mode: 0o600 + }); + try { + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ + '--homedir', + toGpgPath(gpgHome), + '--batch', + '--import', + toGpgPath(privateKeyFile) + ], { silent: true }); } - }; - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ - '--batch', - '--import-options', - 'import-show', - '--import', - PRIVATE_KEY_FILE - ], options); - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(PRIVATE_KEY_FILE); - const match = output.match(PRIVATE_KEY_FINGERPRINT_REGEX); - return match && match[0]; + finally { + fs__WEBPACK_IMPORTED_MODULE_0__.rmSync(privateKeyFile, { force: true }); + } + return gpgHome; + } + catch (error) { + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); + throw error; + } } -async function deleteKey(keyFingerprint) { - await exec.exec('gpg', ['--batch', '--yes', '--delete-secret-and-public-key', keyFingerprint], { - silent: true - }); +async function removeGpgHome(gpgHome) { + if (!gpgHome) { + return; + } + const resolvedGpgHome = path__WEBPACK_IMPORTED_MODULE_1__.resolve(gpgHome); + const resolvedTempDir = path__WEBPACK_IMPORTED_MODULE_1__.resolve(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()); + if (path__WEBPACK_IMPORTED_MODULE_1__.dirname(resolvedGpgHome) !== resolvedTempDir || + !path__WEBPACK_IMPORTED_MODULE_1__.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { + throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); + } + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { - const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__/* .downloadTool */ .bq(signatureUrl); + const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); let gpgHome; try { - gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getTempDir */ .G4(), 'verify-signature-gpg-home-')); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); } catch (error) { try { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(signaturePath); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); } catch { // ignore cleanup failures @@ -347,14 +368,14 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); const options = { silent: true }; - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', toGpgPath(publicKeyFile) ], options); - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', @@ -364,8 +385,8 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(signaturePath); - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(gpgHome); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); } } diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 85410c772..7a244081a 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -49,8 +49,15 @@ async function configureAuthentication() { await createAuthenticationSettings(id, usernameEnvVar, passwordEnvVar, settingsDirectory, overwriteSettings, gpgPassphraseEnvVar); if (gpgPrivateKey) { _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq('Importing private gpg key'); - const keyFingerprint = (await _gpg_js__WEBPACK_IMPORTED_MODULE_5__/* .importKey */ .Fh(gpgPrivateKey)) || ''; - _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .saveState */ .LZ(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .STATE_GPG_PRIVATE_KEY_FINGERPRINT */ .wm, keyFingerprint); + const gpgHome = await _gpg_js__WEBPACK_IMPORTED_MODULE_5__/* .importKey */ .Fh(gpgPrivateKey); + try { + _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .saveState */ .LZ(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .STATE_GPG_HOME */ .Fi, gpgHome); + _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .exportVariable */ .dN('GNUPGHOME', gpgHome); + } + catch (error) { + await _gpg_js__WEBPACK_IMPORTED_MODULE_5__/* .removeGpgHome */ .mS(gpgHome); + throw error; + } } } function getInputWithDeprecatedAlias(inputName, deprecatedInputName, defaultValue) { @@ -124,25 +131,29 @@ async function write(directory, settings, overwriteSettings) { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Fh: () => (/* binding */ importKey), -/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature) +/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), +/* harmony export */ mS: () => (/* binding */ removeGpgHome) /* harmony export */ }); -/* unused harmony exports PRIVATE_KEY_FILE, toGpgPath, deleteKey */ +/* unused harmony exports GPG_HOME_PREFIX, toGpgPath */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6928); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8701); -/* harmony import */ var _actions_exec__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5260); -/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(9805); -/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(6982); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _actions_io__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(8701); +/* harmony import */ var _actions_exec__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(5260); +/* harmony import */ var _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(9805); +/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527); + -const PRIVATE_KEY_FILE = path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getTempDir */ .G4(), 'private-key.asc'); -const PRIVATE_KEY_FINGERPRINT_REGEX = /\w{40}/; +const GPG_HOME_PREFIX = 'setup-java-gpg-'; +const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // internally. Passing Windows paths with backslashes can cause fatal GPG errors @@ -154,45 +165,62 @@ function toGpgPath(p) { .replace(/\\/g, '/') .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } +function createGpgHome(prefix) { + const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(), prefix)); + if (process.platform !== 'win32') { + fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); + } + return gpgHome; +} async function importKey(privateKey) { - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(PRIVATE_KEY_FILE, privateKey, { - encoding: 'utf-8', - flag: 'w' - }); - let output = ''; - const options = { - silent: true, - listeners: { - stdout: (data) => { - output += data.toString(); - } + const gpgHome = createGpgHome(GPG_HOME_PREFIX); + const privateKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `private-key-${(0,crypto__WEBPACK_IMPORTED_MODULE_2__.randomUUID)()}.asc`); + try { + fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(privateKeyFile, privateKey, { + encoding: 'utf-8', + flag: 'wx', + mode: 0o600 + }); + try { + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ + '--homedir', + toGpgPath(gpgHome), + '--batch', + '--import', + toGpgPath(privateKeyFile) + ], { silent: true }); + } + finally { + fs__WEBPACK_IMPORTED_MODULE_0__.rmSync(privateKeyFile, { force: true }); } - }; - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ - '--batch', - '--import-options', - 'import-show', - '--import', - PRIVATE_KEY_FILE - ], options); - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(PRIVATE_KEY_FILE); - const match = output.match(PRIVATE_KEY_FINGERPRINT_REGEX); - return match && match[0]; + return gpgHome; + } + catch (error) { + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); + throw error; + } } -async function deleteKey(keyFingerprint) { - await exec.exec('gpg', ['--batch', '--yes', '--delete-secret-and-public-key', keyFingerprint], { - silent: true - }); +async function removeGpgHome(gpgHome) { + if (!gpgHome) { + return; + } + const resolvedGpgHome = path__WEBPACK_IMPORTED_MODULE_1__.resolve(gpgHome); + const resolvedTempDir = path__WEBPACK_IMPORTED_MODULE_1__.resolve(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()); + if (path__WEBPACK_IMPORTED_MODULE_1__.dirname(resolvedGpgHome) !== resolvedTempDir || + !path__WEBPACK_IMPORTED_MODULE_1__.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { + throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); + } + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { - const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_4__/* .downloadTool */ .bq(signatureUrl); + const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); let gpgHome; try { - gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getTempDir */ .G4(), 'verify-signature-gpg-home-')); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); } catch (error) { try { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(signaturePath); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); } catch { // ignore cleanup failures @@ -203,14 +231,14 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); const options = { silent: true }; - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', toGpgPath(publicKeyFile) ], options); - await _actions_exec__WEBPACK_IMPORTED_MODULE_3__/* .exec */ .m('gpg', [ + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', @@ -220,8 +248,8 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(signaturePath); - await _actions_io__WEBPACK_IMPORTED_MODULE_2__/* .rmRF */ .Yz(gpgHome); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); } } diff --git a/dist/setup/index.js b/dist/setup/index.js index 17d8e1d95..9a94a86b1 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -30770,6 +30770,7 @@ module.exports = { /* harmony export */ __nccwpck_require__.d(__webpack_exports__, { /* harmony export */ At: () => (/* binding */ INPUT_CACHE_DEPENDENCY_PATH), /* harmony export */ E8: () => (/* binding */ INPUT_SET_DEFAULT), +/* harmony export */ Fi: () => (/* binding */ STATE_GPG_HOME), /* harmony export */ GL: () => (/* binding */ INPUT_CACHE_JDK), /* harmony export */ I9: () => (/* binding */ INPUT_FORCE_DOWNLOAD), /* harmony export */ K$: () => (/* binding */ GPG_PASSPHRASE_PROFILE_ID), @@ -30810,7 +30811,6 @@ module.exports = { /* harmony export */ vO: () => (/* binding */ MVN_SETTINGS_FILE), /* harmony export */ wX: () => (/* binding */ INPUT_SHOW_DOWNLOAD_PROGRESS), /* harmony export */ wc: () => (/* binding */ INPUT_JDK_FILE_DEPRECATED), -/* harmony export */ wm: () => (/* binding */ STATE_GPG_PRIVATE_KEY_FINGERPRINT), /* harmony export */ wz: () => (/* binding */ INPUT_GPG_PRIVATE_KEY), /* harmony export */ xp: () => (/* binding */ INPUT_DEFAULT_SERVER_PASSWORD) /* harmony export */ }); @@ -30855,7 +30855,7 @@ const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; const INPUT_CACHE_PATH = 'cache-path'; const INPUT_CACHE_READ_ONLY = 'cache-read-only'; const INPUT_JOB_STATUS = 'job-status'; -const STATE_GPG_PRIVATE_KEY_FINGERPRINT = 'gpg-private-key-fingerprint'; +const STATE_GPG_HOME = 'gpg-home'; const M2_DIR = '.m2'; const MVN_SETTINGS_FILE = 'settings.xml'; const MVN_TOOLCHAINS_FILE = 'toolchains.xml'; diff --git a/src/gpg.ts b/src/gpg.ts index 9abd8d391..b89ff0161 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -31,10 +31,7 @@ function createGpgHome(prefix: string): string { export async function importKey(privateKey: string): Promise { const gpgHome = createGpgHome(GPG_HOME_PREFIX); - const privateKeyFile = path.join( - gpgHome, - `private-key-${randomUUID()}.asc` - ); + const privateKeyFile = path.join(gpgHome, `private-key-${randomUUID()}.asc`); try { fs.writeFileSync(privateKeyFile, privateKey, { From 53f738690fa6a0223ba16f1dbb6f3f955a9b77e8 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 5 Aug 2026 11:53:12 -0400 Subject: [PATCH 4/4] Address isolated GPG home review feedback Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/auth.test.ts | 17 +++++++++++++---- __tests__/gpg.test.ts | 18 ++++++++++++++++++ dist/cleanup/index.js | 11 ++++++++++- dist/setup/220.index.js | 16 +++++++++++++--- dist/setup/463.index.js | 16 +++++++++++++--- dist/setup/81.index.js | 18 ++++++++++++++---- src/auth.ts | 2 +- src/gpg.ts | 16 +++++++++++++++- 8 files changed, 97 insertions(+), 17 deletions(-) diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index 36bbcc79b..7457eb603 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -43,7 +43,8 @@ jest.unstable_mockModule('@actions/core', () => ({ jest.unstable_mockModule('../src/gpg.js', () => ({ importKey: jest.fn(), - removeGpgHome: jest.fn() + removeGpgHome: jest.fn(), + toGpgPath: jest.fn() })); // Dynamic imports after mocking @@ -67,6 +68,7 @@ describe('auth tests', () => { spyOSHomedir.mockReturnValue(__dirname); spyInfo = core.info as jest.Mock; spyInfo.mockImplementation(() => null); + (gpg.toGpgPath as jest.Mock).mockImplementation((p: string) => p); }, 300000); afterEach(() => { @@ -74,6 +76,7 @@ describe('auth tests', () => { (core.exportVariable as jest.Mock).mockReset(); (gpg.importKey as jest.Mock).mockReset(); (gpg.removeGpgHome as jest.Mock).mockReset(); + (gpg.toGpgPath as jest.Mock).mockReset(); }); afterAll(async () => { @@ -158,9 +161,11 @@ describe('auth tests', () => { ); }, 100000); - it('exports and persists the isolated GPG home', async () => { - const gpgHome = path.join(__dirname, 'runner', 'temp', 'setup-java-gpg-1'); + it('exports a GPG-compatible path and persists the native GPG home', async () => { + const gpgHome = 'D:\\a\\_temp\\setup-java-gpg-1'; + const exportedGpgHome = '/d/a/_temp/setup-java-gpg-1'; (gpg.importKey as jest.Mock).mockResolvedValue(gpgHome); + (gpg.toGpgPath as jest.Mock).mockReturnValue(exportedGpgHome); (core.getInput as jest.Mock).mockImplementation((name: string) => { const inputs: Record = { 'server-id': 'packages', @@ -176,7 +181,11 @@ describe('auth tests', () => { expect(gpg.importKey).toHaveBeenCalledWith('KEY ONE\nKEY TWO'); expect(core.saveState).toHaveBeenCalledWith(STATE_GPG_HOME, gpgHome); - expect(core.exportVariable).toHaveBeenCalledWith('GNUPGHOME', gpgHome); + expect(gpg.toGpgPath).toHaveBeenCalledWith(gpgHome); + expect(core.exportVariable).toHaveBeenCalledWith( + 'GNUPGHOME', + exportedGpgHome + ); }); it('removes the isolated GPG home when environment export fails', async () => { diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts index 84bf1631a..db69e3de8 100644 --- a/__tests__/gpg.test.ts +++ b/__tests__/gpg.test.ts @@ -188,10 +188,28 @@ describe('gpg tests', () => { await gpg.removeGpgHome(gpgHome); await gpg.removeGpgHome(gpgHome); + expect(exec.exec).toHaveBeenNthCalledWith( + 2, + 'gpgconf', + ['--homedir', gpg.toGpgPath(gpgHome), '--kill', 'gpg-agent'], + {silent: true, ignoreReturnCode: true} + ); + expect(exec.exec).toHaveBeenCalledTimes(2); expect(fs.existsSync(gpgHome)).toBe(false); expect(fs.existsSync(unrelatedGpgHome)).toBe(true); }); + it('removes the GPG home when gpgconf is unavailable', async () => { + const gpgHome = await gpg.importKey('KEY CONTENTS'); + (exec.exec as jest.Mock).mockRejectedValueOnce( + new Error('gpgconf not found') + ); + + await gpg.removeGpgHome(gpgHome); + + expect(fs.existsSync(gpgHome)).toBe(false); + }); + it('refuses to remove a GPG home it does not own', async () => { const unrelatedGpgHome = path.join(tempDir, 'user-gpg-home'); fs.mkdirSync(unrelatedGpgHome, {recursive: true}); diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index d45ff029e..dcc63218f 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -35724,7 +35724,7 @@ async function importKey(privateKey) { return gpgHome; } catch (error) { - await io.rmRF(gpgHome); + await removeGpgHome(gpgHome); throw error; } } @@ -35738,6 +35738,15 @@ async function removeGpgHome(gpgHome) { !external_path_.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); } + if (!external_fs_.existsSync(resolvedGpgHome)) { + return; + } + try { + await lib_exec/* exec */.m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + } + catch { + // gpgconf may be unavailable, but directory removal must still be attempted. + } await lib_io/* rmRF */.Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { diff --git a/dist/setup/220.index.js b/dist/setup/220.index.js index 3f27b2b7f..e9ba3c91f 100644 --- a/dist/setup/220.index.js +++ b/dist/setup/220.index.js @@ -171,9 +171,10 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), -/* harmony export */ mS: () => (/* binding */ removeGpgHome) +/* harmony export */ mS: () => (/* binding */ removeGpgHome), +/* harmony export */ nY: () => (/* binding */ toGpgPath) /* harmony export */ }); -/* unused harmony exports GPG_HOME_PREFIX, toGpgPath */ +/* unused harmony export GPG_HOME_PREFIX */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6928); @@ -235,7 +236,7 @@ async function importKey(privateKey) { return gpgHome; } catch (error) { - await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); + await removeGpgHome(gpgHome); throw error; } } @@ -249,6 +250,15 @@ async function removeGpgHome(gpgHome) { !path__WEBPACK_IMPORTED_MODULE_1__.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); } + if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { + return; + } + try { + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + } + catch { + // gpgconf may be unavailable, but directory removal must still be attempted. + } await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { diff --git a/dist/setup/463.index.js b/dist/setup/463.index.js index 9e85602b0..c0578ee6f 100644 --- a/dist/setup/463.index.js +++ b/dist/setup/463.index.js @@ -269,9 +269,10 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), -/* harmony export */ mS: () => (/* binding */ removeGpgHome) +/* harmony export */ mS: () => (/* binding */ removeGpgHome), +/* harmony export */ nY: () => (/* binding */ toGpgPath) /* harmony export */ }); -/* unused harmony exports GPG_HOME_PREFIX, toGpgPath */ +/* unused harmony export GPG_HOME_PREFIX */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6928); @@ -333,7 +334,7 @@ async function importKey(privateKey) { return gpgHome; } catch (error) { - await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); + await removeGpgHome(gpgHome); throw error; } } @@ -347,6 +348,15 @@ async function removeGpgHome(gpgHome) { !path__WEBPACK_IMPORTED_MODULE_1__.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); } + if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { + return; + } + try { + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + } + catch { + // gpgconf may be unavailable, but directory removal must still be attempted. + } await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 7a244081a..4d98b9c2a 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -52,7 +52,7 @@ async function configureAuthentication() { const gpgHome = await _gpg_js__WEBPACK_IMPORTED_MODULE_5__/* .importKey */ .Fh(gpgPrivateKey); try { _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .saveState */ .LZ(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .STATE_GPG_HOME */ .Fi, gpgHome); - _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .exportVariable */ .dN('GNUPGHOME', gpgHome); + _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .exportVariable */ .dN('GNUPGHOME', _gpg_js__WEBPACK_IMPORTED_MODULE_5__/* .toGpgPath */ .nY(gpgHome)); } catch (error) { await _gpg_js__WEBPACK_IMPORTED_MODULE_5__/* .removeGpgHome */ .mS(gpgHome); @@ -132,9 +132,10 @@ async function write(directory, settings, overwriteSettings) { /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), -/* harmony export */ mS: () => (/* binding */ removeGpgHome) +/* harmony export */ mS: () => (/* binding */ removeGpgHome), +/* harmony export */ nY: () => (/* binding */ toGpgPath) /* harmony export */ }); -/* unused harmony exports GPG_HOME_PREFIX, toGpgPath */ +/* unused harmony export GPG_HOME_PREFIX */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6928); @@ -196,7 +197,7 @@ async function importKey(privateKey) { return gpgHome; } catch (error) { - await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); + await removeGpgHome(gpgHome); throw error; } } @@ -210,6 +211,15 @@ async function removeGpgHome(gpgHome) { !path__WEBPACK_IMPORTED_MODULE_1__.basename(resolvedGpgHome).startsWith(GPG_HOME_PREFIX)) { throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); } + if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { + return; + } + try { + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + } + catch { + // gpgconf may be unavailable, but directory removal must still be attempted. + } await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { diff --git a/src/auth.ts b/src/auth.ts index fcad90fea..49bf9ae34 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -55,7 +55,7 @@ export async function configureAuthentication() { const gpgHome = await gpg.importKey(gpgPrivateKey); try { core.saveState(constants.STATE_GPG_HOME, gpgHome); - core.exportVariable('GNUPGHOME', gpgHome); + core.exportVariable('GNUPGHOME', gpg.toGpgPath(gpgHome)); } catch (error) { await gpg.removeGpgHome(gpgHome); throw error; diff --git a/src/gpg.ts b/src/gpg.ts index b89ff0161..46e0a6ce5 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -58,7 +58,7 @@ export async function importKey(privateKey: string): Promise { return gpgHome; } catch (error) { - await io.rmRF(gpgHome); + await removeGpgHome(gpgHome); throw error; } } @@ -77,6 +77,20 @@ export async function removeGpgHome(gpgHome: string): Promise { throw new Error(`Refusing to remove unexpected GPG home: ${gpgHome}`); } + if (!fs.existsSync(resolvedGpgHome)) { + return; + } + + try { + await exec.exec( + 'gpgconf', + ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], + {silent: true, ignoreReturnCode: true} + ); + } catch { + // gpgconf may be unavailable, but directory removal must still be attempted. + } + await io.rmRF(resolvedGpgHome); }