Skip to content

DFLOW-151 - JVM client setup (Windows)#10

Closed
akushsky wants to merge 1 commit into
feature/DFLOW-152-jvm-macosfrom
feature/DFLOW-151-jvm-windows
Closed

DFLOW-151 - JVM client setup (Windows)#10
akushsky wants to merge 1 commit into
feature/DFLOW-152-jvm-macosfrom
feature/DFLOW-151-jvm-windows

Conversation

@akushsky

@akushsky akushsky commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a Windows client-side installer + validator that wires a corporate CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic redirected through package-reroute validates correctly. JVM trust only — does not configure Node/npm/Python and does not touch Docker credentials (pair with install_certs_windows.ps1 for those).

Single path on Windows. -Djavax.net.ssl.trustStoreType=Windows-ROOT is not exposed in v1: the Gradle Daemon stale-cache issue (gradle/gradle#6584) was fixed in Gradle 8.3 via gradle/gradle#25106, but we keep the JKS+JAVA_TOOL_OPTIONS recipe uniform across Linux/macOS/Windows and avoid silently breaking developers still on Gradle < 8.3. The script:

  1. Build a per-user JKS truststore at %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks containing the customer CA.
  2. Set JAVA_TOOL_OPTIONS at User scope via [Environment]::SetEnvironmentVariable(..., 'User') → writes HKCU\Environment + broadcasts WM_SETTINGCHANGE.

No Administrator required. User-scope writes to HKCU\Environment without elevation; %LOCALAPPDATA% is per-user.

Base branch

Stacked on feature/DFLOW-152-jvm-macos (PR #9), which is stacked on feature/DFLOW-150-jvm-linux (PR #8). Diff shows only Windows-specific files. When PR #9 merges, GitHub auto-rebases this to feature/DFLOW-150-jvm-linux; after PR #8 merges, to main.

Files

  • install_certs_jvm_windows.ps1 — installer.
  • validate_certs_jvm_windows.ps1 — companion validator.
  • _jvm_windows_paths.ps1 — shared constants (dot-sourced).
  • testing/test_install_certs_jvm_windows.ps1 — smoke runner (15 invariants).
  • .github/workflows/ci.yml — new test-windows-jvm job.
  • README.md — new "Windows (JVM)" section.

Design symmetry with siblings

  • validate_pem ported to PowerShell using System.Security.Cryptography.X509Certificates (no openssl dependency for the install path; DER input rejected for cross-platform predictability).
  • -CertName regex ^[A-Za-z0-9._-]+$ rejects path-traversal at parse time.
  • Idempotent re-install: JKS removed-then-recreated (single alias guaranteed); env var replaced (not appended).
  • Validator asserts trustedCertEntry-only — a PrivateKeyEntry makes it fail loud (cross-platform invariant).
  • Validator accepts -CertName as a no-op for fleet-wrapper parity with the Linux validator.
  • Test runner captures stdout+stderr per invocation; dumps on UNEXPECTED exit only.

Test plan

15 invariants on windows-latest:

  • positive install + validate
  • subject mismatch → exit 1
  • idempotent re-install (single JKS alias after 2 runs)
  • custom -CertName round-trips (alias inside JKS = cert-name)
  • path-traversal -CertName rejected
  • malformed PEM rejected
  • expired CA rejected (skipped when openssl can't produce a verifiably-expired test cert)
  • leaf cert (CA:FALSE) rejected
  • [Environment]::GetEnvironmentVariable('JAVA_TOOL_OPTIONS', 'User') references the expected JKS path
  • validate_pem 30-day-expiry warn fires
  • validate_pem multi-cert bundle warn fires
  • JTO env var REPLACES (not appends) on re-install
  • missing keytool fails cleanly (PATH+JAVA_HOME stripped)
  • mandatory -UseCert: no-args invocation fails
  • DER cert rejected (C1 cross-platform parity)

CI uses actions/setup-java@v4 (Temurin 21) to provide keytool.exe via JAVA_HOME.

🤖 Generated with Claude Code

@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from ed4d1a9 to d59501e Compare June 9, 2026 13:30
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Qodana for JVM

It seems all right 👌

No new problems were found according to the checks applied

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2026.1.0
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

akushsky added a commit that referenced this pull request Jun 9, 2026
Critical:
- C1: capture $LASTEXITCODE IMMEDIATELY after the native call in
  Invoke-Installer / Invoke-Validator, BEFORE the Out-String pipeline.
  Under $ErrorActionPreference='Stop' a pipeline element raising any
  error would jump past `$rc = $LASTEXITCODE` and leave $rc carrying a
  stale value from a previous step -- which can silently flip an
  -ExpectFail assertion into a phantom pass.

Important:
- I10: new Invoke-Keytool helper that wraps the inline `& $Keytool -list`
  calls (alias-count assertion in test #3, custom-alias assertion in
  test #4) with $ErrorActionPreference='Continue'. JDK 17+ has been
  observed emitting crypto-policy notices to stderr at rc=0 from `-list`;
  under Stop those would terminate the test. Same pattern installer +
  validator already use for their own keytool calls.
- I12: Cleanup() switched from -ErrorAction SilentlyContinue to
  probe-then-warn. A locked $JksDir from a leaked keytool.exe child of
  the previous test now produces a clear warning instead of silently
  failing and then dying mid-Build-JksTruststore in the next test.
- I16: print openssl version + emit Write-Warning if it's older than
  3.2 (test #7 requires -not_before / -not_after introduced in 3.2). A
  future image bump that drops openssl below 3.2 will surface as a
  yellow CI annotation instead of a silent SKIP that nobody notices.

New tests (raising the matrix from 10 to 14):
- I19: docstring split #10 into 10 + 11 (30-day-expiry vs bundle-warn
  were rolled into one bullet but are distinct invariants).
- I14: new test #12 -- JTO env var REPLACES on re-install. Pre-seeds a
  sentinel value before install, then asserts the sentinel is gone
  post-install. Catches a future regression that flips to append-mode.
- I13: new test #13 -- missing keytool fails cleanly. Spawns the
  installer in a child shell with $env:JAVA_HOME=$null and a PATH
  stripped of all JDK locations; asserts rc!=0 + error message mentions
  keytool. Exercises Require-Keytool's pre-flight probe (added in the
  installer hardening commit).
- I15: new test #14 -- mandatory -UseCert. Invokes the installer with
  no args under -NonInteractive (no hang waiting for prompt); asserts
  rc!=0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
akushsky added a commit that referenced this pull request Jun 9, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from 2426948 to 4b9e5d1 Compare June 9, 2026 13:59
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from 0741ec4 to b51ed3e Compare June 10, 2026 06:47
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from 4b9e5d1 to d0923b4 Compare June 10, 2026 06:53
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from b51ed3e to 8276bcb Compare June 10, 2026 07:02
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from d0923b4 to cb913b2 Compare June 10, 2026 07:03
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from 8276bcb to a7b7274 Compare June 10, 2026 07:09
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from cb913b2 to fbcccb1 Compare June 10, 2026 07:09
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from a7b7274 to b45d81e Compare June 10, 2026 07:15
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from fbcccb1 to 96509ca Compare June 10, 2026 07:15
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from b45d81e to e597dcf Compare June 10, 2026 07:19
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from 96509ca to b9aaee2 Compare June 10, 2026 07:19
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from e597dcf to 69e37aa Compare June 10, 2026 07:35
@akushsky akushsky closed this Jun 10, 2026
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from b9aaee2 to 69e37aa Compare June 10, 2026 07:36
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky reopened this Jun 10, 2026
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from b492eec to f169037 Compare June 10, 2026 08:31
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from 69e37aa to 57b27a7 Compare June 10, 2026 16:47
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from f169037 to c0b2129 Compare June 10, 2026 16:51
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from 57b27a7 to caf69f9 Compare June 10, 2026 16:57
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from c0b2129 to 5d836ae Compare June 10, 2026 16:57
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from caf69f9 to 5982ef4 Compare June 10, 2026 17:02
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from 5d836ae to 8fe09a6 Compare June 10, 2026 17:02
akushsky added a commit that referenced this pull request Jun 10, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-152-jvm-macos branch from 5982ef4 to 8c0a901 Compare June 10, 2026 17:18
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@akushsky akushsky force-pushed the feature/DFLOW-151-jvm-windows branch from 8fe09a6 to adb51bb Compare June 10, 2026 17:18
RaniLinkov pushed a commit that referenced this pull request Jun 24, 2026
Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RaniLinkov pushed a commit that referenced this pull request Jun 24, 2026
Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RaniLinkov added a commit that referenced this pull request Jun 25, 2026
* DFLOW-150 - JVM client setup (Linux)

Adds a Linux client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_debian_ubuntu.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Two lab-verified paths, auto-detected:

  Path A — update-ca-trust
    RHEL/Fedora/CentOS/Amazon-Linux when a JDK whose lib/security/cacerts
    symlinks to /etc/pki/ca-trust/extracted/java/cacerts is on PATH
    (Red Hat OpenJDK). Drops the CA into /etc/pki/ca-trust/source/anchors/
    and runs `update-ca-trust extract`. No env var.

  Path B — JKS + JAVA_TOOL_OPTIONS
    Everything else (Debian/Ubuntu, Amazon Corretto, Eclipse Temurin,
    SDKMAN, manual .tar.gz installs). Builds a JKS at
    /etc/ssl/package-route-jvm/truststore.jks containing only the customer
    CA; writes JAVA_TOOL_OPTIONS to /etc/environment plus the developer
    user's .bashrc/.zshrc. JDK-version-agnostic by construction.

Detection rule (detect_mode):
  1. No update-ca-trust on PATH                 -> Path B
  2. /etc/pki/ca-trust/extracted/java/cacerts absent -> Path B
  3. No `java` on PATH                          -> Path A (assumes Red Hat
       OpenJDK will follow via dnf; emits a loud end-of-run warning)
  4. `java`'s cacerts symlinks to RHEL store    -> Path A; else Path B

--mode java-tool-options / --mode update-ca-trust overrides detection.

Files:
  install_certs_jvm_linux.sh      Installer (mirrors install_certs_debian_ubuntu.sh patterns).
  validate_certs_jvm_linux.sh     Companion validator; subject-substring match.
  _jvm_linux_paths.sh             Shared constants sourced by both scripts so they cannot drift.
  testing/test_install_certs_jvm_linux.sh   Docker matrix runner (4 distros x 10 invariants).
  .github/workflows/ci.yml        New test-linux-jvm job runs the matrix runner.
  README.md                       New "Linux (JVM)" section with the full design + caveats.

Hardening fixes folded in from two rounds of pr-review-toolkit review:

  Critical:
  - --cert-name actually propagates to the Path B JKS alias and the validator
    accepts --cert-name so Path A's anchor file can be located when a
    non-default basename was used.
  - validate_pem rejects expired certs, leaf certs (CA:FALSE), warns on
    bundles (keytool -importcert -noprompt only imports the first cert).
  - install_via_update_ca_trust and validate_keystore_contains_subject
    capture keytool output explicitly; the previous pipefail+SIGPIPE
    pattern (... | grep -qi "$subject") silently turned real keytool
    failures into "subject not found" misdiagnoses AND real positive
    matches into false negatives.
  - validator detect_mode "both paths present" branch now routes the
    whole warn block to stderr; previously the warn lines leaked into
    the command-substitution capture and broke the case dispatch,
    yielding a silent exit-0 with NO checks run.

  Important:
  - validator FAILs (not OKs) when keytool is missing on the JKS path.
  - --all-users root check moved into parse_args (fast-fail exit 1).
  - update_user_shell_rc tracks RC_UPDATED and the final summary prints
    a loud WARNING when the per-user rc step was skipped.
  - --mode jto -> --mode java-tool-options for self-documenting CLI.
  - mktemp is -p $(dirname target) so `mv` is rename(2) (atomic), and an
    empty-awk-output check refuses to clobber /etc/environment.
  - --cert-name regex-validated to [A-Za-z0-9._-]+ to prevent path
    traversal in the anchor file path.
  - chown failure on the user's rc file captured + warned.
  - get_target_user verifies the picked username exists in /etc/passwd.

Smoke matrix (testing/test_install_certs_jvm_linux.sh): Ubuntu 22.04,
Debian 12, RHEL UBI 9, Amazon Linux 2023, in parallel. Per-container
invariants: positive install+validate, subject mismatch -> exit 1,
idempotent re-install (no duplicate lines/aliases), custom --cert-name
round-trips, path-traversal rejected, malformed PEM rejected, expired
CA rejected (skipped when openssl can't produce a verifiably-expired
test cert), leaf cert (CA:FALSE) rejected, forced --mode update-ca-trust
(Path A) on hosts with update-ca-trust, dual-artifact validator
regression test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* DFLOW-152 - JVM client setup (macOS)

Adds a macOS client-side installer + validator that wires a corporate CA
into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only —
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_macos.sh if you need those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux). Single path on macOS — there is no
update-ca-trust fork because KeychainStore is broken per JDK-8321045:

  1. Build a per-user JKS truststore at
     ~/Library/Application Support/JFrog/package-route-jvm/truststore.jks
  2. Write a per-user LaunchAgent at
     ~/Library/LaunchAgents/com.jfrog.package-reroute.jto-env.plist
     calling `launchctl setenv JAVA_TOOL_OPTIONS=…` at RunAtLoad
  3. Bootstrap the agent into `gui/<uid>` so Dock-launched IDEs
     (IntelliJ, JetBrains Toolbox, `open -a …`) inherit the env var

The ~/.zshrc / ~/.bash_profile shortcut is deliberately NOT touched —
verified in the research wiki to silently fail for GUI-launched IDE
builds.

Files:
  install_certs_jvm_macos.sh              Installer (~540 lines).
  validate_certs_jvm_macos.sh             Companion validator.
  _jvm_macos_paths.sh                     Shared constants sourced by both.
  testing/test_install_certs_jvm_macos.sh 12-invariant smoke runner.
  .github/workflows/ci.yml                New test-macos-jvm job.
  README.md                               New "macOS (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review (5
agents, 21 findings: 5 Critical + 13 Important + 3 Minor):

  Critical:
  - validate_pem leaf-cert (CA:FALSE) check now works on stock macOS
    LibreSSL. The previous `openssl x509 -ext` flag is OpenSSL 3.x-only;
    LibreSSL silently failed → leaf certs accepted as trust anchors.
    New path parses `-noout -text` output (portable across both).
  - chown failure capture at both call sites (JKS dir + plist).
    Under --all-users a silent chown would leave root-owned plists in a
    user's ~/Library/LaunchAgents/, which launchd silently refuses to
    load — exactly the phantom-success the project bans.
  - --cert-name semantics clarified in constants file + --help. The
    previous "LaunchAgent label suffix" claim was wishful thinking; the
    flag is alias-cosmetic only.
  - Test runner docstring rewritten (cleanup runs only on positive
    cases) and the iter_all_users "mirrors install_certs_macos.sh"
    claim corrected (it's a stricter filter, not a mirror).

  Important:
  - get_single_target_user filter+order matches install_certs_macos.sh
    (SUDO_USER → /dev/console → logname; rejects loginwindow pseudo-user).
  - get_user_home eval-on-username fallback replaced with dscacheutil.
  - bootstrap_launch_agent retry bumped 5×100ms → 20×100ms to survive
    EDR exec latency; warn text no longer suggests a logout dance.
  - Validator --help documents the absence of --cert-name.
  - Validator final summary qualifies "All checks passed" with a WARN
    count so a green exit doesn't over-promise when launchctl getenv
    was skipped (no GUI session).
  - install_as_test_user / validate_as_test_user capture combined
    stdout/stderr to a tempfile; dump on UNEXPECTED exit only.
  - Smoke matrix gained 3 new invariants: --all-users iteration (#11),
    plist content validation via plutil -extract (#10), validate_pem
    warn-paths for 30-day-expiry and multi-cert bundle (#12).
  - Test #9 (launchctl getenv) now retries 20×100ms to mirror the
    installer.
  - CI workflow uses explicit `sudo env JAVA_HOME=… PATH=…` instead of
    --preserve-env, surviving future actions/setup-java renames. PATH
    includes /usr/sbin so chown is resolvable under sudo.

  Minor:
  - README requirements list names plutil/launchctl/dscl/stat.
  - README "five launch contexts" claim scoped to what the test
    actually verifies.
  - JKS_PASSWORD "not a secret" comment scoped to trustedCertEntry-only
    stores (PrivateKeyEntry would change the calculus).

Smoke matrix: 12/12 green locally on dev Mac, 12/12 green on CI
(Test (macOS JVM) job, macos-latest runner with Temurin 21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* DFLOW-151 - JVM client setup (Windows)

Adds a Windows client-side installer + validator that wires a corporate
CA into the JVM trust path so Maven / Gradle / sbt / Apache Ivy traffic
redirected through package-reroute validates correctly. JVM trust only --
does not configure Node/npm/Python and does not touch Docker credentials.
Pair with install_certs_windows.ps1 for those flows.

Based on the published research wiki (DFLOW-136):
  https://jfrog-int.atlassian.net/wiki/spaces/RTFACT/pages/2440101931

Sibling to DFLOW-150 (Linux) and DFLOW-152 (macOS). Single path on
Windows -- there is no Windows-ROOT trustStoreType option because the
Gradle Daemon caches a stale store (gradle/gradle#6584):

  1. Build a per-user JKS truststore at
     %LOCALAPPDATA%\JFrog\package-route-jvm\truststore.jks
  2. Set JAVA_TOOL_OPTIONS at User scope via
     [Environment]::SetEnvironmentVariable(..., 'User')
     which writes HKCU\Environment AND broadcasts WM_SETTINGCHANGE.

No Administrator required (User-scope writes to HKCU\Environment without
elevation; %LOCALAPPDATA% is per-user).

Files:
  install_certs_jvm_windows.ps1              Installer (~370 lines).
  validate_certs_jvm_windows.ps1             Companion validator.
  _jvm_windows_paths.ps1                     Shared constants dot-sourced.
  testing/test_install_certs_jvm_windows.ps1 14-invariant smoke runner.
  .github/workflows/ci.yml                   New test-windows-jvm job.
  README.md                                  New "Windows (JVM)" section.

Hardening fixes folded in from a round of pr-review-toolkit review
(5 agents, 28 findings: 2 Critical + 21 Important + 5 Minor):

  Critical:
  - SetEnvironmentVariable round-trip verify. Windows 10 1607+ silently
    truncates user-env values > 2047 chars rather than throwing; future
    JTO extensions would corrupt HKCU. Now reads back and hard-fails on
    mismatch.
  - Test runner captures $LASTEXITCODE IMMEDIATELY after the native
    powershell.exe call, BEFORE the Out-String pipeline. Without this an
    intervening pipeline error leaves $rc carrying a stale value from a
    previous step and -ExpectFail can phantom-pass.

  Important production-code:
  - Require-Keytool now probes `keytool -help` to reject corrupt 0-byte
    stubs and broken-runtime keytools.
  - Build-JksTruststore post-import verify: runs keytool -list and asserts
    at least one trustedCertEntry, catching JBR-bundled keytool cases
    where rc=0 hides a non-standard java.security provider list.
  - Build-JksTruststore precondition checks for $env:LOCALAPPDATA
    existence + readability (OneDrive Known-Folder-Move, roaming-profile
    failures get actionable diagnostics).
  - Test-CaCertificate basicConstraints comment documents the legacy-cert
    behavior (extension absent -> accept, matches Linux/macOS siblings).
  - Maintenance comment on $prevEAP capture / restore.
  - Set-JavaToolOptions now quotes the trustStorePassword value.
  - Build-JksTruststore surfaces keytool's non-failure stderr warnings
    (JKS deprecation, weak-algo notices) on the success path.
  - Test-UserEnvVar regex anchors both branches so a path like
    $JksPath.bak.pkcs12 doesn't false-positive as a prefix match.
  - Test-UserEnvVar warns on Machine-scope sibling env vars to flag
    mixed-scope misconfig.
  - Validator's WarnCount infrastructure now actually fires on the
    Machine-scope path.

  Important test-runner:
  - Invoke-Keytool helper wraps inline `keytool -list` in tests with
    EAP=Continue, matching the installer/validator pattern. JDK 17+
    crypto-policy notices to stderr won't terminate the test.
  - Cleanup() switched from -ErrorAction SilentlyContinue to probe-then-
    warn so locked $JksDir from a leaked keytool.exe child surfaces.
  - openssl version banner + warning when < 3.2 (required by test #7).
  - 4 new tests raise the matrix from 10 to 14: JTO env var replaces on
    re-install (catches future append-mode regression), missing keytool
    fails cleanly, mandatory -UseCert no-args fails non-interactively,
    docstring split #10 -> #10+#11 (30-day expiry vs bundle warn).
  - Explicit exit 0 so child-rc from negative tests doesn't leak to the
    shell wrapper.

  Docs / future-proofing:
  - PowerShell 5.1+ named as a requirement in installer header.
  - README "JDK-version-agnostic" claim qualified ("across currently-
    supported JDKs; JKS format still read by JDK 8-25; future JDK
    dropping JKS would require a format bump").
  - README "covered for free" for Gradle auto-provisioned JDKs now
    cross-references the Gradle Daemon caveat.
  - README validation row clarifies that basicConstraints absent ->
    accept; CA:TRUE required only when extension present.
  - _jvm_windows_paths.ps1 header documents the "UTF-8 BOM +
    ASCII-only content" guard for PowerShell 5.1's Windows-1252
    default; em-dashes in source files terminate string literals
    mid-line under cp1252 decoding and the parser reports a baffling
    error 100+ lines later.

Smoke matrix: 14/14 green on windows-latest in CI (Test (Windows JVM)
job, Temurin 21 via actions/setup-java).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* DFLOW-140 - Use bundled JVM truststores on macOS and Windows

Signed-off-by: rani <ranil@jfrog.com>

* DFLOW-140 - Split Linux JVM trust installers

* DFLOW-140 - Add JVM truststore bundle builders

---------

Signed-off-by: rani <ranil@jfrog.com>
Co-authored-by: Michael Akushsky <michaelak@jfrog.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gmininberg-jfrog gmininberg-jfrog deleted the feature/DFLOW-151-jvm-windows branch June 28, 2026 08:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants