← back to Cli Printing Press
fix(cli): redact shipcheck api key in json (#1673)
4093ef3eef08ad8a88b6ebf16b42ce817a16e113 · 2026-05-19 15:28:19 -0700 · Trevin Chow
* fix(cli): redact shipcheck api key in json
* fix(cli): address shipcheck redaction review
Files touched
M internal/cli/shipcheck.goM internal/cli/shipcheck_test.go
Diff
commit 4093ef3eef08ad8a88b6ebf16b42ce817a16e113
Author: Trevin Chow <trevin@trevinchow.com>
Date: Tue May 19 15:28:19 2026 -0700
fix(cli): redact shipcheck api key in json (#1673)
* fix(cli): redact shipcheck api key in json
* fix(cli): address shipcheck redaction review
---
internal/cli/shipcheck.go | 23 ++++++++++++++++++++---
internal/cli/shipcheck_test.go | 37 +++++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/internal/cli/shipcheck.go b/internal/cli/shipcheck.go
index 2dc5fb0c..3c5a577b 100644
--- a/internal/cli/shipcheck.go
+++ b/internal/cli/shipcheck.go
@@ -318,8 +318,7 @@ type shipcheckJSONEnvelope struct {
}
// renderShipcheckJSON marshals the envelope to w. Each leg's `command`
-// field shows the full argv as it would be invoked at the shell so an
-// operator can copy-paste-rerun a specific leg from the JSON output.
+// field shows the argv used for that leg with sensitive flag values redacted.
func renderShipcheckJSON(w *os.File, binPath string, results []shipcheckLegResult, runStartedAt time.Time, runElapsed time.Duration) error {
env := shipcheckJSONEnvelope{
Passed: shipcheckUmbrellaCode(results) == 0,
@@ -335,7 +334,7 @@ func renderShipcheckJSON(w *os.File, binPath string, results []shipcheckLegResul
Passed: r.Passed(),
StartedAt: r.StartedAt.UTC().Format(time.RFC3339),
ElapsedMS: r.Elapsed.Milliseconds(),
- Command: strings.Join(append([]string{binPath}, r.Argv...), " "),
+ Command: renderShipcheckCommand(binPath, r.Argv),
})
}
enc := json.NewEncoder(w)
@@ -343,6 +342,24 @@ func renderShipcheckJSON(w *os.File, binPath string, results []shipcheckLegResul
return enc.Encode(env)
}
+func renderShipcheckCommand(binPath string, argv []string) string {
+ args := append([]string{binPath}, redactShipcheckCommandArgv(argv)...)
+ return strings.Join(args, " ")
+}
+
+func redactShipcheckCommandArgv(argv []string) []string {
+ redacted := make([]string, len(argv))
+ copy(redacted, argv)
+ for i, arg := range redacted {
+ if arg == "--api-key" {
+ if i+1 < len(redacted) {
+ redacted[i+1] = "<redacted>"
+ }
+ }
+ }
+ return redacted
+}
+
// validateShipcheckDir confirms --dir points at something that looks
// like a built printing-press CLI: a directory containing go.mod and
// either an internal/cli/ tree or a cmd/<name>-pp-cli/ tree. We are
diff --git a/internal/cli/shipcheck_test.go b/internal/cli/shipcheck_test.go
index ab958b19..db13d6f3 100644
--- a/internal/cli/shipcheck_test.go
+++ b/internal/cli/shipcheck_test.go
@@ -474,6 +474,43 @@ func TestShipcheck_JSONEnvelope_AllPass(t *testing.T) {
}
}
+func TestShipcheck_JSONEnvelope_RedactsAPIKeyFromCommands(t *testing.T) {
+ h := newShipcheckHarness(t)
+ const secret = "secret-token"
+
+ out := captureStdout(t, func() {
+ if err := runShipcheckCmd(t, "--dir", h.dir, "--json", "--api-key", secret); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ })
+
+ var env shipcheckJSONEnvelope
+ if err := json.Unmarshal([]byte(extractFinalJSONObject(t, out)), &env); err != nil {
+ t.Fatalf("envelope is not valid JSON: %v", err)
+ }
+
+ var verifyCommand string
+ for _, leg := range env.Legs {
+ if strings.Contains(leg.Command, secret) {
+ t.Fatalf("leg %s command leaked API key: %q", leg.Name, leg.Command)
+ }
+ if leg.Name == "verify" {
+ verifyCommand = leg.Command
+ }
+ }
+ if verifyCommand == "" {
+ t.Fatal("envelope missing verify command")
+ }
+ if !strings.Contains(verifyCommand, "--api-key <redacted>") {
+ t.Fatalf("verify command should include redacted API key flag; got %q", verifyCommand)
+ }
+
+ verifyArgs := findInvocation(readStubLog(t, h.logFile), "verify")
+ if !argvHas(verifyArgs, secret) {
+ t.Fatalf("verify subprocess argv should still receive the raw API key; got %v", verifyArgs)
+ }
+}
+
// TestShipcheck_JSONEnvelope_OneFailure: --json envelope reflects a
// failing leg with passed=false at the leg and envelope level.
func TestShipcheck_JSONEnvelope_OneFailure(t *testing.T) {
← d8178077 fix(cli): handle binary-only response endpoints (Accept + ba
·
back to Cli Printing Press
·
fix(cli): return failure exit from verify json (#1693) 5261ae73 →