convertToUnixPath() rewrites drive letters to MSYS form (R:\ -> /r/), which native GnuPG (Gpg4win) cannot resolve. Add investigation.md and a windows-latest reproducer workflow that installs native GnuPG and expects the action to fail at GPG signature verification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.6 KiB
Investigation: GPG check not working on Windows runner
- Reported: https://community.sonarsource.com/t/sonarqube-scan-action-gpg-check-not-working-on-windows-runner/186785
- Affected version:
SonarSource/sonarqube-scan-action@v8.2.1 - Environment: Windows runner with native GnuPG (Gpg4win) at
C:\Program Files (x86)\GnuPG\bin\gpg.exe - Verdict: ✅ Confirmed bug in
src/main/gpg-verification.js
Summary
On Windows, the action converts every path it hands to gpg (the --homedir, the
signature file, and the ZIP file) into an MSYS/Git-Bash-style path such as
/r/GHEC/Build_B1/_temp/gpg-957091c8. That format is only understood by the
Git-for-Windows build of GPG. When the gpg on PATH is the native GnuPG
(Gpg4win) — as in the reporter's environment — those paths are meaningless, so GPG
cannot create/read its home directory, keyring, sockets, or lock files, and signature
verification fails.
Root cause
convertToUnixPath() in src/main/gpg-verification.js:106-118:
export function convertToUnixPath(windowsPath) {
if (process.platform !== "win32") {
return windowsPath;
}
let unixPath = windowsPath.replaceAll('\\', "/");
unixPath = unixPath.replace(/^([A-Za-z]):/, (match, drive) => {
return `/${drive.toLowerCase()}`; // R:\... -> /r/...
});
return unixPath;
}
For the reporter's temp dir R:\GHEC\Build_B1\_temp\gpg-957091c8 this produces:
R:\GHEC\Build_B1\_temp\gpg-957091c8 → /r/GHEC/Build_B1/_temp/gpg-957091c8
which is exactly the path seen in the failure logs.
The function's own comment states the assumption:
GPG on Windows (from Git for Windows) expects Unix-style paths—src/main/gpg-verification.js:102
That assumption is wrong for the common case. There are two GPG builds on Windows:
| GPG build | Understands /r/GHEC/... (MSYS) |
Understands R:/GHEC/... (native) |
|---|---|---|
Git for Windows / MSYS2 gpg |
✅ | ✅ |
Native GnuPG / Gpg4win (C:\Program Files (x86)\GnuPG\bin\gpg.exe) |
❌ | ✅ |
The MSYS drive-letter form (/r/...) is understood by only one of the two builds,
whereas the plain Windows form with forward slashes (R:/...) is understood by both.
By rewriting R: → /r, the action picks the form that breaks native GnuPG.
How the converted path is used
All three call sites feed native GnuPG a path it cannot resolve:
src/main/gpg-verification.js:165—--homedirfor--recv-keys(key import)src/main/gpg-verification.js:247—--homedirfor--verifysrc/main/gpg-verification.js:250-251— the.ascsignature path and the ZIP path
Mapping to the reported errors
With --homedir /r/GHEC/Build_B1/_temp/gpg-957091c8, native GnuPG treats the argument
as a bogus location and cannot create/open its home dir. That directly explains every
symptom in the report:
| Reported symptom | Explanation |
|---|---|
pubring.kbx → No such file or directory |
Keyring cannot be created/read under the bogus homedir |
can't create '…/gnupg_spawn_dirmngr_sentinel.lock' → The system cannot find the path specified |
Native Windows API cannot resolve /r/...; parent path does not exist |
can't connect to the dirmngr: No such file or directory |
dirmngr socket cannot be created under the bogus homedir |
Kein Dirmngr (keyserver retrieval failed) |
Consequence of dirmngr never starting → --recv-keys fails |
The reporter's own suggested fix — use R:/GHEC/Build_B1/_temp/gpg-957091c8 — matches
the analysis above.
Why it isn't caught by CI / existing tests
- The unit tests in
src/main/__tests__/gpg-verification.test.js:132-163only assert the transformation itself (C:\a\_temp\gpg-home→/c/a/_temp/gpg-home). They encode the buggy behaviour as the expected behaviour, so they pass while the real bug persists. - GitHub-hosted
windows-latestrunners resolvegpgto the Git-for-Windows build, which happens to accept/r/.... The bug only surfaces when the native GnuPG (Gpg4win) is first onPATH— the reporter's self-hosted setup. This is why it escaped testing.
Suggested fix (not applied — investigation only)
On Windows, do not rewrite the drive letter. Normalise separators only, keeping a native Windows path with forward slashes, which both GPG builds accept:
export function convertToUnixPath(windowsPath) {
if (process.platform !== "win32") {
return windowsPath;
}
// Keep the drive letter (R:/...). Native GnuPG (Gpg4win) cannot resolve the
// MSYS "/r/..." form, and Git-for-Windows GPG understands "R:/..." too.
return windowsPath.replaceAll("\\", "/");
}
The corresponding tests at src/main/__tests__/gpg-verification.test.js:132-153 should be
updated to expect C:/a/_temp/gpg-home (and the function likely renamed, since it no
longer produces a Unix path).
Secondary observation (not the reported bug)
src/main/run-sonar-scanner.js:147 invokes ${scannerDir}/jre/bin/java without a .exe
suffix and with forward slashes. This only runs when SONAR_ROOT_CERT is set (truststore
handling) and is unrelated to the GPG failure, but is worth a separate look for Windows
robustness.
Reproducer
A CI reproducer is provided in .github/workflows/issue-reproduced.yml. It runs on
windows-latest, installs the native GnuPG (Gpg4win) and puts it first on PATH to
match the reporter's environment, then runs the action (uses: ./, which executes the
committed dist/index.js). It expects the action to fail at GPG signature
verification. It also includes a direct step showing native gpg rejecting an
MSYS-style --homedir, isolating the root cause. The workflow triggers on: push.