fix(CLI-45): standardize the CLI credential env var and fail fast on empty passwords

All five client CLIs now share one credential contract for `authenticate-user`:
flags `--password` / `--password-env` (Go: `-password` / `-password-env`) with
default env `MXGATEWAY_VERIFY_PASSWORD`, resolution flag-then-env, and a resolved
credential that is missing *or empty* is a usage error naming the flag and the
variable. The value is never echoed and never reaches the wire.

Go and Java previously sent an empty credential when the variable was unset,
turning a misconfigured environment into a real MXAccess authentication attempt.
Go now returns the guard error before dialing; Java throws a picocli
ParameterException instead of falling back to "". Python's `--password-env`
gained the canonical default and its UsageError names the resolved variable.
Rust treats an empty flag or env value as missing, with the resolution extracted
into a testable `resolve_verify_user_password`. .NET adopts the canonical flags
and keeps `--verify-user-password`, `--verify-user-password-env`, and
MXGATEWAY_VERIFY_USER_PASSWORD as deprecated aliases for one release.

Docs same commit: CrossLanguageSmokeMatrix.md gains the credential contract and
the per-CLI subcommand-coverage table (the documented-not-fixed half of the
finding); all five READMEs name the canonical variable and the fail-fast rule,
and the .NET README carries the deprecation note. Tracking flipped to Done in
both remediation registers with a change-log row.

No .proto changed; no generated code regenerated.
This commit is contained in:
Joseph Doherty
2026-08-07 06:03:24 -04:00
parent cf66ebbcfb
commit 37cb3b0df8
17 changed files with 764 additions and 53 deletions
+56
View File
@@ -90,6 +90,37 @@ The shared inputs are:
The commands in the matrix use `MXGATEWAY_API_KEY` through each CLI's
`api-key-env` flag. They must not embed bearer tokens or raw API keys.
### Credential contract for `authenticate-user`
Every CLI resolves the MXAccess verify-user credential the same way, so one
exported variable drives the same operator workflow in all five languages:
| Variable | Default | Purpose |
|----------|---------|---------|
| `MXGATEWAY_VERIFY_PASSWORD` | Empty | Verify-user credential read by `authenticate-user` when `--password` is omitted. |
- Flags are `--password` (Go: `-password`) for an explicit value and
`--password-env` (Go: `-password-env`) for the *name* of the environment
variable, defaulting to `MXGATEWAY_VERIFY_PASSWORD`.
- Resolution order is flag, then environment variable. Prefer the variable: the
flag puts the secret in shell history and the process table.
- A resolved credential that is **missing or empty** is a usage error. The CLI
fails fast before dialing rather than authenticating with an empty password,
and the error names only the flag and the variable — never the value. Nothing
echoes the credential to stdout, stderr, or logs.
This is CLI argument validation, not an MXAccess parity exception: the client
*libraries* still transmit whatever credential they are given. Only the operator
tools refuse to fabricate an empty one.
The .NET CLI accepted `--verify-user-password`, `--verify-user-password-env`, and
`MXGATEWAY_VERIFY_USER_PASSWORD` before this contract was unified. Those names
remain as deprecated aliases for one release; new scripts must use the canonical
names above. The full .NET resolution order is `--password`,
`--verify-user-password`, the variable named by `--password-env` (or the
deprecated `--verify-user-password-env`, default `MXGATEWAY_VERIFY_PASSWORD`),
then `MXGATEWAY_VERIFY_USER_PASSWORD`.
### TLS variant
The matrix runs over plaintext (`h2c`) by default. A TLS variant exists but stays
@@ -132,6 +163,31 @@ for quick local checks, but the full cross-language matrix uses explicit
operation commands because not every bundled smoke command streams events yet.
The explicit sequence remains the parity baseline for issue-level validation.
## Per-CLI Subcommand Coverage
The matrix sequence itself is available everywhere, but the later single-item
session commands were not added to every CLI at the same time. A runner that
reaches beyond the required sequence must branch on language, so the current
deltas are specified here rather than left to be discovered:
| Subcommand | .NET | Rust | Go | Python | Java |
|------------|------|------|----|--------|------|
| `unregister` | yes | yes | no | no | no |
| `add-buffered-item` | yes | no | no | no | no |
| `set-buffered-update-interval` | yes | no | no | no | no |
| `suspend` | yes | no | no | no | no |
| `activate` | yes | no | no | no | no |
| `write-secured` | yes | yes | yes | yes | yes |
| `write-secured2` | yes | no | no | no | no |
| `authenticate-user` | yes | yes | yes | yes | yes |
| `archestra-user-to-id` | yes | no | no | no | no |
Only .NET exposes all nine. Rust adds `unregister` and the credential pair; Go,
Python, and Java expose the credential pair only. Every gap is CLI surface only —
all five *libraries* implement all nine typed helpers, so a gap is a missing
operator command, never a missing capability. Levelling the CLIs is separate
feature work and is not tracked as a defect here.
## Validation
Run the matrix shape tests after changing the smoke matrix: