diff --git a/CHANGELOG.md b/CHANGELOG.md index e91923a..357de3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ and versions are tracked in the repo-root `VERSION` file. alpha, beta, release-candidate, and GA identifiers while locking publication until the verified-artifact and pre-GA release-candidate gates are complete. +### Security + +- Added explicit sensitive-command diagnostics for the standard command runner + and GitHub helpers, allowing callers to publish a safe operation label while + keeping protected argument values out of dry-run, retry, timeout, and final + failure records. + ### Fixed - Hardened file-section processing, allowed-dirty-path checks, temporary diff --git a/lib/bash/gh/README.md b/lib/bash/gh/README.md index 52c50d6..7afb4f4 100644 --- a/lib/bash/gh/README.md +++ b/lib/bash/gh/README.md @@ -21,12 +21,13 @@ import "/path/to/base-bash-libs/lib/bash/gh/lib_gh.sh" lines from the GitHub CLI and then logs a caller-provided login hint, or the default `gh auth login -h github.com` hint. - `gh_report_command_failure [gh args...]` - Logs a failed `gh` command and appends auth diagnostics. The original status + Logs a failed `gh` command and appends auth diagnostics. Protected reporting + uses the control-first sensitive form documented below. The original status is returned. - `gh_run [gh args...]` Runs `gh "$@"` after command availability checks. On command failure, it reports the failed command and auth diagnostics while preserving the original - exit status. + exit status. Protected calls use the sensitive form documented below. - `gh_repo_from_remote_url ` Parses supported GitHub SSH and HTTPS remote URLs into `owner/repo`. Returns non-zero for non-GitHub or malformed remotes and leaves the result variable @@ -42,7 +43,8 @@ import "/path/to/base-bash-libs/lib/bash/gh/lib_gh.sh" errors such as secondary rate limits, `Retry-After`, abuse detection, and 502/503/504-style failures. `BASE_GH_API_MAX_ATTEMPTS` defaults to `2`. `BASE_GH_API_RETRY_DELAY_SECONDS` defaults to `2` when the error output does - not include a `Retry-After` value. + not include a `Retry-After` value. Protected calls use the sensitive form + documented below. All GitHub helper failures return a nonzero status and preserve the underlying `gh` status where applicable. The remote parser and origin inference helpers @@ -53,13 +55,60 @@ Public functions validate the documented argument count before expanding required positional parameters. Invalid calls return `1`, including when the caller has enabled `nounset`; optional flags such as `--optional` are rejected when misspelled. The variadic `gh_run` and `gh_api_with_retry` helpers continue -to pass all arguments through to `gh` unchanged. +to pass GitHub arguments after any protected-diagnostic control prefix through +to `gh` unchanged. The library does not change the caller's `errexit`, `nounset`, `pipefail`, `shopt`, `IFS`, `OPTIND`, cwd, umask, traps, or positional parameters. Its diagnostic parsing uses a command-scoped empty `IFS`, and failed `gh` commands retain their original status from `1` through `255`. +## Secret-safe command diagnostics + +Ordinary `gh_run` and `gh_report_command_failure` failures render every GitHub +argument with Bash `%q`. This preserves argument boundaries and produces a +copyable diagnostic, but it is not secret-safe. Headers, fields, URL userinfo, +positional values, and `--option=value` forms are all rendered as supplied. + +Use `--sensitive` whenever any GitHub argument may contain a credential or +other value that must not enter terminal or persistent logs: + +```bash +gh_run --sensitive --safe-display "create release" -- \ + release create "$tag" --notes "$private_notes" + +gh_api_with_retry --sensitive --safe-display "update project item" -- \ + graphql --header "Authorization: Bearer $token" \ + --raw-field "query=$query" + +gh_report_command_failure --sensitive --safe-display "publish release" -- \ + "$status" release create "$tag" --notes "$private_notes" +``` + +A protected call requires the explicit `--` separator. `--safe-display` is +valid only with `--sensitive`; its value must be a non-empty printable ASCII +label that does not begin with `-` and that the caller has already determined +is safe to log. The label appears as, for example, `create release [sensitive +GitHub operation; arguments hidden]`. Without a label the helpers use only the +generic bracketed description. + +Protected diagnostics never render the GitHub argv. This applies to final +failure records, retry notices, persistent logs, and the nested authentication +check performed by `gh_run` and `gh_report_command_failure`. A protected +`gh_api_with_retry` may inspect captured failure text internally to decide +whether and when to retry, but it does not replay that text on failure. +Successful API output remains functional stdout and is returned unchanged. + +Sensitivity is explicit rather than heuristic. The helpers do not try to infer +which `--header`, `--field`, `--raw-field`, `--option=value`, URL, extension, +alias, or positional argument contains a secret. They also do not sanitize +output emitted by the executed command, whether that command is a shell +function, builtin, or external subprocess. Caller-enabled shell tracing such +as `set -x`, operating-system process listings, and an unsafe label supplied +through `--safe-display` are also outside this guarantee. Callers remain +responsible for those channels and should prefer non-argv credential +mechanisms whenever the invoked tool supports them. + ## Boundary This library is intentionally generic. It does not know about Base branch diff --git a/lib/bash/gh/lib_gh.sh b/lib/bash/gh/lib_gh.sh index a5603cc..f216a67 100644 --- a/lib/bash/gh/lib_gh.sh +++ b/lib/bash/gh/lib_gh.sh @@ -28,6 +28,129 @@ gh_require_cli() { } } +__gh_sensitive_controls_usage__() { + local __gh_controls_helper_name="${1-}" + + case "$__gh_controls_helper_name" in + gh_report_command_failure) + log_error -l base_bash_libs.gh \ + "Usage: gh_report_command_failure [gh args...] or gh_report_command_failure --sensitive [--safe-display