Skip to content

fix(cli): trust system and explicit CAs, explain login failures, fix custom installer paths (0.38.2) - #1716

Merged
chronoai-kai merged 5 commits into
mainfrom
implement-1708-1710
Sep 30, 2026
Merged

chronoai-kai merged 5 commits into
mainfrom
implement-1708-1710

Conversation

@chronoai-kai

@chronoai-kai chronoai-kai commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What

Fixes three CLI problems reported from a Linux cloud workspace behind an HTTPS proxy with a custom trusted CA (0.38.2).

Closes #1708, closes #1709, closes #1710.

#1708 — the CLI now trusts system and explicit CAs

Cause. The CLI's HTTP client used reqwest's rustls-tls feature, i.e. only the bundled Mozilla roots. It ignored SSL_CERT_FILE, SSL_CERT_DIR and the OS store. The node and nyxid ssh WebSocket paths used the OS store only, so the two halves of the CLI trusted different sets.

Fix. cli/src/tls.rs builds one process-wide trust set, used by every HTTP and WSS client in the CLI and node agent:

  • Bundled Mozilla roots: always on, so minimal containers without a CA bundle keep working.
  • SSL_CERT_FILE / SSL_CERT_DIR: used when set.
  • The OS store: used when neither is set, including keychain/MDM roots on macOS. It is loaded once and only when a certificate does not chain to the roots above. That keeps the ~110–200 ms macOS keychain load off every command; a fast-failing command goes from 0.13 s to 0.01 s.
  • NYXID_CA_CERT: a new PEM file of extra trusted CAs, always added on top.

Verification is never weakened. Hostname checks still apply with a trusted CA. There is no bypass switch.

Failure behaviour.

  • An explicitly configured CA source that is broken is a hard error naming the variable and path.
  • An empty variable counts as unset.

Where the settings apply.

  • Sigstore TUF download for update attestation: uses the same trust through a tough::Transport. TUF signature, expiry, hash and length checks are unchanged. The embedded bootstrap root is byte-identical to sigstore 0.13.0's.
  • Background services: nyxid node daemon install, nyxid update auto enable and nyxid node docker start/restart carry the CA paths. Docker gets read-only bind mounts plus container-local variables. Proxy variables are never persisted.
  • Node agent startup: nyxid node start fails fast on a broken CA setting instead of reconnecting forever.
  • Help: nyxid --help and nyxid login --help list the variables.

#1709 — login_unavailable says why

Unchanged. Error codes, messages and exit statuses are exactly as before; pending, denied, expired, rate-limited and similar outcomes produce byte-identical JSON.

Added. login_unavailable and login_storage_failed gain an additive error.diagnostic:

  • stage: config, connect, proxy, tls, request, response, validation or storage.
  • timeout: connect or request, when a timeout occurred.
  • http_status / server_error_code: the HTTP status, plus the server's numeric error code only.
  • endpoint: the URL without userinfo, query or fragment.
  • causes: de-duplicated and bounded.
  • hint: depends on the failure. For TLS, an unknown issuer, a hostname mismatch and an expired certificate each get their own hint.

Redaction. Tokens, codes, polling secrets, Authorization headers, proxy credentials and query strings are removed, and tests cover this.

Where else the classifier is used.

  • Generic command errors: the same classifier adds stage and hint lines.
  • nyxid doctor Network / TLS section: trust sources and counts, NYXID_CA_CERT status, proxy variables shown without credentials, and NO_PROXY patterns.
  • nyxid doctor NyxID API section: checks GET {base}/health using --base-url, else the --profile saved URL, else the default.

#1710 — install.sh with a custom NYXID_ACTIVE_SYMLINK

Cause. The cargo-dist installer always writes to $HOME/.local/bin, but the wrapper only looked at $NYXID_ACTIVE_SYMLINK. With a custom path it misread a successful install as "no prebuilt" and started rustup and cargo.

Fix.

  • Staging: the wrapper runs the real cargo-dist 0.30.0 installer into private staging (NYXID_CLI_UNMANAGED_INSTALL). It clears the NYXID_CLI_INSTALL_DIR / CARGO_DIST_FORCE_INSTALL_DIR overrides, which would otherwise take precedence.
  • Install: it checks the staged --version, installs into NYXID_INSTALL_ROOT/vX.Y.Z and atomically links NYXID_ACTIVE_SYMLINK.
  • PATH and final check: PATH is configured for the directory that actually holds the symlink, and the final check runs that path.
  • Source fallback: it runs only when no prebuilt installer could be downloaded or run, and ends in the same layout. A bad staged binary or a failed relocation is a hard error.
  • Default layout: unchanged.
  • CI: skills/nyxid/scripts/** now triggers the CLI tests.

Docs

  • New guide: docs/site/cli/guides/network.md (Network, proxies and TLS), linked from the docs nav.
  • Updated: install and authenticate getting-started pages, the credential-node guide, the node reference, docs/RELEASING.md, and the skill references (device-login.md, nodes.md).

Verification

  • Local:
    • cargo fmt --check and cargo clippy --workspace --all-targets -D warnings pass.
    • cargo test -p nyxid-cli passes with no failures and no ignored tests. 82 test names were added and none removed.
    • The three KMS feature builds pass, and so do the docs manifest tests.
  • Real TLS, proxy and installer tests:
    • TLS: private-CA servers built with rcgen and tokio-rustls cover default rejection, acceptance with NYXID_CA_CERT or SSL_CERT_FILE, rejection on hostname mismatch and of a self-signed leaf, and the lazy OS-store fallback running exactly once.
    • Proxy: CONNECT proxies cover success, 403, 407 and connection refused, all without leaking credentials.
    • WSS: the WebSocket handshake uses the same trust as HTTP.
    • Installer: a hermetic harness with stub curl, uname, cargo and rustup covers default, custom-HOME, custom-root, custom-symlink, both-custom and XDG layouts, the no-fallback failure cases and reruns.
  • Manual, on the built binary:
    • Against an openssl s_server private CA: untrusted gives stage tls with the CA hint; a wrong host gives the hostname hint; trusted passes TLS.
    • Proxy refused, 403 and 407 all give stage proxy, with credentials redacted.
    • nyxid doctor reports that https://nyx-api.chrono-ai.fun/health returned 200.
    • A sandboxed nyxid update --version 0.37.1 verified the release attestation through the new TUF transport against the live Sigstore CDN.

chrono-kw added 3 commits September 30, 2026 23:59
… custom installer paths

- #1708: one process-wide TLS trust (bundled Mozilla + lazily loaded OS store
  honouring SSL_CERT_FILE/SSL_CERT_DIR + additive NYXID_CA_CERT) for every
  HTTP and WSS client, Sigstore TUF downloads included; broken explicit CA
  configuration fails closed; node daemon, auto-update scheduler and node
  docker forward CA paths (never proxy variables).
- #1709: login keeps its codes, messages and exit statuses and adds a
  sanitized error.diagnostic (stage, timeout, status, endpoint, causes, hint)
  for login_unavailable/storage; doctor gains Network/TLS and NyxID API
  (/health, --base-url/--profile) sections with the same classifier.
- #1710: install.sh stages the cargo-dist installer privately, installs into
  the versioned root and atomically links NYXID_ACTIVE_SYMLINK; Rust source
  fallback only when no prebuilt installer could be obtained or run.
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

📊 Code coverage

Component Lines Threshold Status Δ vs base
Backend (nyxid) 87.44% 73% ✅ — 0.00
CLI (nyxid-cli) 72.45% 64% ✅ 🔺 +0.83
Frontend (vitest) 71.00% 15% ✅ 🔻 -0.01

Gate: line coverage must stay at or above the threshold. Ratchet plan (W21): Backend → 55%, CLI → 50%, Frontend → 30% by quarter end.

@chronoai-kai chronoai-kai changed the title fix(cli): trust system and explicit CAs, explain login failures, fix custom installer paths (0.38.1) fix(cli): trust system and explicit CAs, explain login failures, fix custom installer paths (0.38.2) Sep 30, 2026
@chronoai-kai
chronoai-kai merged commit 14d76f4 into main Sep 30, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant