Skip to content

[CFX-6924] fix(tls): repair Windows cert export - #786

Open
cdevent wants to merge 4 commits into
mainfrom
cdevent/CFX-6924-tls-fix-febug
Open

[CFX-6924] fix(tls): repair Windows cert export #786
cdevent wants to merge 4 commits into
mainfrom
cdevent/CFX-6924-tls-fix-febug

Conversation

@cdevent

@cdevent cdevent commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

and stop swallowing TLS errors

Three defects that compounded into an undiagnosable failure: the export broke, the error explaining it was discarded, and there was no fallback diagnostic.

  1. Pin PSModulePath for the cert-export subprocess.

ExportWindowsCerts spawned powershell.exe with the parent environment. The Cert: drive comes from Microsoft.PowerShell.Security, which autoloads only if PSModulePath includes the Windows PowerShell system module directory. pwsh 7 sets a value that omits it, and CI runners often mangle it -- the child then could not enumerate any store, -ErrorAction SilentlyContinue ate the DriveNotFoundException, and empty stdout surfaced as "no certificates found in Windows cert store".

The child now gets a pinned PSModulePath. The script also imports the module explicitly and asserts the Cert: drive exists, so a genuine module problem reports itself instead of masquerading as an empty store. Per-store SilentlyContinue is kept, since an individual store may legitimately be empty.

Verified on Windows 11 25H2 against the same broken PSModulePath that reproduces the bug: v0.2.83 exits 1 with no bundle, the fixed binary exits 0 and writes 73 certificates.

  1. Make cmd.ReportError the single error reporter.

Cobra skips printing when the executed command sets SilenceErrors: true, and main.go discarded the error entirely -- so anything raised by the root PersistentPreRunE vanished for the 11 commands setting that flag, including all of auth. "dr --ca-cert auth check" exited 1 with zero bytes on both streams, on every platform. A typo in a CA path silently fell back to system trust.

RootCmd now sets SilenceErrors so cobra never prints, and main reports through cmd.ReportError, which honours the existing cli.ErrSilent contract. Errors are printed exactly once. ExecuteContext returns unwrapped, since its "execute root command:" prefix would otherwise leak into user-facing output.

  1. Surface the subprocess's stderr.

cmd.Output already captures the child's stderr into ExitError.Stderr and the code dropped it. It is now included in the error, and the empty-store message says which stores were checked.

Tests: table tests for the PSModulePath helpers (kept build-tag free so they run on every host, not only Windows) and for ReportError, plus a guard that RootCmd.SilenceErrors stays true. task lint passes for linux, darwin and windows; task test passes.

RATIONALE

CHANGES

Same A/B, same broken PSModulePath, now confirmed on Windows Server 2025 as well as Windows 11 25H2. And the silent-failure fix works there too — --ca-cert with a bad path printed one clear line where it previously printed nothing.
OLD (installed v0.2.85) -> exit=1 bundle=False "no certificates found in Windows cert store"
NEW (fixed 371a3ad) -> exit=0 bundle=True

PR Automation

Comment-Commands: Trigger CI by commenting on the PR:

  • /trigger-smoke-test or /trigger-test-smoke - Run smoke tests
  • /trigger-install-test or /trigger-test-install - Run installation tests

Labels: Apply labels to trigger workflows:

  • run-smoke-tests or go - Run smoke tests on demand (only works for non-forked PRs)

Important

For Forked PRs: The run-smoke-tests label won't work. A required Smoke Tests check will block merge until a maintainer acts:

  • A maintainer uses /approve-smoke-tests to run smoke tests (results will set the check)
  • A maintainer uses /skip-smoke-tests to bypass the check without running tests

Please comment requesting a maintainer review if you need smoke tests to run.


Note

Medium Risk
Touches TLS setup and Windows trust-bundle export on the critical path for HTTPS; changes are targeted fixes with tests but affect how cert/TLS failures surface on all platforms.

Overview
Fixes CFX-6924, where Windows cert export could fail silently and root TLS/config errors could exit with no stderr output.

Windows cert export now spawns powershell.exe with a pinned PSModulePath (helpers in internal/tls/psmodulepath.go) so Microsoft.PowerShell.Security and the Cert: drive load under pwsh/CI environments. The script imports the module explicitly, checks the Cert provider, includes subprocess stderr on failure, and clarifies the truly-empty-store case.

CLI errors are centralized: RootCmd.SilenceErrors = true, new cmd.ReportError (respects cli.ErrSilent), and main prints failures before exit. ExecuteContext returns errors unwrapped so users do not see an extra execute root command: prefix. Tests lock in ReportError behavior and SilenceErrors on the root command.

Reviewed by Cursor Bugbot for commit 8ea508f. Configure here.

…LS errors

Three defects that compounded into an undiagnosable failure: the export broke,
the error explaining it was discarded, and there was no fallback diagnostic.

1. Pin PSModulePath for the cert-export subprocess.

ExportWindowsCerts spawned powershell.exe with the parent environment. The
Cert: drive comes from Microsoft.PowerShell.Security, which autoloads only if
PSModulePath includes the Windows PowerShell system module directory. pwsh 7
sets a value that omits it, and CI runners often mangle it -- the child then
could not enumerate any store, -ErrorAction SilentlyContinue ate the
DriveNotFoundException, and empty stdout surfaced as "no certificates found in
Windows cert store".

The child now gets a pinned PSModulePath. The script also imports the module
explicitly and asserts the Cert: drive exists, so a genuine module problem
reports itself instead of masquerading as an empty store. Per-store
SilentlyContinue is kept, since an individual store may legitimately be empty.

Verified on Windows 11 25H2 against the same broken PSModulePath that
reproduces the bug: v0.2.83 exits 1 with no bundle, the fixed binary exits 0
and writes 73 certificates.

2. Make cmd.ReportError the single error reporter.

Cobra skips printing when the executed command sets SilenceErrors: true, and
main.go discarded the error entirely -- so anything raised by the root
PersistentPreRunE vanished for the 11 commands setting that flag, including all
of auth. "dr --ca-cert <bad-path> auth check" exited 1 with zero bytes on both
streams, on every platform. A typo in a CA path silently fell back to system
trust.

RootCmd now sets SilenceErrors so cobra never prints, and main reports through
cmd.ReportError, which honours the existing cli.ErrSilent contract. Errors are
printed exactly once. ExecuteContext returns unwrapped, since its
"execute root command:" prefix would otherwise leak into user-facing output.

3. Surface the subprocess's stderr.

cmd.Output already captures the child's stderr into ExitError.Stderr and the
code dropped it. It is now included in the error, and the empty-store message
says which stores were checked.

Tests: table tests for the PSModulePath helpers (kept build-tag free so they
run on every host, not only Windows) and for ReportError, plus a guard that
RootCmd.SilenceErrors stays true. task lint passes for linux, darwin and
windows; task test passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cdevent
cdevent requested a review from a team as a code owner August 18, 2026 10:33
@datarobot-pr-review-router

Copy link
Copy Markdown

🎫 Jira: CFX-6924 — Test Private CA support on Windows

@cdevent cdevent changed the title [CFX-6924] fix(tls): repair Windows cert export and stop swallowing T… [CFX-6924] fix(tls): repair Windows cert export Aug 18, 2026
@github-actions github-actions Bot added the go Pull requests that update go code label Aug 18, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8ea508f. Configure here.

Comment thread main.go
// RootCmd sets SilenceErrors, so nothing has printed this yet. Report it here
// before exiting; ReportError skips cli.ErrSilent, which commands return once
// they have printed their own message.
cmd.ReportError(os.Stderr, err)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate auth error output

Medium Severity

Centralizing on ReportError while leaving self-reporting paths returning plain errors re-prints failures that were already shown. auth login calls log.Error then returns err, and EnsureAuthenticatedE returns "authentication failed" after EnsureAuthenticated already wrote user messages, so commands that previously relied on SilenceErrors (including dr auth login and dr dotenv update) now emit duplicate stderr lines.

Suggested change
cmd.ReportError(os.Stderr, err)
return cli.ErrSilent

(in cmd/auth/login/cmd.go after each log.Error, and have EnsureAuthenticatedE return cli.ErrSilent instead of errors.New("authentication failed"))

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by project rule: Bugbot Rules for DataRobot CLI

Reviewed by Cursor Bugbot for commit 8ea508f. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed in 371a3ad — this was a real regression from centralizing error reporting. Thanks.

What I verified before changing anything. cmd/auth/login/cmd.go already had the correct pattern in two places (return cli.ErrSilent after log.Error, lines 57 and 64) and violated it in three, so the fix was to follow the file's own convention rather than revert the centralization. All three now return cli.ErrSilent.

For EnsureAuthenticatedE I checked that ErrSilent cannot hide a failure — every path where EnsureAuthenticated returns false reports first: ReportEnvCredentialsError + reportStoredProfileNotUsed, GetBaseURLOrAsk (message emitted internally), both skipAuthFlow branches, and the two log.Error calls for key retrieval and config write. So the message is always already on screen.

Empirically: dr --skip-auth auth login now prints one line, not two.

Three related paths I deliberately left alone, having checked each:

  • cmd/self/update and cmd/self/completion print then return errors, but neither sets SilenceErrors — cobra printed those before and main prints them now, so output is unchanged.
  • auth.WriteConfigFileSilent logs and returns the error, but its only command-level caller (auth/logout) already returns ErrSilent. It does double-log with auth login, which is pre-existing and out of scope here.
  • component/add and component/update set SilenceErrors: true and never returned ErrSilent, so their errors were previously swallowed entirely. Centralizing fixed a second silent-failure case: dr component add outside a repo root now reports You must be in the repository root directory. instead of exiting 1 in silence.

Tests added for both fixed paths. task lint is clean for linux/darwin/windows.

Unrelated flaky test, for the record (not touched by this PR): TestExecutePluginContextCancellation failed in 2 of 4 full-suite runs on this branch. internal/plugin/exec_test.go:219 sleeps a fixed 500 ms to let the subprocess install its SIGTERM trap; under a parallel -race run that is sometimes not enough, cancel() lands first, and the shell takes SIGTERM's default action — exit -1 and no marker file. It passes 5/5 in isolation and the suite runs with -shuffle=on, so ordering varies. Worth its own ticket to poll for a readiness marker instead of sleeping.

Addresses Cursor bugbot on #786. Centralizing error reporting in main.go made
the cli.ErrSilent contract load-bearing, and three paths that print their own
message still returned plain errors -- so those failures printed twice.

cmd/auth/login RunE had the pattern right in two places (return cli.ErrSilent
after log.Error) and wrong in three; those three now follow the file's own
convention. auth.EnsureAuthenticatedE returned errors.New("authentication
failed") after EnsureAuthenticated had already written a user-facing message;
verified that every path returning false reports first -- the env-credential
report, GetBaseURLOrAsk, both skip-auth-flow branches, and the two log.Error
calls -- so returning ErrSilent cannot hide a failure.

Verified: "dr --skip-auth auth login" now prints one line, not two.

Left alone deliberately: cmd/self/update and cmd/self/completion never set
SilenceErrors, so cobra printed their errors before and main prints them now --
no change in output. auth.WriteConfigFileSilent logs and returns the error, but
its only command-level caller (auth/logout) already returns ErrSilent. And
component/add plus component/update set SilenceErrors while never returning
ErrSilent, so their errors were previously swallowed whole; centralizing fixed
a second silent-failure case ("dr component add" outside a repo root now
reports instead of exiting 1 in silence).

Tests: EnsureAuthenticatedE returns ErrSilent on failure and nil on success;
login RunE returns ErrSilent for the --skip-auth short circuit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@datarobot-pr-review-router

Copy link
Copy Markdown

Code Ownership

Cli Maintainers

  • cmd/auth/login/cmd.go
  • cmd/auth/login/cmd_test.go
  • cmd/exit.go
  • cmd/exit_test.go
  • cmd/root.go
  • internal/auth/auth.go
  • internal/auth/auth_test.go
  • internal/tls/psmodulepath.go
  • internal/tls/psmodulepath_test.go
  • internal/tls/tls_windows.go
  • main.go

Review requested from the teams above. Labels will be removed automatically upon approval.

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Smoke tests triggered! Running on Linux and Windows...

@github-actions

Copy link
Copy Markdown
Contributor

All smoke tests passed!

✅ Linux: success
✅ Windows: success

View run details

@chasdr chasdr left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

one non-blocking note on a file outside this diff: the centralization changes cmd/component/update/cmd.go behavior. the uv-not-installed path (around line 181) logs "uv is not installed." and then returns execErr, so main.ReportError now also prints the raw Error: exec "uv".... before, that return was swallowed and the user saw just the one friendly line. did you want that path to return cli.ErrSilent since it already printed its own message?

cdevent and others added 2 commits August 19, 2026 17:06
Reported while validating the export fix: when there is genuinely nothing to
export, "no certificates found in Windows cert store" states the outcome without
telling the user what to do about it. With the PSModulePath fix in place, that
message now means what it says -- an unreachable store reports itself separately
-- so an empty store is a machine-configuration problem the user can act on.

ExportWindowsCerts returns a new ErrNoWindowsCerts sentinel for that case, and
applyWindowsCerts pairs it with guidance: how to inspect both Root stores, how
to import a missing organization CA, and the --ca-cert escape hatch.

The call site reports the error itself and returns cli.ErrSilent rather than
letting main.go print. Printing guidance at the call site while main printed the
error afterwards put the help block above the message it explains; owning both
keeps the order right, and ErrSilent suppresses the duplicate. Everything goes
to stderr, so stdout stays parseable under --output-format json.

The guidance helper lives in an untagged file so it is unit tested on every
host, matching the psmodulepath helpers earlier in this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bly is

Raised while reviewing the new guidance: on a machine whose stores clearly do
contain certificates, telling the user to import a CA is exactly the wrong
advice -- the same trap the original message set. Two residual paths could still
produce a false "no certificates found": a restricted PowerShell language mode
forbids the [Convert]::ToBase64String call, so enumeration succeeds while every
encode fails and stdout comes back empty with exit code 0; and any other
per-item encode failure behaves the same way.

The script now refuses to run outside FullLanguage, naming the mode it found,
and reports the enumerated count as a CERTCOUNT header before attempting any
encoding. ErrNoWindowsCerts is returned only for CERTCOUNT=0. A non-zero count
with no PEM body returns the new ErrCertEncodeFailed instead, which states how
many certificates were seen -- so the advice can never contradict what the user
sees in certmgr.

Header parsing lives in an untagged file with table tests covering CRLF output,
stray whitespace, a missing header, and the empty-versus-unencodable
distinction, since that is where a regression would quietly restore the
misleading message. Missing or unparseable headers are errors rather than a
silent zero.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

00 - Ready for Review go Pull requests that update go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants