From 85030b78b58b52f56c1ed3d0e893d2c93117274b Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Mon, 24 Aug 2026 12:03:01 -0400 Subject: [PATCH 1/8] docs: complete v6 release highlights (#1254) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: bb8de955-60c9-4a56-a7a2-d0be5d32dbc4 --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b38f3c5a..4e6e73a96 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,17 @@ steps: ### V6 - Migrated the action implementation to ESM to support the latest `@actions/*` packages. -- Added the `oracle-openjdk`, `redhat`, and `liberica-nik` distributions. +- Added Oracle OpenJDK (`oracle-openjdk`), Red Hat Build of OpenJDK (`redhat`), and Liberica Native Image Kit (`liberica-nik`), and expanded Tencent Kona support through JDK 25. - Added `java-version: latest` to resolve the newest stable GA release from the distribution's remote metadata. +- Expanded install compatibility with JEP 322 multi-field versions such as `18.0.1.1`, Temurin `jdk+jmods` packages, and native musl artifacts on Alpine for Dragonwell, Corretto, Zulu, and Liberica. - JDK downloads now automatically verify authoritative checksums, and package signature verification defaults to enabled for Temurin and Microsoft builds. - Added `force-download: true` to bypass the tool cache and perform a reproducible fresh install. - Dependency caching now supports custom paths with `cache-path` and restore-only operation with `cache-read-only: true`. +- Dependency cache keys now include `.mvn/extensions.xml` and `gradle.properties`, preventing stale restores when Maven extensions or Gradle dependency properties change. - Downloaded JDKs are now [cached](#caching-jdk-installations) automatically when `cache` is set; use `cache-jdk` to enable or disable it independently. +- Warm JDK-cached jobs can reuse cached release metadata, avoiding vendor API calls while retaining a stale-metadata fallback for vendor outages and rate limits. - Maven configuration now supports multiple server credentials and custom dependency-resolution repositories. +- Maven signing keys are imported into an isolated temporary GPG home instead of the runner's default keyring. - Set `problem-matcher: false` to disable Java compiler and uncaught-exception annotations. - GraalVM distributions now set `GRAALVM_HOME` in addition to `JAVA_HOME`. - Invalid boolean values, unsupported distribution/package/platform combinations, and mismatched Maven toolchain ID counts now fail with targeted errors. @@ -65,6 +69,7 @@ steps: - Deprecated aliases still work, but emit warnings. - Maven GPG passphrases are now passed through `gpg.passphraseEnvName` instead of a deprecated `gpg.passphrase` server entry in `settings.xml`. This requires `maven-gpg-plugin` 3.2.0 or newer. See [GPG](docs/advanced-usage.md#gpg). - Legacy AdoptOpenJDK distributions were removed. Use `temurin` instead of `adopt` or `adopt-hotspot`, and `semeru` instead of `adopt-openj9`. +- See the [complete V6 release notes](https://github.com/actions/setup-java/releases/tag/v6.0.0) for all enhancements and fixes. ### V5 From 1a8f22b7f7b115efae2d0dbe944753d561a53801 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Mon, 24 Aug 2026 12:32:37 -0400 Subject: [PATCH 2/8] chore: streamline Dependabot updates (#1255) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 916e0cf3-84f7-43fa-9822-3dac5cf8c5e6 --- .github/dependabot.yml | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 853bc0a13..f85e23877 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,25 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file - version: 2 updates: - # Enable version updates for npm - package-ecosystem: 'npm' - # Look for `package.json` and `lock` files in the `root` directory directory: '/' - # Check the npm registry for updates every day (weekdays) schedule: - interval: 'weekly' + interval: 'monthly' + cooldown: + default-days: 7 + groups: + monthly-npm-updates: + applies-to: 'version-updates' + patterns: + - '*' - # Enable version updates for GitHub Actions - package-ecosystem: 'github-actions' - # Workflow files stored in the default location of `.github/workflows` - # You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`. directory: '/' schedule: - interval: 'weekly' + interval: 'monthly' + cooldown: + default-days: 7 + groups: + monthly-actions-updates: + applies-to: 'version-updates' + patterns: + - '*' From 273229181560643261a71a1544c22304f2a40df8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:26:11 -0400 Subject: [PATCH 3/8] chore(deps-dev): update eslint and globals (#1256) * chore(deps-dev): bump the monthly-npm-updates group with 3 updates Bumps the monthly-npm-updates group with 3 updates: [eslint](https://github.com/eslint/eslint), [globals](https://github.com/sindresorhus/globals) and [typescript](https://github.com/microsoft/TypeScript). Updates `eslint` from 10.8.0 to 10.8.1 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.8.0...v10.8.1) Updates `globals` from 17.9.0 to 17.11.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0) Updates `typescript` from 6.0.3 to 7.0.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2) --- updated-dependencies: - dependency-name: eslint dependency-version: 10.8.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: monthly-npm-updates - dependency-name: globals dependency-version: 17.11.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: typescript dependency-version: 7.0.2 dependency-type: direct:development update-type: version-update:semver-major dependency-group: monthly-npm-updates ... Signed-off-by: dependabot[bot] * fix(deps): keep TypeScript on supported version TypeScript 7 is outside the peer dependency ranges supported by typescript-eslint and ts-jest, causing npm ci to fail before dist can be rebuilt. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f1ca1f37-990b-4feb-9e3d-708cfdccbb6e --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bruno Borges Copilot-Session: f1ca1f37-990b-4feb-9e3d-708cfdccbb6e --- package-lock.json | 311 +++++++++++++++++++++++++++++++++++++++------- package.json | 4 +- 2 files changed, 270 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4ac71414..9ee474f69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,11 +27,11 @@ "@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/parser": "^8.65.0", "@vercel/ncc": "^0.45.0", - "eslint": "^10.7.0", + "eslint": "^10.8.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jest": "^29.16.1", "eslint-plugin-n": "^18.3.0", - "globals": "^17.9.0", + "globals": "^17.11.0", "husky": "^9.1.7", "jest": "^30.4.2", "lint-staged": "^17.3.0", @@ -1795,18 +1795,18 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", - "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", + "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.67.0", "@typescript-eslint/types": "8.67.0", "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", - "debug": "^4.4.3" + "@typescript-eslint/utils": "8.67.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1820,7 +1820,35 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/project-service": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { "version": "8.67.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", @@ -1842,30 +1870,35 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/scope-manager": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", - "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1874,20 +1907,25 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", - "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", "dev": true, "license": "MIT", "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "engines": { @@ -1898,25 +1936,74 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/types": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", - "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree": { + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", + "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { "version": "8.67.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", @@ -1944,17 +2031,16 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils": { + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1964,10 +2050,58 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", + "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", + "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.67.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", @@ -3010,9 +3144,9 @@ } }, "node_modules/eslint": { - "version": "10.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", - "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", + "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -3152,6 +3286,97 @@ } } }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", + "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/typescript-estree": "8.67.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", + "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.67.0", + "@typescript-eslint/tsconfig-utils": "8.67.0", + "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/visitor-keys": "8.67.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", + "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.67.0", + "@typescript-eslint/types": "^8.67.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.67.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", + "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/eslint-plugin-n": { "version": "18.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.3.0.tgz", @@ -3691,9 +3916,9 @@ } }, "node_modules/globals": { - "version": "17.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", - "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 29069ace8..b6d0bc576 100644 --- a/package.json +++ b/package.json @@ -60,11 +60,11 @@ "@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/parser": "^8.65.0", "@vercel/ncc": "^0.45.0", - "eslint": "^10.7.0", + "eslint": "^10.8.1", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jest": "^29.16.1", "eslint-plugin-n": "^18.3.0", - "globals": "^17.9.0", + "globals": "^17.11.0", "husky": "^9.1.7", "jest": "^30.4.2", "lint-staged": "^17.3.0", From 8fd32400856216412ac77ad6dc67cca66ecffb93 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 07:39:25 -0400 Subject: [PATCH 4/8] [WIP] Fix failing GitHub Actions job for temurin 17 (#1259) * Initial plan * Install GnuPG in Alpine E2E job --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- .github/workflows/e2e-versions.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml index 552d7a2af..3f219b7bd 100644 --- a/.github/workflows/e2e-versions.yml +++ b/.github/workflows/e2e-versions.yml @@ -154,8 +154,8 @@ jobs: version: ['21', '17'] steps: - *checkout_step - - name: Install bash - run: apk add --no-cache bash + - name: Install bash and GnuPG + run: apk add --no-cache bash gnupg - name: setup-java uses: ./ id: setup-java From 4889c4aff54cecfc8d479c505d9893fd814df73f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 10:34:10 -0400 Subject: [PATCH 5/8] Fix Temurin EA E2E signature verification (#1260) * Configure Temurin EA E2E signature verification Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> * Disable signature verification for Temurin EA E2E Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> * Fix Temurin EA signature verification scope Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7ac2d888-2383-49c7-9178-da5455a10034 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> Co-authored-by: Bruno Borges Copilot-Session: 7ac2d888-2383-49c7-9178-da5455a10034 --- .github/workflows/e2e-versions.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/e2e-versions.yml b/.github/workflows/e2e-versions.yml index 3f219b7bd..c08b5f08d 100644 --- a/.github/workflows/e2e-versions.yml +++ b/.github/workflows/e2e-versions.yml @@ -340,6 +340,7 @@ jobs: with: java-version: ${{ matrix.version }} distribution: ${{ matrix.distribution }} + verify-signature: ${{ matrix.distribution == 'temurin' && contains(matrix.version, '-ea') && 'false' || '' }} - name: Verify Java env: JAVA_VERSION: ${{ matrix.version }} From 0781fc6af30d3e1f67f2d243034e1a15cf5f3677 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Thu, 3 Sep 2026 13:27:24 -0400 Subject: [PATCH 6/8] Fix alpine failures by switching default back to only warn on verification failures. To prevent build failures due to missing GPG or rotated vendor keys. (#1262) Also allow multiple GPG keys to be provided. Co-authored-by: John <1615532+johnoliver@users.noreply.github.com> --- README.md | 35 ++++- __tests__/cleanup-java.test.ts | 2 +- __tests__/distributors/base-installer.test.ts | 19 ++- .../distributors/local-installer.test.ts | 6 +- .../distributors/microsoft-installer.test.ts | 58 ++++++++ .../distributors/temurin-installer.test.ts | 129 ++++++++++++++++++ __tests__/gpg.test.ts | 29 +++- __tests__/jdk-cache.test.ts | 63 ++++++--- action.yml | 4 +- dist/cleanup/314.index.js | 23 +++- dist/cleanup/index.js | 19 ++- dist/setup/19.index.js | 2 +- dist/setup/220.index.js | 41 ++++-- dist/setup/242.index.js | 5 +- dist/setup/463.index.js | 47 +++++-- dist/setup/779.index.js | 23 +++- dist/setup/81.index.js | 18 ++- dist/setup/index.js | 5 +- docs/advanced-usage.md | 29 ++-- src/constants.ts | 3 + src/distributions/base-installer.ts | 11 +- src/distributions/base-models.ts | 4 +- src/distributions/local/installer.ts | 2 +- src/distributions/microsoft/installer.ts | 38 ++++-- src/distributions/temurin/installer.ts | 54 +++++--- src/gpg.ts | 21 ++- src/jdk-cache.ts | 25 +++- 27 files changed, 577 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index 4e6e73a96..f76c65e73 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ steps: - Added Oracle OpenJDK (`oracle-openjdk`), Red Hat Build of OpenJDK (`redhat`), and Liberica Native Image Kit (`liberica-nik`), and expanded Tencent Kona support through JDK 25. - Added `java-version: latest` to resolve the newest stable GA release from the distribution's remote metadata. - Expanded install compatibility with JEP 322 multi-field versions such as `18.0.1.1`, Temurin `jdk+jmods` packages, and native musl artifacts on Alpine for Dragonwell, Corretto, Zulu, and Liberica. -- JDK downloads now automatically verify authoritative checksums, and package signature verification defaults to enabled for Temurin and Microsoft builds. +- JDK downloads now automatically verify authoritative checksums. Package signature verification is supported for Temurin and Microsoft builds, with configurable strict enforcement. - Added `force-download: true` to bypass the tool cache and perform a reproducible fresh install. - Dependency caching now supports custom paths with `cache-path` and restore-only operation with `cache-read-only: true`. - Dependency cache keys now include `.mvn/extensions.xml` and `gradle.properties`, preventing stale restores when Maven extensions or Gradle dependency properties change. @@ -156,8 +156,8 @@ steps: | `force-download` | Always download Java and replace any matching version in the tool cache. | `false` | | `set-default` | Add Java to `PATH` and set `JAVA_HOME`. When `false`, only version-specific `JAVA_HOME__` variables are set. | `true` | | `problem-matcher` | Register Java compiler and uncaught exception problem matchers. | `true` | -| `verify-signature` | Verify downloaded Java package signatures when supported. Defaults to `true` for `temurin` and `microsoft`, and `false` for other distributions. | Automatically enabled for `temurin` and `microsoft` | -| `verify-signature-public-key` | ASCII-armored GPG public key to use for signature verification. Overrides the bundled key. | | +| `verify-signature` | Verify downloaded Java package signatures when supported. Explicitly setting this to `true` makes verification failures fatal. | Distribution-dependent; see [Download integrity and signatures](#download-integrity-and-signatures) | +| `verify-signature-public-key` | One or more ASCII-armored GPG public keys used for signature verification. Concatenate multiple armored key blocks. Custom keys replace the bundled distribution keys. | | | `token` | Token for fetching GitHub.com-hosted version manifests, useful on GitHub Enterprise Server when unauthenticated requests are rate-limited. | `${{ github.token }}` on GitHub.com; empty string on GHES | | `cache` | Enable dependency caching for `maven`, `gradle`, or `sbt`. | | | `cache-jdk` | Cache downloaded JDK installations between jobs. When omitted, JDK caching is enabled only if `cache` is set. Set explicitly to `true` or `false` to override. | Enabled when `cache` is set | @@ -247,7 +247,34 @@ GitHub-hosted runners primarily pre-cache Eclipse Temurin JDKs. See the installe Distributions or individual releases without an authoritative checksum continue to install normally, with the omission reported in debug logs. Installations resolved directly from the runner tool cache — including JDKs preinstalled on the runner image and JDKs installed by an earlier step of the same job — are not downloaded again and are not reverified, even when `verify-signature: true` is set. Use `force-download: true` to always download and verify the archive. -Use `verify-signature: true` to verify package signatures for distributions that support it. Currently supported distributions are `temurin` and `microsoft`; setting it for an unsupported distribution fails the workflow. +Package signature verification is supported for `temurin` and `microsoft`. When `verify-signature` is omitted, the action checks the signature and warns if GPG is unavailable or verification fails, but does not enforce the result. Explicitly setting `verify-signature: true` enforces verification and makes these failures fatal. Setting `verify-signature: true` for an unsupported distribution also fails the workflow. + +> [!WARNING] +> Requesting explicit signature verification with verify-signature can fail a build after an unexpected but legitimate vendor signing-key rotation, because the action's bundled keys may not yet include the new key. Confirm a new key through the vendor's trusted documentation before using it. + +After confirming a legitimate rotation, configure the updated key with `verify-signature-public-key`. The input accepts one or more ASCII-armored public keys; concatenate complete armored key blocks when both old and new vendor keys are needed during a transition. Custom keys replace, rather than extend, the keys bundled with the selected distribution. + +```yaml +- uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: '25' + verify-signature: true + verify-signature-public-key: | + -----BEGIN PGP PUBLIC KEY BLOCK----- + ...vendor key material... + -----END PGP PUBLIC KEY BLOCK----- +``` + +As a temporary fallback while a legitimate rotation is being investigated, set `verify-signature: false`. This disables package signature verification, although authoritative checksum verification still applies when the vendor publishes a checksum. + +```yaml +- uses: actions/setup-java@v6 + with: + distribution: temurin + java-version: '25' + verify-signature: false +``` ## Caching diff --git a/__tests__/cleanup-java.test.ts b/__tests__/cleanup-java.test.ts index 63b0df8a0..36441fbf6 100644 --- a/__tests__/cleanup-java.test.ts +++ b/__tests__/cleanup-java.test.ts @@ -393,7 +393,7 @@ function createRegisteredJdk(version = '21.0.8+9') { architecture: 'x64', version, source: `sha256:${path.basename(root)}`, - verification: 'unverified', + verification: 'disabled', path: jdkPath }; registerJdk(jdk); diff --git a/__tests__/distributors/base-installer.test.ts b/__tests__/distributors/base-installer.test.ts index 825d792a3..281f73e5e 100644 --- a/__tests__/distributors/base-installer.test.ts +++ b/__tests__/distributors/base-installer.test.ts @@ -71,8 +71,11 @@ jest.unstable_mockModule('@actions/tool-cache', () => ({ })); jest.unstable_mockModule('../../src/jdk-cache.js', () => ({ - getJdkVerificationIdentity: jest.fn((verified: boolean, key?: string) => - verified ? (key ? 'verified:custom' : 'verified:bundled') : 'unverified' + getJdkVerificationIdentity: jest.fn( + (verified: boolean, enforced: boolean, key?: string) => + verified + ? `${enforced ? 'enforced' : 'check-and-warn'}:${key ? 'custom' : 'bundled'}` + : 'disabled' ), registerJdk: jest.fn(), restoreJdk: jest.fn() @@ -395,8 +398,10 @@ describe('setupJava', () => { beforeEach(() => { (jdkCache.getJdkVerificationIdentity as jest.Mock).mockImplementation( - (verified: boolean, key?: string) => - verified ? (key ? 'verified:custom' : 'verified:bundled') : 'unverified' + (verified: boolean, enforced: boolean, key?: string) => + verified + ? `${enforced ? 'enforced' : 'check-and-warn'}:${key ? 'custom' : 'bundled'}` + : 'disabled' ); spyGetToolcachePath = util.getToolcachePath as jest.Mock; spyGetToolcachePath.mockImplementation( @@ -826,7 +831,7 @@ describe('setupJava', () => { expect(jdkCache.registerJdk).toHaveBeenCalledWith( expect.objectContaining({ version: actualJavaVersion, - verification: 'unverified' + verification: 'disabled' }) ); }); @@ -886,7 +891,7 @@ describe('setupJava', () => { architecture: 'x86', version: actualJavaVersion, source: `some/random_url/java/${actualJavaVersion}`, - verification: 'unverified', + verification: 'disabled', path: path.join(toolCachePath, 'Java_Empty_jdk', actualJavaVersion) }); expect(downloadTool).not.toHaveBeenCalled(); @@ -919,7 +924,7 @@ describe('setupJava', () => { architecture: 'x86', version: actualJavaVersion, source: `some/random_url/java/${actualJavaVersion}`, - verification: 'unverified', + verification: 'disabled', path: path.join(toolCachePath, 'Java_Empty_jdk', actualJavaVersion) }; expect(jdkCache.restoreJdk).toHaveBeenCalledWith(expectedIdentity); diff --git a/__tests__/distributors/local-installer.test.ts b/__tests__/distributors/local-installer.test.ts index f20ff6ad4..6d0521fc9 100644 --- a/__tests__/distributors/local-installer.test.ts +++ b/__tests__/distributors/local-installer.test.ts @@ -58,7 +58,7 @@ jest.unstable_mockModule('@actions/tool-cache', () => ({ })); jest.unstable_mockModule('../../src/jdk-cache.js', () => ({ - getJdkVerificationIdentity: jest.fn(() => 'unverified'), + getJdkVerificationIdentity: jest.fn(() => 'disabled'), registerJdk: jest.fn(), restoreJdk: jest.fn() })); @@ -106,7 +106,7 @@ describe('setupJava', () => { beforeEach(() => { (jdkCache.getJdkVerificationIdentity as jest.Mock).mockReturnValue( - 'unverified' + 'disabled' ); spyGetToolcachePath = util.getToolcachePath as jest.Mock; spyGetToolcachePath.mockImplementation( @@ -283,7 +283,7 @@ describe('setupJava', () => { expect.objectContaining({ distribution: 'jdkfile', version: actualJavaVersion, - verification: 'unverified' + verification: 'disabled' }) ); } finally { diff --git a/__tests__/distributors/microsoft-installer.test.ts b/__tests__/distributors/microsoft-installer.test.ts index dcb100ba9..7260b5e4c 100644 --- a/__tests__/distributors/microsoft-installer.test.ts +++ b/__tests__/distributors/microsoft-installer.test.ts @@ -444,6 +444,64 @@ describe('downloadTool', () => { ); }); + it('warns with key rotation recovery guidance when default verification fails', async () => { + spyVerifySignature.mockRejectedValue(new Error('bad signature')); + const signedDistribution = new MicrosoftDistributions({ + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + + await signedDistribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz', + signatureUrl: 'https://example.com/jdk.tar.gz.sig' + }); + + expect(core.warning).toHaveBeenCalledWith( + expect.stringMatching( + /bad signature.*https:\/\/github\.com\/actions\/setup-java#download-integrity-and-signatures/ + ) + ); + expect(spyExtractJdkFile).toHaveBeenCalled(); + }); + + it('fails with recovery guidance when verification is explicitly enabled', async () => { + spyVerifySignature.mockRejectedValue(new Error('bad signature')); + const signedDistribution = new MicrosoftDistributions({ + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false, + verifySignature: true + }); + + await expect( + signedDistribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz', + signatureUrl: 'https://example.com/jdk.tar.gz.sig' + }) + ).rejects.toThrow( + /bad signature.*https:\/\/github\.com\/actions\/setup-java#download-integrity-and-signatures/ + ); + expect(spyExtractJdkFile).not.toHaveBeenCalled(); + }); + + it('warns when the signature is missing during default verification', async () => { + await distribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz' + }); + + expect(core.warning).toHaveBeenCalledWith( + "Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version 17.0.14+7." + ); + expect(spyVerifySignature).not.toHaveBeenCalled(); + expect(spyExtractJdkFile).toHaveBeenCalled(); + }); + it('fails when signature is missing and verification is enabled', async () => { const signedDistribution = new MicrosoftDistributions({ version: '17', diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index ea4c74577..7606627e6 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -73,6 +73,7 @@ jest.unstable_mockModule('../../src/util.js', () => ({ jest.unstable_mockModule('../../src/gpg.js', () => ({ importKey: jest.fn(), removeGpgHome: jest.fn(), + isGpgAvailable: jest.fn(), verifyPackageSignature: jest.fn() })); @@ -437,6 +438,7 @@ describe('downloadTool', () => { beforeEach(() => { spyDownloadTool = tc.downloadTool as jest.Mock; spyDownloadTool.mockResolvedValue('/tmp/jdk.tar.gz'); + (gpg.isGpgAvailable as jest.Mock).mockResolvedValue(true); spyVerifySignature = gpg.verifyPackageSignature as jest.Mock; spyVerifySignature.mockResolvedValue(undefined); spyExtractJdkFile = util.extractJdkFile as jest.Mock; @@ -502,6 +504,133 @@ describe('downloadTool', () => { expect(spyVerifySignature).not.toHaveBeenCalled(); }); + it('skips implicit signature verification when gpg is unavailable', async () => { + (gpg.isGpgAvailable as jest.Mock).mockResolvedValue(false); + const distribution = new TemurinDistribution( + { + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + + await expect( + distribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz', + signatureUrl: 'https://example.com/jdk.tar.gz.sig' + }) + ).resolves.toEqual({version: '17.0.14+7', path: '/tmp/toolcache'}); + + expect(spyVerifySignature).not.toHaveBeenCalled(); + expect(core.warning).toHaveBeenCalledWith( + "Input 'verify-signature' is enabled, but gpg is not available." + ); + }); + + it('fails when signature verification is explicitly enabled without gpg', async () => { + (gpg.isGpgAvailable as jest.Mock).mockResolvedValue(false); + const distribution = new TemurinDistribution( + { + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false, + verifySignature: true + }, + TemurinImplementation.Hotspot + ); + + await expect( + distribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz', + signatureUrl: 'https://example.com/jdk.tar.gz.sig' + }) + ).rejects.toThrow( + "Input 'verify-signature' is enabled, but gpg is not available." + ); + + expect(spyVerifySignature).not.toHaveBeenCalled(); + }); + + it('warns when implicit signature verification fails', async () => { + spyVerifySignature.mockRejectedValue(new Error('bad signature')); + const distribution = new TemurinDistribution( + { + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + + await expect( + distribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz', + signatureUrl: 'https://example.com/jdk.tar.gz.sig' + }) + ).resolves.toEqual({version: '17.0.14+7', path: '/tmp/toolcache'}); + + expect(core.warning).toHaveBeenCalledWith( + expect.stringContaining( + 'https://github.com/actions/setup-java#download-integrity-and-signatures' + ) + ); + }); + + it('fails when explicitly requested signature verification fails', async () => { + spyVerifySignature.mockRejectedValue(new Error('bad signature')); + const distribution = new TemurinDistribution( + { + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false, + verifySignature: true + }, + TemurinImplementation.Hotspot + ); + + await expect( + distribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz', + signatureUrl: 'https://example.com/jdk.tar.gz.sig' + }) + ).rejects.toThrow( + /Failed to verify signature for Temurin version 17\.0\.14\+7.*bad signature.*https:\/\/github\.com\/actions\/setup-java#download-integrity-and-signatures/ + ); + }); + + it('warns when a signature is missing and verification is implicit', async () => { + const distribution = new TemurinDistribution( + { + version: '17', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + + await expect( + distribution['downloadTool']({ + version: '17.0.14+7', + url: 'https://example.com/jdk.tar.gz' + }) + ).resolves.toEqual({version: '17.0.14+7', path: '/tmp/toolcache'}); + + expect(core.warning).toHaveBeenCalledWith( + "Input 'verify-signature' is enabled, but no signature URL was found for Temurin version 17.0.14+7." + ); + expect(spyVerifySignature).not.toHaveBeenCalled(); + }); + it('downloads and adds matching JMODs to the JDK', async () => { spyDownloadTool .mockResolvedValueOnce('/tmp/jdk.tar.gz') diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts index db69e3de8..2e411bcc8 100644 --- a/__tests__/gpg.test.ts +++ b/__tests__/gpg.test.ts @@ -245,7 +245,7 @@ describe('gpg tests', () => { expect.any(String), '--batch', '--import', - expect.stringContaining('public-key.asc') + expect.stringContaining('public-key-0.asc') ], expect.objectContaining({silent: true}) ); @@ -263,5 +263,32 @@ describe('gpg tests', () => { expect.objectContaining({silent: true}) ); }); + + it('imports multiple bundled keys before verifying the package', async () => { + (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', + ['public-key-a', 'public-key-b'] + ); + + expect(exec.exec).toHaveBeenNthCalledWith( + 1, + 'gpg', + [ + '--homedir', + expect.any(String), + '--batch', + '--import', + expect.stringContaining('public-key-0.asc'), + expect.stringContaining('public-key-1.asc') + ], + expect.objectContaining({silent: true}) + ); + expect(exec.exec).toHaveBeenCalledTimes(2); + }); }); }); diff --git a/__tests__/jdk-cache.test.ts b/__tests__/jdk-cache.test.ts index 63788a78d..c5c3c4fb9 100644 --- a/__tests__/jdk-cache.test.ts +++ b/__tests__/jdk-cache.test.ts @@ -43,7 +43,7 @@ const jdk = { architecture: 'x64', version: '21.0.8+9', source: 'sha256:abc123', - verification: 'unverified', + verification: 'disabled', path: '/toolcache/Java_temurin_jdk/21.0.8-9' }; @@ -117,32 +117,59 @@ describe('JDK cache', () => { ); }); - it('separates unverified, bundled-key, and custom-key caches', () => { - const unverified = getJdkVerificationIdentity(false); - const bundled = getJdkVerificationIdentity(true); + it('separates verification policies and keys', () => { + const disabled = getJdkVerificationIdentity(false, false); + const checkAndWarnBundled = getJdkVerificationIdentity(true, false); + const enforcedBundled = getJdkVerificationIdentity(true, true); const customA = getJdkVerificationIdentity( + true, true, '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nkey-a\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n' ); const customANormalized = getJdkVerificationIdentity( + true, true, '-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey-a\n-----END PGP PUBLIC KEY BLOCK-----' ); - const customB = getJdkVerificationIdentity(true, 'different-key'); - - expect(new Set([unverified, bundled, customA, customB])).toHaveProperty( - 'size', - 4 + const customB = getJdkVerificationIdentity(true, true, 'different-key'); + const checkAndWarnCustomA = getJdkVerificationIdentity( + true, + false, + '-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey-a\n-----END PGP PUBLIC KEY BLOCK-----' ); + const customList = getJdkVerificationIdentity(true, true, [ + 'key-a', + 'key-b' + ]); + const customListWithDifferentBoundary = getJdkVerificationIdentity( + true, + true, + ['key-ak', 'ey-b'] + ); + + expect( + new Set([ + disabled, + checkAndWarnBundled, + enforcedBundled, + customA, + customB + ]) + ).toHaveProperty('size', 5); + expect(disabled).toBe('disabled'); + expect(checkAndWarnBundled).toBe('check-and-warn:bundled'); + expect(enforcedBundled).toBe('enforced:bundled'); + expect(checkAndWarnCustomA).not.toBe(customA); expect(customA).toBe(customANormalized); expect(customA).not.toContain('key-a'); + expect(customList).not.toBe(customListWithDifferentBoundary); expect( new Set( - [unverified, bundled, customA, customB].map(verification => - buildJdkCacheKey({...jdk, verification}) + [disabled, checkAndWarnBundled, enforcedBundled, customA, customB].map( + verification => buildJdkCacheKey({...jdk, verification}) ) ) - ).toHaveProperty('size', 4); + ).toHaveProperty('size', 5); }); it('restores and records an exact JDK cache hit', async () => { @@ -210,12 +237,12 @@ describe('JDK cache', () => { it('saves only the key matching the installation that occupies the path', async () => { const jdkPath = createInstallation(); - const verified = {...jdk, path: jdkPath, verification: 'verified:bundled'}; - const unverified = {...jdk, path: jdkPath}; + const enforced = {...jdk, path: jdkPath, verification: 'enforced:bundled'}; + const disabled = {...jdk, path: jdkPath}; - registerJdk(verified); + registerJdk(enforced); writeInstallation(jdkPath, 'force-downloaded-without-verification'); - registerJdk(unverified); + registerJdk(disabled); (core.getState as jest.Mock).mockReturnValue(lastState()); (cache.saveCache as jest.Mock).mockResolvedValue(1); @@ -223,11 +250,11 @@ describe('JDK cache', () => { expect(cache.saveCache).not.toHaveBeenCalledWith( [jdkPath], - buildJdkCacheKey(verified) + buildJdkCacheKey(enforced) ); expect(cache.saveCache).toHaveBeenCalledWith( [jdkPath], - buildJdkCacheKey(unverified) + buildJdkCacheKey(disabled) ); }); diff --git a/action.yml b/action.yml index 72506e5b1..1f244a223 100644 --- a/action.yml +++ b/action.yml @@ -39,10 +39,10 @@ inputs: required: false default: true verify-signature: - description: 'Verify downloaded Java package signatures when supported by the selected distribution' + description: 'Check downloaded Java package signatures when supported by the selected distribution. When omitted, failures produce warnings. Explicitly setting this to true enforces verification and makes failures fatal, including failures caused by an unexpected vendor signing-key rotation.' required: false verify-signature-public-key: - description: 'ASCII-armored GPG public key used to verify the downloaded package signature. Overrides the default bundled key for the selected distribution.' + description: 'One or more ASCII-armored GPG public keys used to verify downloaded package signatures. Concatenate multiple armored key blocks. Custom keys replace the bundled keys for the selected distribution.' required: false server-id: description: 'ID of the distributionManagement repository in the pom.xml diff --git a/dist/cleanup/314.index.js b/dist/cleanup/314.index.js index 1d80a1366..2d82c62b6 100644 --- a/dist/cleanup/314.index.js +++ b/dist/cleanup/314.index.js @@ -122,16 +122,25 @@ function getInstallationIdentity(jdkPath, architecture) { return undefined; } } -function getJdkVerificationIdentity(verifySignature, publicKey) { +function getJdkVerificationIdentity(verifySignature, enforceSignatureVerification, publicKey) { if (!verifySignature) { - return 'unverified'; + return 'disabled'; } + const verificationPolicy = enforceSignatureVerification + ? 'enforced' + : 'check-and-warn'; if (!publicKey) { - return 'verified:bundled'; - } - const normalizedKey = publicKey.replace(/\r\n?/g, '\n').trim(); - const fingerprint = createHash('sha256').update(normalizedKey).digest('hex'); - return `verified:custom:sha256:${fingerprint}`; + return `${verificationPolicy}:bundled`; + } + const publicKeys = Array.isArray(publicKey) ? publicKey : [publicKey]; + const normalizedKeys = publicKeys.map(key => key.replace(/\r\n?/g, '\n').trim()); + const fingerprintSource = Array.isArray(publicKey) + ? normalizedKeys.map(key => `${Buffer.byteLength(key)}:${key}`).join('') + : normalizedKeys[0]; + const fingerprint = createHash('sha256') + .update(fingerprintSource) + .digest('hex'); + return `${verificationPolicy}:custom:sha256:${fingerprint}`; } async function saveJdkCaches() { const state = lib_core/* getState */.Gu(STATE_JDK_CACHES); diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 1fd64fd6a..2ae9fe8df 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -30774,7 +30774,7 @@ module.exports = { /* harmony export */ gk: () => (/* binding */ INPUT_CACHE), /* 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_MVN_SERVER_CREDENTIALS, INPUT_MVN_REPOSITORIES, INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL, INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, 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, MAVEN_REPOSITORIES_PROFILE_ID, MAVEN_CENTRAL_REPOSITORY_ID, MAVEN_CENTRAL_REPOSITORY_URL, 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, SIGNATURE_VERIFICATION_DOCUMENTATION_URL, SIGNATURE_VERIFICATION_FAILURE_HELP, INPUT_MVN_SERVER_CREDENTIALS, INPUT_MVN_REPOSITORIES, INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL, INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, 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, MAVEN_REPOSITORIES_PROFILE_ID, MAVEN_CENTRAL_REPOSITORY_ID, MAVEN_CENTRAL_REPOSITORY_URL, 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'; @@ -30789,6 +30789,8 @@ const INPUT_SET_DEFAULT = 'set-default'; const INPUT_PROBLEM_MATCHER = 'problem-matcher'; const INPUT_VERIFY_SIGNATURE = 'verify-signature'; const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; +const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures'; +const SIGNATURE_VERIFICATION_FAILURE_HELP = (/* unused pure expression or super */ null && (`If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`)); const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; @@ -35767,6 +35769,9 @@ var src_util = __nccwpck_require__(4527); const GPG_HOME_PREFIX = 'setup-java-gpg-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; +async function isGpgAvailable() { + return Boolean(await io.which('gpg', false)); +} // 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 @@ -35850,15 +35855,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); } try { - const publicKeyFile = path.join(gpgHome, 'public-key.asc'); - fs.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); + const publicKeys = Array.isArray(publicKeyContent) + ? publicKeyContent + : [publicKeyContent]; + const publicKeyFiles = publicKeys.map((publicKey, index) => { + const publicKeyFile = path.join(gpgHome, `public-key-${index}.asc`); + fs.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' }); + return toGpgPath(publicKeyFile); + }); const options = { silent: true }; await exec.exec('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', - toGpgPath(publicKeyFile) + ...publicKeyFiles ], options); await exec.exec('gpg', [ '--homedir', diff --git a/dist/setup/19.index.js b/dist/setup/19.index.js index 3a24d5673..d9aaa6f1f 100644 --- a/dist/setup/19.index.js +++ b/dist/setup/19.index.js @@ -65,7 +65,7 @@ class LocalDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_3__/ architecture: this.architecture, version: this.version, source, - verification: getJdkVerificationIdentity(false), + verification: getJdkVerificationIdentity(false, false), path: this.getJdkCachePath(this.version) }; } diff --git a/dist/setup/220.index.js b/dist/setup/220.index.js index 3dadd6a0f..b1af6f1af 100644 --- a/dist/setup/220.index.js +++ b/dist/setup/220.index.js @@ -42,6 +42,8 @@ Fa133tP85xzJEq1XeXm8WeLFo2wV =rHCS -----END PGP PUBLIC KEY BLOCK-----`; +// EXTERNAL MODULE: ./src/constants.ts +var constants = __webpack_require__(7242); // EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules var core = __webpack_require__(3838); // EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules @@ -62,6 +64,7 @@ var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_); + class MicrosoftDistributions extends base_installer/* JavaBase */.O { constructor(installerOptions) { super('Microsoft', installerOptions); @@ -70,15 +73,23 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O { core/* info */.pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); let javaArchivePath = await this.downloadAndVerify(javaRelease); if (this.verifySignature) { - if (!javaRelease.signatureUrl) { - throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`); - } - core/* info */.pq(`Verifying Java package signature...`); try { - await gpg/* verifyPackageSignature */.Yi(javaArchivePath, javaRelease.signatureUrl, this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY); + if (!javaRelease.signatureUrl) { + throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`); + } + core/* info */.pq(`Verifying Java package signature...`); + try { + await gpg/* verifyPackageSignature */.Yi(javaArchivePath, javaRelease.signatureUrl, this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY); + } + catch (error) { + throw new Error(`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${error.message} ${constants/* SIGNATURE_VERIFICATION_FAILURE_HELP */.kQ}`, { cause: error }); + } } catch (error) { - throw new Error(`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${error.message}`, { cause: error }); + if (this.verifySignatureExplicitlyRequested) { + throw error; + } + core/* warning */.$e(error instanceof Error ? error.message : `Unknown error: ${error}`); } } core/* info */.pq(`Extracting Java archive...`); @@ -167,7 +178,8 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O { /* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), /* harmony export */ mS: () => (/* binding */ removeGpgHome), -/* harmony export */ nY: () => (/* binding */ toGpgPath) +/* harmony export */ nY: () => (/* binding */ toGpgPath), +/* harmony export */ o6: () => (/* binding */ isGpgAvailable) /* harmony export */ }); /* unused harmony export GPG_HOME_PREFIX */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); @@ -189,6 +201,9 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O { const GPG_HOME_PREFIX = 'setup-java-gpg-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; +async function isGpgAvailable() { + return Boolean(await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .which */ .K7('gpg', false)); +} // 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 @@ -272,15 +287,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); } try { - const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); + const publicKeys = Array.isArray(publicKeyContent) + ? publicKeyContent + : [publicKeyContent]; + const publicKeyFiles = publicKeys.map((publicKey, index) => { + const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `public-key-${index}.asc`); + fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' }); + return toGpgPath(publicKeyFile); + }); const options = { silent: true }; await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', - toGpgPath(publicKeyFile) + ...publicKeyFiles ], options); await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', diff --git a/dist/setup/242.index.js b/dist/setup/242.index.js index e333cf6ca..12e3d457d 100644 --- a/dist/setup/242.index.js +++ b/dist/setup/242.index.js @@ -226,6 +226,7 @@ class JavaBase { floatingVersionVerified = false; setDefault; verifySignature; + verifySignatureExplicitlyRequested; verifySignaturePublicKey; constructor(distribution, installerOptions) { this.distribution = distribution; @@ -246,6 +247,8 @@ class JavaBase { : true; this.verifySignature = installerOptions.verifySignature ?? this.supportsSignatureVerification(); + this.verifySignatureExplicitlyRequested = + installerOptions.verifySignature === true; this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey; } async downloadAndVerify(javaRelease) { @@ -481,7 +484,7 @@ class JavaBase { architecture: this.architecture, version: javaRelease.version, source: this.getJdkReleaseIdentity(javaRelease), - verification: getJdkVerificationIdentity(this.verifySignature, this.verifySignaturePublicKey), + verification: getJdkVerificationIdentity(this.verifySignature, this.verifySignatureExplicitlyRequested, this.verifySignaturePublicKey), path: this.getJdkCachePath(javaRelease.version) }; } diff --git a/dist/setup/463.index.js b/dist/setup/463.index.js index 7d792fd43..c07adf58b 100644 --- a/dist/setup/463.index.js +++ b/dist/setup/463.index.js @@ -159,15 +159,32 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { async downloadPackage(release) { const archivePath = await this.downloadAndVerify(release); if (this.verifySignature) { - if (!release.signatureUrl) { - throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`); - } - core/* info */.pq(`Verifying Java package signature...`); try { - await gpg/* verifyPackageSignature */.Yi(archivePath, release.signatureUrl, this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY); + if (!(await gpg/* isGpgAvailable */.o6())) { + throw new Error("Input 'verify-signature' is enabled, but gpg is not available."); + } + if (!release.signatureUrl) { + throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`); + } + core/* info */.pq(`Verifying Java package signature...`); + try { + await gpg/* verifyPackageSignature */.Yi(archivePath, release.signatureUrl, this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY); + } + catch (error) { + const verificationError = new Error(`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${error.message} ${constants/* SIGNATURE_VERIFICATION_FAILURE_HELP */.kQ}`, { cause: error }); + if (this.verifySignatureExplicitlyRequested) { + throw verificationError; + } + else { + core/* warning */.$e(verificationError.message); + } + } } catch (error) { - throw new Error(`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${error.message}`, { cause: error }); + if (this.verifySignatureExplicitlyRequested) { + throw error; + } + core/* warning */.$e(error instanceof Error ? error.message : `Unknown error: ${error}`); } } return archivePath; @@ -273,7 +290,8 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { /* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), /* harmony export */ mS: () => (/* binding */ removeGpgHome), -/* harmony export */ nY: () => (/* binding */ toGpgPath) +/* harmony export */ nY: () => (/* binding */ toGpgPath), +/* harmony export */ o6: () => (/* binding */ isGpgAvailable) /* harmony export */ }); /* unused harmony export GPG_HOME_PREFIX */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); @@ -295,6 +313,9 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { const GPG_HOME_PREFIX = 'setup-java-gpg-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; +async function isGpgAvailable() { + return Boolean(await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .which */ .K7('gpg', false)); +} // 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 @@ -378,15 +399,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); } try { - const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); + const publicKeys = Array.isArray(publicKeyContent) + ? publicKeyContent + : [publicKeyContent]; + const publicKeyFiles = publicKeys.map((publicKey, index) => { + const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `public-key-${index}.asc`); + fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' }); + return toGpgPath(publicKeyFile); + }); const options = { silent: true }; await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', - toGpgPath(publicKeyFile) + ...publicKeyFiles ], options); await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', diff --git a/dist/setup/779.index.js b/dist/setup/779.index.js index ff832e637..85048a8da 100644 --- a/dist/setup/779.index.js +++ b/dist/setup/779.index.js @@ -127,16 +127,25 @@ function getInstallationIdentity(jdkPath, architecture) { return undefined; } } -function getJdkVerificationIdentity(verifySignature, publicKey) { +function getJdkVerificationIdentity(verifySignature, enforceSignatureVerification, publicKey) { if (!verifySignature) { - return 'unverified'; + return 'disabled'; } + const verificationPolicy = enforceSignatureVerification + ? 'enforced' + : 'check-and-warn'; if (!publicKey) { - return 'verified:bundled'; - } - const normalizedKey = publicKey.replace(/\r\n?/g, '\n').trim(); - const fingerprint = (0,crypto__WEBPACK_IMPORTED_MODULE_0__.createHash)('sha256').update(normalizedKey).digest('hex'); - return `verified:custom:sha256:${fingerprint}`; + return `${verificationPolicy}:bundled`; + } + const publicKeys = Array.isArray(publicKey) ? publicKey : [publicKey]; + const normalizedKeys = publicKeys.map(key => key.replace(/\r\n?/g, '\n').trim()); + const fingerprintSource = Array.isArray(publicKey) + ? normalizedKeys.map(key => `${Buffer.byteLength(key)}:${key}`).join('') + : normalizedKeys[0]; + const fingerprint = (0,crypto__WEBPACK_IMPORTED_MODULE_0__.createHash)('sha256') + .update(fingerprintSource) + .digest('hex'); + return `${verificationPolicy}:custom:sha256:${fingerprint}`; } async function saveJdkCaches() { const state = _actions_core__WEBPACK_IMPORTED_MODULE_4__/* .getState */ .Gu(STATE_JDK_CACHES); diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 7e1fa785d..eb322254b 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -265,7 +265,8 @@ async function write(directory, settings, overwriteSettings) { /* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), /* harmony export */ mS: () => (/* binding */ removeGpgHome), -/* harmony export */ nY: () => (/* binding */ toGpgPath) +/* harmony export */ nY: () => (/* binding */ toGpgPath), +/* harmony export */ o6: () => (/* binding */ isGpgAvailable) /* harmony export */ }); /* unused harmony export GPG_HOME_PREFIX */ /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); @@ -287,6 +288,9 @@ async function write(directory, settings, overwriteSettings) { const GPG_HOME_PREFIX = 'setup-java-gpg-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; +async function isGpgAvailable() { + return Boolean(await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .which */ .K7('gpg', false)); +} // 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 @@ -370,15 +374,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); } try { - const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); - fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); + const publicKeys = Array.isArray(publicKeyContent) + ? publicKeyContent + : [publicKeyContent]; + const publicKeyFiles = publicKeys.map((publicKey, index) => { + const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `public-key-${index}.asc`); + fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' }); + return toGpgPath(publicKeyFile); + }); const options = { silent: true }; await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', toGpgPath(gpgHome), '--batch', '--import', - toGpgPath(publicKeyFile) + ...publicKeyFiles ], options); await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ '--homedir', diff --git a/dist/setup/index.js b/dist/setup/index.js index d4cbdcc52..d585a3730 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -30801,6 +30801,7 @@ module.exports = { /* harmony export */ jv: () => (/* binding */ MAVEN_CENTRAL_REPOSITORY_URL), /* harmony export */ kM: () => (/* binding */ INPUT_JDK_FILE), /* harmony export */ kN: () => (/* binding */ MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG), +/* harmony export */ kQ: () => (/* binding */ SIGNATURE_VERIFICATION_FAILURE_HELP), /* harmony export */ ko: () => (/* binding */ MAVEN_GPG_PASSPHRASE_DEFAULT_ENV), /* harmony export */ m7: () => (/* binding */ INPUT_MVN_TOOLCHAIN_VENDOR), /* harmony export */ nr: () => (/* binding */ INPUT_MVN_TOOLCHAIN_ID), @@ -30821,7 +30822,7 @@ module.exports = { /* harmony export */ xg: () => (/* binding */ MAVEN_CENTRAL_REPOSITORY_ID), /* harmony export */ xp: () => (/* binding */ INPUT_DEFAULT_SERVER_PASSWORD) /* harmony export */ }); -/* unused harmony exports INPUT_CACHE_READ_ONLY, INPUT_JOB_STATUS */ +/* unused harmony exports SIGNATURE_VERIFICATION_DOCUMENTATION_URL, INPUT_CACHE_READ_ONLY, INPUT_JOB_STATUS */ const MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; const INPUT_JAVA_VERSION = 'java-version'; const INPUT_JAVA_VERSION_FILE = 'java-version-file'; @@ -30836,6 +30837,8 @@ const INPUT_SET_DEFAULT = 'set-default'; const INPUT_PROBLEM_MATCHER = 'problem-matcher'; const INPUT_VERIFY_SIGNATURE = 'verify-signature'; const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; +const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures'; +const SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`; const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 04289f7a8..8561ab6f4 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -539,18 +539,27 @@ tool-cache installation short-circuits setup, so a changed `jdk-file` is not re-extracted for a version that is already installed. Use `force-download: true` when the archive contents change but the version does not. -The verification identity separates unverified downloads from packages verified -with the distribution's bundled signing key and from packages verified with each -custom key. Custom public keys are represented by a SHA-256 fingerprint of -normalized key material; the key itself is not placed in the cache key, the logs, -or action state. A verified exact-key hit reuses content that was -signature-verified when it was downloaded by the run that saved the entry, -instead of downloading and verifying it again. +The verification identity separates requests that disable signature verification, +check and warn without enforcement, or explicitly enforce verification. Disabled +and check-and-warn requests have the same non-enforcement guarantee, but they are +kept separate so an entry downloaded with verification disabled cannot prevent a +later check-and-warn request from attempting verification. The identity also +separates the distribution's bundled signing keys from custom keys. Custom +public-key sets are represented by a SHA-256 fingerprint of normalized, +boundary-delimited key material; the keys themselves are not placed in the cache +key, the logs, or action state. Enforced requests only restore entries created by +an enforced request whose signature verification succeeded. Check-and-warn entries +may have been saved after verification succeeded or after a verification failure +was reported as a warning. + +For signature-verification defaults, enforced failure behavior, and recovery from +a legitimate vendor signing-key rotation, see +[Download integrity and signatures](../README.md#download-integrity-and-signatures). > [!IMPORTANT] -> The JDK cache **key** is what isolates verification modes and release -> identity: a JDK cache entry created by an unverified download can never be -> restored for a request that sets `verify-signature: true`, and vice versa. +> The JDK cache **key** isolates disabled, check-and-warn, and enforced verification +> modes as well as release identity. A check-and-warn entry can never be restored +> for a request that sets `verify-signature: true`, and vice versa. > `cache-jdk` does not change how the runner tool cache is used. setup-java > first looks for an installation in the runner tool cache — a preinstalled > JDK, or one installed by an earlier step of the same job — and uses it as-is. Such an installation is not downloaded again, and its checksum diff --git a/src/constants.ts b/src/constants.ts index a11ab3a5a..9404e188d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,6 +12,9 @@ export const INPUT_SET_DEFAULT = 'set-default'; export const INPUT_PROBLEM_MATCHER = 'problem-matcher'; export const INPUT_VERIFY_SIGNATURE = 'verify-signature'; export const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; +export const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = + 'https://github.com/actions/setup-java#download-integrity-and-signatures'; +export const SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`; export const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; export const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = diff --git a/src/distributions/base-installer.ts b/src/distributions/base-installer.ts index fa515abec..f2d0c1414 100644 --- a/src/distributions/base-installer.ts +++ b/src/distributions/base-installer.ts @@ -9,12 +9,13 @@ import { getToolcachePath, isVersionSatisfies } from '../util.js'; -import { +import type { ChecksumAlgorithm, ChecksumMetadata, JavaDownloadRelease, JavaInstallerOptions, - JavaInstallerResults + JavaInstallerResults, + SignatureVerificationKey } from './base-models.js'; import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants.js'; import {RetryingHttpClient} from '../retrying-http-client.js'; @@ -44,7 +45,8 @@ export abstract class JavaBase { private floatingVersionVerified = false; protected setDefault: boolean; protected verifySignature: boolean; - protected verifySignaturePublicKey: string | undefined; + protected verifySignatureExplicitlyRequested: boolean; + protected verifySignaturePublicKey: SignatureVerificationKey | undefined; constructor( protected distribution: string, @@ -70,6 +72,8 @@ export abstract class JavaBase { : true; this.verifySignature = installerOptions.verifySignature ?? this.supportsSignatureVerification(); + this.verifySignatureExplicitlyRequested = + installerOptions.verifySignature === true; this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey; } @@ -369,6 +373,7 @@ export abstract class JavaBase { source: this.getJdkReleaseIdentity(javaRelease), verification: getJdkVerificationIdentity( this.verifySignature, + this.verifySignatureExplicitlyRequested, this.verifySignaturePublicKey ), path: this.getJdkCachePath(javaRelease.version) diff --git a/src/distributions/base-models.ts b/src/distributions/base-models.ts index c3559f100..b12f78dac 100644 --- a/src/distributions/base-models.ts +++ b/src/distributions/base-models.ts @@ -1,3 +1,5 @@ +export type SignatureVerificationKey = string | readonly string[]; + export interface JavaInstallerOptions { version: string; architecture: string; @@ -7,7 +9,7 @@ export interface JavaInstallerOptions { cacheJdk?: boolean; setDefault?: boolean; verifySignature?: boolean; - verifySignaturePublicKey?: string; + verifySignaturePublicKey?: SignatureVerificationKey; } export interface JavaInstallerResults { diff --git a/src/distributions/local/installer.ts b/src/distributions/local/installer.ts index 9b197aee5..c42ffb494 100644 --- a/src/distributions/local/installer.ts +++ b/src/distributions/local/installer.ts @@ -65,7 +65,7 @@ export class LocalDistribution extends JavaBase { architecture: this.architecture, version: this.version, source, - verification: getJdkVerificationIdentity(false), + verification: getJdkVerificationIdentity(false, false), path: this.getJdkCachePath(this.version) }; } diff --git a/src/distributions/microsoft/installer.ts b/src/distributions/microsoft/installer.ts index e33c7a390..a3db97a98 100644 --- a/src/distributions/microsoft/installer.ts +++ b/src/distributions/microsoft/installer.ts @@ -12,6 +12,7 @@ import { } from '../../util.js'; import * as gpg from '../../gpg.js'; import {MICROSOFT_PUBLIC_KEY} from './microsoft-key.js'; +import {SIGNATURE_VERIFICATION_FAILURE_HELP} from '../../constants.js'; import * as core from '@actions/core'; import * as tc from '@actions/tool-cache'; import fs from 'fs'; @@ -34,22 +35,31 @@ export class MicrosoftDistributions extends JavaBase { let javaArchivePath = await this.downloadAndVerify(javaRelease); if (this.verifySignature) { - if (!javaRelease.signatureUrl) { - throw new Error( - `Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.` - ); - } - core.info(`Verifying Java package signature...`); try { - await gpg.verifyPackageSignature( - javaArchivePath, - javaRelease.signatureUrl, - this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY - ); + if (!javaRelease.signatureUrl) { + throw new Error( + `Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.` + ); + } + core.info(`Verifying Java package signature...`); + try { + await gpg.verifyPackageSignature( + javaArchivePath, + javaRelease.signatureUrl, + this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY + ); + } catch (error) { + throw new Error( + `Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${(error as Error).message} ${SIGNATURE_VERIFICATION_FAILURE_HELP}`, + {cause: error} + ); + } } catch (error) { - throw new Error( - `Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${(error as Error).message}`, - {cause: error} + if (this.verifySignatureExplicitlyRequested) { + throw error; + } + core.warning( + error instanceof Error ? error.message : `Unknown error: ${error}` ); } } diff --git a/src/distributions/temurin/installer.ts b/src/distributions/temurin/installer.ts index ab7238e31..e01535e91 100644 --- a/src/distributions/temurin/installer.ts +++ b/src/distributions/temurin/installer.ts @@ -8,7 +8,10 @@ import * as gpg from '../../gpg.js'; import {ADOPTIUM_PUBLIC_KEY} from './adoptium-key.js'; import {JavaBase} from '../base-installer.js'; import {ITemurinAvailableVersions} from './models.js'; -import {MACOS_JAVA_CONTENT_POSTFIX} from '../../constants.js'; +import { + MACOS_JAVA_CONTENT_POSTFIX, + SIGNATURE_VERIFICATION_FAILURE_HELP +} from '../../constants.js'; import { JavaDownloadRelease, JavaInstallerOptions, @@ -141,24 +144,41 @@ export class TemurinDistribution extends JavaBase { const archivePath = await this.downloadAndVerify(release); if (this.verifySignature) { - if (!release.signatureUrl) { - throw new Error( - `Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.` - ); - } - core.info(`Verifying Java package signature...`); try { - await gpg.verifyPackageSignature( - archivePath, - release.signatureUrl, - this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY - ); + if (!(await gpg.isGpgAvailable())) { + throw new Error( + "Input 'verify-signature' is enabled, but gpg is not available." + ); + } + if (!release.signatureUrl) { + throw new Error( + `Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.` + ); + } + core.info(`Verifying Java package signature...`); + try { + await gpg.verifyPackageSignature( + archivePath, + release.signatureUrl, + this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY + ); + } catch (error) { + const verificationError = new Error( + `Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${(error as Error).message} ${SIGNATURE_VERIFICATION_FAILURE_HELP}`, + {cause: error} + ); + if (this.verifySignatureExplicitlyRequested) { + throw verificationError; + } else { + core.warning(verificationError.message); + } + } } catch (error) { - throw new Error( - `Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${ - (error as Error).message - }`, - {cause: error} + if (this.verifySignatureExplicitlyRequested) { + throw error; + } + core.warning( + error instanceof Error ? error.message : `Unknown error: ${error}` ); } } diff --git a/src/gpg.ts b/src/gpg.ts index 46e0a6ce5..0d4f76aae 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -5,11 +5,16 @@ 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'; +import type {ExecOptions} from '@actions/exec'; +import type {SignatureVerificationKey} from './distributions/base-models.js'; export const GPG_HOME_PREFIX = 'setup-java-gpg-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; +export async function isGpgAvailable(): Promise { + return Boolean(await io.which('gpg', false)); +} + // 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 @@ -97,7 +102,7 @@ export async function removeGpgHome(gpgHome: string): Promise { export async function verifyPackageSignature( archivePath: string, signatureUrl: string, - publicKeyContent: string + publicKeyContent: SignatureVerificationKey ) { const signaturePath = await tc.downloadTool(signatureUrl); let gpgHome: string; @@ -117,8 +122,14 @@ export async function verifyPackageSignature( ); } try { - const publicKeyFile = path.join(gpgHome, 'public-key.asc'); - fs.writeFileSync(publicKeyFile, publicKeyContent, {encoding: 'utf-8'}); + const publicKeys = Array.isArray(publicKeyContent) + ? publicKeyContent + : [publicKeyContent]; + const publicKeyFiles = publicKeys.map((publicKey, index) => { + const publicKeyFile = path.join(gpgHome, `public-key-${index}.asc`); + fs.writeFileSync(publicKeyFile, publicKey, {encoding: 'utf-8'}); + return toGpgPath(publicKeyFile); + }); const options: ExecOptions = {silent: true}; await exec.exec( 'gpg', @@ -127,7 +138,7 @@ export async function verifyPackageSignature( toGpgPath(gpgHome), '--batch', '--import', - toGpgPath(publicKeyFile) + ...publicKeyFiles ], options ); diff --git a/src/jdk-cache.ts b/src/jdk-cache.ts index afee92060..ecaa90180 100644 --- a/src/jdk-cache.ts +++ b/src/jdk-cache.ts @@ -4,6 +4,7 @@ import path from 'path'; import * as cache from '@actions/cache'; import * as core from '@actions/core'; import {isCacheFeatureAvailable} from './cache-feature.js'; +import type {SignatureVerificationKey} from './distributions/base-models.js'; const STATE_JDK_CACHES = 'jdk-caches'; const JDK_CACHE_KEY_VERSION = 1; @@ -117,18 +118,30 @@ function getInstallationIdentity( export function getJdkVerificationIdentity( verifySignature: boolean, - publicKey?: string + enforceSignatureVerification: boolean, + publicKey?: SignatureVerificationKey ): string { if (!verifySignature) { - return 'unverified'; + return 'disabled'; } + const verificationPolicy = enforceSignatureVerification + ? 'enforced' + : 'check-and-warn'; if (!publicKey) { - return 'verified:bundled'; + return `${verificationPolicy}:bundled`; } - const normalizedKey = publicKey.replace(/\r\n?/g, '\n').trim(); - const fingerprint = createHash('sha256').update(normalizedKey).digest('hex'); - return `verified:custom:sha256:${fingerprint}`; + const publicKeys = Array.isArray(publicKey) ? publicKey : [publicKey]; + const normalizedKeys = publicKeys.map(key => + key.replace(/\r\n?/g, '\n').trim() + ); + const fingerprintSource = Array.isArray(publicKey) + ? normalizedKeys.map(key => `${Buffer.byteLength(key)}:${key}`).join('') + : normalizedKeys[0]; + const fingerprint = createHash('sha256') + .update(fingerprintSource) + .digest('hex'); + return `${verificationPolicy}:custom:sha256:${fingerprint}`; } export async function saveJdkCaches(): Promise { From 134912a529122c8803d42fbefc4adb2bd177f986 Mon Sep 17 00:00:00 2001 From: otaconix Date: Wed, 9 Sep 2026 08:20:21 +0200 Subject: [PATCH 7/8] Fix import-safe checks when scripts are run from a path with symlinks (#1265) * Fix import-safe checks when scripts are run from a path with symlinks In v6, setup-java was made "import-safe" to facilitate testing. This prevents setup-java & cleanup-java from doing anything when their sources get imported. This works fine in the general case, but actually invoking the script (`node setup-java/index.js`) when the path to the script contains symlinks led to the script incorrectly believing it was imported, and refuse to actually run. To fix this, we pass `process.argv[1]` through `fs.realpathSync`, which resolves symlinks in the path. Fixes #1264 * Preserve import safety when resolving symlink entrypoints Share entrypoint detection between setup and cleanup, handle non-file entrypoints safely, and normalize both paths for preserved symlinks. Add real-process regression coverage and rebuild action bundles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554 * Update js-yaml to fix merge-source denial of service Bump the transitive development dependency from 3.15.1 to 3.15.2 to address GHSA-2883-xcg3-v3hh without changing dependency ranges or CI checks. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554 --------- Co-authored-by: Bruno Borges Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554 --- __tests__/entrypoints.test.ts | 87 +++++++++++++++++++++ __tests__/is-main-module.test.ts | 52 ++++++++++++ __tests__/setup-java.module-loading.test.ts | 1 + __tests__/setup-java.test.ts | 1 + dist/cleanup/index.js | 27 ++++++- dist/setup/index.js | 27 ++++++- package-lock.json | 6 +- src/cleanup-java.ts | 4 +- src/is-main-module.ts | 26 ++++++ src/setup-java.ts | 3 +- 10 files changed, 226 insertions(+), 8 deletions(-) create mode 100644 __tests__/entrypoints.test.ts create mode 100644 __tests__/is-main-module.test.ts create mode 100644 src/is-main-module.ts diff --git a/__tests__/entrypoints.test.ts b/__tests__/entrypoints.test.ts new file mode 100644 index 000000000..1fc07dd08 --- /dev/null +++ b/__tests__/entrypoints.test.ts @@ -0,0 +1,87 @@ +import {afterAll, beforeAll, describe, expect, it} from '@jest/globals'; +import {spawnSync} from 'child_process'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import {fileURLToPath, pathToFileURL} from 'url'; + +const dist = fileURLToPath(new URL('../dist/', import.meta.url)); +let tempDir: string; +let linkedDist: string; + +beforeAll(() => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-java-entrypoints-')); + linkedDist = path.join(tempDir, 'linked # dist'); + fs.symlinkSync(dist, linkedDist, 'junction'); +}); + +afterAll(() => { + fs.rmSync(tempDir, {recursive: true, force: true}); +}); + +function execute(args: string[], input?: string) { + return spawnSync(process.execPath, args, { + encoding: 'utf8', + input, + timeout: 10000, + env: { + PATH: process.env.PATH, + SystemRoot: process.env.SystemRoot + } + }); +} + +describe.each([ + ['setup', 1, 'java-version or java-version-file input expected'], + ['cleanup', 0, ''] +] as const)('%s entrypoint', (name, exitCode, output) => { + it.each(['direct', 'symlink', 'preserved symlink'])( + 'executes through a %s path', + mode => { + const entry = path.join( + mode === 'direct' ? dist : linkedDist, + name, + 'index.js' + ); + const args = + mode === 'preserved symlink' + ? ['--preserve-symlinks-main', entry] + : [entry]; + const result = execute(args); + + expect(result.error).toBeUndefined(); + expect(result.status).toBe(exitCode); + expect(result.stderr).toBe(''); + expect(result.stdout).not.toContain('skipping the execution'); + if (output) { + expect(result.stdout).toContain(output); + } else { + expect(result.stdout).toBe(''); + } + } + ); + + it.each(['eval', 'stdin', 'file'])( + 'does not execute when imported from %s', + mode => { + const moduleUrl = pathToFileURL(path.join(dist, name, 'index.js')).href; + const source = `const {run} = await import(${JSON.stringify(moduleUrl)}); console.log(typeof run);`; + const importer = path.join(tempDir, `${name}-importer.mjs`); + fs.writeFileSync(importer, source); + const args = + mode === 'file' + ? [importer] + : mode === 'eval' + ? ['--input-type=module', '-e', source] + : ['--input-type=module', '-']; + const result = execute(args, mode === 'stdin' ? source : undefined); + + expect(result.error).toBeUndefined(); + expect(result.status).toBe(0); + expect(result.stderr).toBe(''); + expect(result.stdout).toContain('skipping the execution'); + expect(result.stdout).toContain('function'); + expect(result.stdout).not.toContain('::error::'); + } + ); +}); diff --git a/__tests__/is-main-module.test.ts b/__tests__/is-main-module.test.ts new file mode 100644 index 000000000..42a5a2572 --- /dev/null +++ b/__tests__/is-main-module.test.ts @@ -0,0 +1,52 @@ +import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals'; +import fs from 'fs'; +import {isMainModule} from '../src/is-main-module.js'; + +describe('main module detection', () => { + const originalArgv = process.argv; + + beforeEach(() => { + process.argv = [process.execPath, 'entrypoint.js']; + }); + + afterEach(() => { + process.argv = originalArgv; + jest.restoreAllMocks(); + }); + + it.each([undefined, '-'])( + 'skips filesystem access when argv[1] is %s', + entrypoint => { + process.argv = + entrypoint === undefined + ? [process.execPath] + : [process.execPath, entrypoint]; + const realpath = jest.spyOn(fs, 'realpathSync'); + + expect(isMainModule(import.meta.url)).toBe(false); + expect(realpath).not.toHaveBeenCalled(); + } + ); + + it.each(['ENOENT', 'ENOTDIR'])( + 'treats a non-file entrypoint returning %s as an import', + code => { + jest.spyOn(fs, 'realpathSync').mockImplementation(() => { + throw Object.assign(new Error('No file-based entrypoint'), {code}); + }); + + expect(isMainModule(import.meta.url)).toBe(false); + } + ); + + it('propagates unexpected filesystem errors', () => { + const error = Object.assign(new Error('Permission denied'), { + code: 'EACCES' + }); + jest.spyOn(fs, 'realpathSync').mockImplementation(() => { + throw error; + }); + + expect(() => isMainModule(import.meta.url)).toThrow(error); + }); +}); diff --git a/__tests__/setup-java.module-loading.test.ts b/__tests__/setup-java.module-loading.test.ts index 7705abfc4..e8c1e7d2e 100644 --- a/__tests__/setup-java.module-loading.test.ts +++ b/__tests__/setup-java.module-loading.test.ts @@ -27,6 +27,7 @@ jest.unstable_mockModule('@actions/core', () => ({ jest.unstable_mockModule('fs', () => ({ default: { + ...jest.requireActual('fs'), readFileSync: jest.fn() } })); diff --git a/__tests__/setup-java.test.ts b/__tests__/setup-java.test.ts index b22fae79a..c75ead639 100644 --- a/__tests__/setup-java.test.ts +++ b/__tests__/setup-java.test.ts @@ -27,6 +27,7 @@ jest.unstable_mockModule('@actions/core', () => ({ jest.unstable_mockModule('fs', () => ({ default: { + ...jest.requireActual('fs'), readFileSync: jest.fn() } })); diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 2ae9fe8df..934dff64c 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -35747,6 +35747,7 @@ __nccwpck_require__.d(__webpack_exports__, { var cleanup_java_core = __nccwpck_require__(3838); // EXTERNAL MODULE: external "fs" var external_fs_ = __nccwpck_require__(9896); +var external_fs_default = /*#__PURE__*/__nccwpck_require__.n(external_fs_); // EXTERNAL MODULE: external "path" var external_path_ = __nccwpck_require__(6928); // EXTERNAL MODULE: external "crypto" @@ -35890,6 +35891,30 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten var constants = __nccwpck_require__(7242); // EXTERNAL MODULE: external "url" var external_url_ = __nccwpck_require__(7016); +;// CONCATENATED MODULE: ./src/is-main-module.ts + + +function isMainModule(moduleUrl) { + const entrypoint = process.argv[1]; + if (!entrypoint || entrypoint === '-') { + return false; + } + let entrypointPath; + try { + entrypointPath = external_fs_default().realpathSync(entrypoint); + } + catch (error) { + if (error instanceof Error && + 'code' in error && + (error.code === 'ENOENT' || error.code === 'ENOTDIR')) { + return false; + } + throw error; + } + // Resolve both paths for runtimes using --preserve-symlinks-main. + return entrypointPath === external_fs_default().realpathSync((0,external_url_.fileURLToPath)(moduleUrl)); +} + ;// CONCATENATED MODULE: ./src/cleanup-java.ts @@ -35957,7 +35982,7 @@ async function run() { await cleanup_java_removeGpgHome(); await ignoreError(saveCaches()); } -if (process.argv[1] === (0,external_url_.fileURLToPath)(import.meta.url)) { +if (isMainModule(import.meta.url)) { run(); } else { diff --git a/dist/setup/index.js b/dist/setup/index.js index d585a3730..8e019a02a 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -36354,6 +36354,30 @@ function configureProblemMatcher(matcherPath) { // EXTERNAL MODULE: ./src/toolchain-ids.ts var toolchain_ids = __nccwpck_require__(7083); +;// CONCATENATED MODULE: ./src/is-main-module.ts + + +function isMainModule(moduleUrl) { + const entrypoint = process.argv[1]; + if (!entrypoint || entrypoint === '-') { + return false; + } + let entrypointPath; + try { + entrypointPath = external_fs_default().realpathSync(entrypoint); + } + catch (error) { + if (error instanceof Error && + 'code' in error && + (error.code === 'ENOENT' || error.code === 'ENOTDIR')) { + return false; + } + throw error; + } + // Resolve both paths for runtimes using --preserve-symlinks-main. + return entrypointPath === external_fs_default().realpathSync((0,external_url_.fileURLToPath)(moduleUrl)); +} + ;// CONCATENATED MODULE: ./src/setup-java.ts @@ -36364,6 +36388,7 @@ var toolchain_ids = __nccwpck_require__(7083); + async function run() { const versions = setup_java_core/* getMultilineInput */.q3(constants/* INPUT_JAVA_VERSION */.QM); let distributionName = setup_java_core/* getInput */.V4(constants/* INPUT_DISTRIBUTION */.g_); @@ -36480,7 +36505,7 @@ async function validateCacheInput(cache) { function settle(promise) { return promise.then(value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason })); } -if (process.argv[1] === (0,external_url_.fileURLToPath)(import.meta.url)) { +if (isMainModule(import.meta.url)) { run(); } else { diff --git a/package-lock.json b/package-lock.json index 9ee474f69..bcc003721 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4830,9 +4830,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/cleanup-java.ts b/src/cleanup-java.ts index cf333d00c..4f45e1839 100644 --- a/src/cleanup-java.ts +++ b/src/cleanup-java.ts @@ -6,7 +6,7 @@ import { isJdkCacheEnabled, isJobStatusSuccess } from './util.js'; -import {fileURLToPath} from 'url'; +import {isMainModule} from './is-main-module.js'; async function removeGpgHome() { const gpgHome = core.getState(constants.STATE_GPG_HOME); @@ -77,7 +77,7 @@ export async function run() { await ignoreError(saveCaches()); } -if (process.argv[1] === fileURLToPath(import.meta.url)) { +if (isMainModule(import.meta.url)) { run(); } else { // https://nodejs.org/api/modules.html#modules_accessing_the_main_module diff --git a/src/is-main-module.ts b/src/is-main-module.ts new file mode 100644 index 000000000..4fcc90f56 --- /dev/null +++ b/src/is-main-module.ts @@ -0,0 +1,26 @@ +import fs from 'fs'; +import {fileURLToPath} from 'url'; + +export function isMainModule(moduleUrl: string): boolean { + const entrypoint = process.argv[1]; + if (!entrypoint || entrypoint === '-') { + return false; + } + + let entrypointPath: string; + try { + entrypointPath = fs.realpathSync(entrypoint); + } catch (error) { + if ( + error instanceof Error && + 'code' in error && + (error.code === 'ENOENT' || error.code === 'ENOTDIR') + ) { + return false; + } + throw error; + } + + // Resolve both paths for runtimes using --preserve-symlinks-main. + return entrypointPath === fs.realpathSync(fileURLToPath(moduleUrl)); +} diff --git a/src/setup-java.ts b/src/setup-java.ts index 6e7c80a62..23de15472 100644 --- a/src/setup-java.ts +++ b/src/setup-java.ts @@ -12,6 +12,7 @@ import {getJavaDistribution} from './distributions/distribution-factory.js'; import {JavaInstallerOptions} from './distributions/base-models.js'; import {configureProblemMatcher} from './problem-matcher.js'; import {validateToolchainIds} from './toolchain-ids.js'; +import {isMainModule} from './is-main-module.js'; export async function run() { const versions = core.getMultilineInput(constants.INPUT_JAVA_VERSION); @@ -172,7 +173,7 @@ function settle(promise: Promise): Promise> { ); } -if (process.argv[1] === fileURLToPath(import.meta.url)) { +if (isMainModule(import.meta.url)) { run(); } else { // https://nodejs.org/api/modules.html#modules_accessing_the_main_module From de7274f081f381c8f8158605e0321c36c376e2e6 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 9 Sep 2026 08:08:57 -0400 Subject: [PATCH 8/8] Avoid macOS GPG socket overflow on long runner paths (#1266) * Initial plan * Fix signature verification GPG homes on long runner paths * Keep macOS GPG verification homes within socket limits Use /tmp for signature verification on macOS while preserving runner temp behavior elsewhere. Cover long and canonical OS temp paths and regenerate action bundles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 32d31c8d-ddbc-4e57-a5c3-f70588fef3f3 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 32d31c8d-ddbc-4e57-a5c3-f70588fef3f3 --- __tests__/gpg.test.ts | 80 +++++++++++++++++++++++++++++++++++++++++ dist/cleanup/index.js | 8 +++-- dist/setup/220.index.js | 8 +++-- dist/setup/463.index.js | 8 +++-- dist/setup/81.index.js | 8 +++-- src/gpg.ts | 11 ++++-- 6 files changed, 108 insertions(+), 15 deletions(-) diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts index 2e411bcc8..f9a0ca3cc 100644 --- a/__tests__/gpg.test.ts +++ b/__tests__/gpg.test.ts @@ -9,10 +9,18 @@ import { } from '@jest/globals'; import {fileURLToPath} from 'url'; import * as fs from 'fs'; +import * as os from 'os'; import * as path from 'path'; import * as io from '@actions/io'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const mockTmpDir = jest.fn(os.tmpdir); + +jest.unstable_mockModule('os', () => ({ + ...os, + default: {...os, tmpdir: mockTmpDir}, + tmpdir: mockTmpDir +})); jest.unstable_mockModule('@actions/exec', () => ({ exec: jest.fn() @@ -34,6 +42,7 @@ describe('gpg tests', () => { await io.rmRF(tempDir); await io.mkdirP(tempDir); jest.clearAllMocks(); + mockTmpDir.mockImplementation(os.tmpdir); (exec.exec as jest.Mock).mockResolvedValue(0); }); @@ -222,6 +231,77 @@ describe('gpg tests', () => { }); describe('verifyPackageSignature', () => { + describe.each(['long', 'canonical macOS'])('%s TMPDIR', tempDirKind => { + afterEach(() => { + process.env['RUNNER_TEMP'] = tempDir; + }); + + it.each(['success', 'import failure', 'verification failure'])( + 'uses a short macOS home or RUNNER_TEMP elsewhere and cleans up after %s', + async outcome => { + const longRunnerTemp = path.join( + tempDir, + 'long-runner-path-'.repeat(8) + ); + const signaturePath = path.join(tempDir, 'jdk.tar.gz.sig'); + const expectedParent = + process.platform === 'darwin' ? '/tmp' : longRunnerTemp; + let gpgHome = ''; + process.env['RUNNER_TEMP'] = longRunnerTemp; + mockTmpDir.mockReturnValue( + tempDirKind === 'long' + ? longRunnerTemp + : `/private/var/folders/ab/${'c'.repeat(31)}/T` + ); + fs.mkdirSync(longRunnerTemp, {recursive: true}); + fs.writeFileSync(signaturePath, 'signature'); + (tc.downloadTool as jest.Mock).mockResolvedValue(signaturePath); + (exec.exec as jest.Mock).mockImplementation( + async (_command: string, args: string[]) => { + gpgHome = path.join(expectedParent, path.posix.basename(args[1])); + expect(args[1]).toBe(gpg.toGpgPath(gpgHome)); + if (process.platform === 'darwin') { + expect( + Buffer.byteLength(path.join(gpgHome, 'S.gpg-agent.browser')) + ).toBeLessThan(104); + } + expect( + fs.readFileSync(path.join(gpgHome, 'public-key-0.asc'), 'utf8') + ).toBe('public key'); + if (process.platform !== 'win32') { + expect(fs.statSync(gpgHome).mode & 0o777).toBe(0o700); + } + if ( + (outcome === 'import failure' && args.includes('--import')) || + (outcome === 'verification failure' && + args.includes('--verify')) + ) { + throw new Error(outcome); + } + return 0; + } + ); + + const verification = gpg.verifyPackageSignature( + path.join(tempDir, 'jdk.tar.gz'), + 'https://example.com/jdk.tar.gz.sig', + 'public key' + ); + if (outcome === 'success') { + await verification; + } else { + await expect(verification).rejects.toThrow(outcome); + } + expect(exec.exec).toHaveBeenCalledTimes( + outcome === 'import failure' ? 1 : 2 + ); + expect(fs.existsSync(gpgHome)).toBe(false); + expect(fs.existsSync(signaturePath)).toBe(false); + expect(fs.readdirSync(longRunnerTemp)).toEqual([]); + } + ); + }); + it('imports bundled key and verifies package', async () => { const publicKeyContent = '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----'; diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 934dff64c..00ad7724f 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -35784,8 +35784,8 @@ function toGpgPath(p) { .replace(/\\/g, '/') .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } -function createGpgHome(prefix) { - const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix)); +function createGpgHome(prefix, tempDir = util.getTempDir()) { + const gpgHome = fs.mkdtempSync(path.join(tempDir, prefix)); if (process.platform !== 'win32') { fs.chmodSync(gpgHome, 0o700); } @@ -35844,7 +35844,9 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const signaturePath = await tc.downloadTool(signatureUrl); let gpgHome; try { - gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); + // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit. + const tempDir = process.platform === 'darwin' ? '/tmp' : util.getTempDir(); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir); } catch (error) { try { diff --git a/dist/setup/220.index.js b/dist/setup/220.index.js index b1af6f1af..f5cfebc79 100644 --- a/dist/setup/220.index.js +++ b/dist/setup/220.index.js @@ -215,8 +215,8 @@ 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)); +function createGpgHome(prefix, tempDir = _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()) { + const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(tempDir, prefix)); if (process.platform !== 'win32') { fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); } @@ -275,7 +275,9 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); let gpgHome; try { - gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); + // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit. + const tempDir = process.platform === 'darwin' ? '/tmp' : _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir); } catch (error) { try { diff --git a/dist/setup/463.index.js b/dist/setup/463.index.js index c07adf58b..02b5bfc93 100644 --- a/dist/setup/463.index.js +++ b/dist/setup/463.index.js @@ -327,8 +327,8 @@ 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)); +function createGpgHome(prefix, tempDir = _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()) { + const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(tempDir, prefix)); if (process.platform !== 'win32') { fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); } @@ -387,7 +387,9 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); let gpgHome; try { - gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); + // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit. + const tempDir = process.platform === 'darwin' ? '/tmp' : _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir); } catch (error) { try { diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index eb322254b..3ac82d8fc 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -302,8 +302,8 @@ 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)); +function createGpgHome(prefix, tempDir = _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()) { + const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(tempDir, prefix)); if (process.platform !== 'win32') { fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); } @@ -362,7 +362,9 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); let gpgHome; try { - gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); + // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit. + const tempDir = process.platform === 'darwin' ? '/tmp' : _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir); } catch (error) { try { diff --git a/src/gpg.ts b/src/gpg.ts index 0d4f76aae..b522d126c 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -26,8 +26,11 @@ export function toGpgPath(p: string): string { .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); } -function createGpgHome(prefix: string): string { - const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix)); +function createGpgHome( + prefix: string, + tempDir: string = util.getTempDir() +): string { + const gpgHome = fs.mkdtempSync(path.join(tempDir, prefix)); if (process.platform !== 'win32') { fs.chmodSync(gpgHome, 0o700); } @@ -107,7 +110,9 @@ export async function verifyPackageSignature( const signaturePath = await tc.downloadTool(signatureUrl); let gpgHome: string; try { - gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); + // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit. + const tempDir = process.platform === 'darwin' ? '/tmp' : util.getTempDir(); + gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir); } catch (error) { try { await io.rmRF(signaturePath);