CLI: read data-connection / auth configs from a file or stdin so secrets stay off the command line #137

Closed
opened 2026-11-13 00:37:06 -05:00 by dohertj2 · 1 comment
Owner

Found during the windev gateway key rotation (2026-11-13).

Problem. data-connection create|update --primary-config '<json>' (and the backup config option) take the whole typed endpoint config on the command line, secrets included: the MxGateway apiKey, and any OPC UA password. While the command runs, anyone who can list processes on that machine can read the secret, and it can land in shell history. docker/seed-sites.sh and docker-env2/seed-sites.sh pass it this way too.

Ask.

  • Let the CLI read a config value from a file or stdin, e.g. --primary-config @path / --primary-config -, or separate --primary-config-file options. Do the same for any other CLI option that carries a secret (--auth-config on external systems, passwords).
  • Switch both seed scripts to the file/stdin form.
  • Document it in the CLI docs. Keep the inline form working for non-secret use.
Found during the windev gateway key rotation (2026-11-13). **Problem.** `data-connection create|update --primary-config '<json>'` (and the backup config option) take the whole typed endpoint config on the command line, secrets included: the MxGateway `apiKey`, and any OPC UA password. While the command runs, anyone who can list processes on that machine can read the secret, and it can land in shell history. `docker/seed-sites.sh` and `docker-env2/seed-sites.sh` pass it this way too. **Ask.** - Let the CLI read a config value from a file or stdin, e.g. `--primary-config @path` / `--primary-config -`, or separate `--primary-config-file` options. Do the same for any other CLI option that carries a secret (`--auth-config` on external systems, passwords). - Switch both seed scripts to the file/stdin form. - Document it in the CLI docs. Keep the inline form working for non-secret use.
dohertj2 added the type:featurepriority:p3 labels 2026-11-13 00:37:06 -05:00
Author
Owner

Done in e2f433e8 on main.

Convention: each secret-bearing option has a --<name>-file <path|-> sibling; - reads stdin (a file named - is ./-). This follows the CLI's existing --config-file / --file options. @path is not used because System.CommandLine treats @ tokens as response files and expands them first. The inline forms still work. Giving both forms of one value is a parse error, at most one option can read stdin, - on a terminal stdin is refused rather than blocking, and one trailing line break is stripped. Values are read at parse time. A missing, unreadable or empty source is a parse error that names only the option and the path, never the content.

Options covered: --password-file (global), --primary-config-file / --backup-config-file (data-connection create|update), --auth-config-file (external-system create|update), --connection-string-file (db-connection create|update; one of the two forms is required), --credentials-file (notification smtp create|update), --auth-token-file (notification sms create|update).

Scripts: docker/seed-sites.sh and docker-env2/seed-sites.sh write the MxGateway config to a mode-600 mktemp file (built with the printf builtin, removed by an EXIT trap) and pass --primary-config-file. docker/seed-flow-outbound-demo.sh pipes its rig connection string through --connection-string-file -. The env2 script was edited but not run (soak).

Docs: CLI README (new Secrets from a file or stdin section + per-command tables), docs/requirements/Component-CLI.md, docker/README.md, docker-env2/README.md.

Tests: ValueSourceOptionsTests (18 tests): file, stdin, inline, inline+file mixed across options, trailing-newline strip, missing file, directory, empty file, terminal stdin, inline+file conflict (error has no content), two stdin readers, --password+--password-file, required connection string, and full stdout/stderr capture on a parse error and on a send failure, both with no content. Mutation-checked: 6 mutations (file value ignored, content in the conflict error, no newline strip, no terminal check, no stdin guard, no required check), each caught. CLI suite 611/611, Release build 0 warnings. Smoke on the primary rig Central :9000: data-connection create with --primary-config-file and --password-file, update with --backup-config-file -, stored values round-tripped, missing file gave exit 1. The throwaway OpcUa connection was then deleted. No live connection's key was touched.

Done in `e2f433e8` on `main`. **Convention:** each secret-bearing option has a `--<name>-file <path|->` sibling; `-` reads stdin (a file named `-` is `./-`). This follows the CLI's existing `--config-file` / `--file` options. `@path` is not used because System.CommandLine treats `@` tokens as response files and expands them first. The inline forms still work. Giving both forms of one value is a parse error, at most one option can read stdin, `-` on a terminal stdin is refused rather than blocking, and one trailing line break is stripped. Values are read at parse time. A missing, unreadable or empty source is a parse error that names only the option and the path, never the content. **Options covered:** `--password-file` (global), `--primary-config-file` / `--backup-config-file` (`data-connection create|update`), `--auth-config-file` (`external-system create|update`), `--connection-string-file` (`db-connection create|update`; one of the two forms is required), `--credentials-file` (`notification smtp create|update`), `--auth-token-file` (`notification sms create|update`). **Scripts:** `docker/seed-sites.sh` and `docker-env2/seed-sites.sh` write the MxGateway config to a mode-600 `mktemp` file (built with the `printf` builtin, removed by an EXIT trap) and pass `--primary-config-file`. `docker/seed-flow-outbound-demo.sh` pipes its rig connection string through `--connection-string-file -`. The env2 script was edited but not run (soak). **Docs:** CLI README (new *Secrets from a file or stdin* section + per-command tables), `docs/requirements/Component-CLI.md`, `docker/README.md`, `docker-env2/README.md`. **Tests:** `ValueSourceOptionsTests` (18 tests): file, stdin, inline, inline+file mixed across options, trailing-newline strip, missing file, directory, empty file, terminal stdin, inline+file conflict (error has no content), two stdin readers, `--password`+`--password-file`, required connection string, and full stdout/stderr capture on a parse error and on a send failure, both with no content. Mutation-checked: 6 mutations (file value ignored, content in the conflict error, no newline strip, no terminal check, no stdin guard, no required check), each caught. CLI suite 611/611, Release build 0 warnings. Smoke on the primary rig Central :9000: `data-connection create` with `--primary-config-file` and `--password-file`, `update` with `--backup-config-file -`, stored values round-tripped, missing file gave exit 1. The throwaway OpcUa connection was then deleted. No live connection's key was touched.
Sign in to join this conversation.