[CFX-6924] fix(tls): repair Windows cert export - #786
Conversation
…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>
|
🎫 Jira: |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ 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.
| // 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) |
There was a problem hiding this comment.
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.
| 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)
Triggered by project rule: Bugbot Rules for DataRobot CLI
Reviewed by Cursor Bugbot for commit 8ea508f. Configure here.
There was a problem hiding this comment.
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/updateandcmd/self/completionprint then return errors, but neither setsSilenceErrors— cobra printed those before andmainprints them now, so output is unchanged.auth.WriteConfigFileSilentlogs and returns the error, but its only command-level caller (auth/logout) already returnsErrSilent. It does double-log withauth login, which is pre-existing and out of scope here.component/addandcomponent/updatesetSilenceErrors: trueand never returnedErrSilent, so their errors were previously swallowed entirely. Centralizing fixed a second silent-failure case:dr component addoutside a repo root now reportsYou 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>
Code OwnershipCli Maintainers
Review requested from the teams above. Labels will be removed automatically upon approval. |
|
🚀 Smoke tests triggered! Running on Linux and Windows... |
|
✅ All smoke tests passed! ✅ Linux: success |
chasdr
left a comment
There was a problem hiding this comment.
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?
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>


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.
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.
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.
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-testor/trigger-test-smoke- Run smoke tests/trigger-install-testor/trigger-test-install- Run installation testsLabels: Apply labels to trigger workflows:
run-smoke-testsorgo- Run smoke tests on demand (only works for non-forked PRs)Important
For Forked PRs: The
run-smoke-testslabel won't work. A required Smoke Tests check will block merge until a maintainer acts:/approve-smoke-teststo run smoke tests (results will set the check)/skip-smoke-teststo bypass the check without running testsPlease 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.exewith a pinnedPSModulePath(helpers ininternal/tls/psmodulepath.go) soMicrosoft.PowerShell.Securityand 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, newcmd.ReportError(respectscli.ErrSilent), andmainprints failures before exit.ExecuteContextreturns errors unwrapped so users do not see an extraexecute root command:prefix. Tests lock inReportErrorbehavior andSilenceErrorson the root command.Reviewed by Cursor Bugbot for commit 8ea508f. Configure here.