From 025d5f6e8d6a9d126f094ba4d15e2c9bee8ea6fa Mon Sep 17 00:00:00 2001 From: Claude Agent Date: Fri, 6 Nov 2026 14:28:34 +0000 Subject: [PATCH] fix: redact config-to-env output from pod logs on config load failure Co-Authored-By: Claude --- scripts/docker-entrypoint.sh | 25 +++++++++++++++++--- tests/test-entrypoint-config-load-failure.sh | 21 +++++++++++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/scripts/docker-entrypoint.sh b/scripts/docker-entrypoint.sh index d6f8900..cc40bcb 100644 --- a/scripts/docker-entrypoint.sh +++ b/scripts/docker-entrypoint.sh @@ -205,6 +205,21 @@ if [ -n "${SUB_PATH:-}" ]; then fi # Load environment variables from config service if configured +# redact_config_log: scrub credentials from text before it is written to the +# pod log. Literal values of the runner's own tokens are replaced first, then +# well-known token shapes (GitHub tokens, JWTs, Basic auth). +redact_config_log() { + local s="$1" v + for v in "${OSC_ACCESS_TOKEN:-}" "${CONFIG_API_KEY:-}" "${GIT_TOKEN:-}" "${GITHUB_TOKEN:-}"; do + if [ -n "$v" ]; then + s="${s//"$v"/[REDACTED]}" + fi + done + printf '%s' "$s" | sed -E \ + -e 's/gh[pso]_[A-Za-z0-9]{20,}/[REDACTED]/g' \ + -e 's/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*/[REDACTED]/g' \ + -e 's/([Bb]asic )[A-Za-z0-9+\/=]{8,}/\1[REDACTED]/g' +} if [ -n "${OSC_ACCESS_TOKEN:-}" ] && [ -n "${CONFIG_SVC:-}" ]; then # Derive OSC environment from OSC_MCP_URL if OSC_ENV is not set explicitly if [[ -z "${OSC_ENV:-}" && -n "${OSC_MCP_URL:-}" ]]; then @@ -242,9 +257,9 @@ if [ -n "${OSC_ACCESS_TOKEN:-}" ] && [ -n "${CONFIG_SVC:-}" ]; then if [ $config_exit -eq 124 ]; then echo "[CONFIG] ERROR: Timed out loading config from config service '$CONFIG_SVC'" >&2 else - echo "[CONFIG] ERROR: Failed to load config (exit $config_exit): $config_env_output $config_err_output" >&2 + echo "[CONFIG] ERROR: Failed to load config (exit $config_exit): $(redact_config_log "$config_err_output")" >&2 fi - if echo "$config_env_output $config_err_output" | grep -qiE '401|403|unauthori[sz]ed|forbidden|expired'; then + if echo "$config_err_output" | grep -qiE '401|403|unauthori[sz]ed|forbidden|expired'; then echo "[CONFIG] Hint: the access token may have expired. Run refresh-app-config to renew it." >&2 fi elif [ -z "$(echo "$config_env_output" | tr -d '[:space:]')" ]; then @@ -258,7 +273,11 @@ if [ -n "${OSC_ACCESS_TOKEN:-}" ] && [ -n "${CONFIG_SVC:-}" ]; then echo "[CONFIG] Loaded $var_count environment variable(s)" else config_failed=1 - echo "[CONFIG] ERROR: Config service '$CONFIG_SVC' returned output with no valid export lines: $config_env_output" >&2 + # Never print the output itself: it holds decrypted parameter values. + # Names only (tenant-chosen keys), enough to see which key is invalid. + rejected_lines=$(echo "$config_env_output" | grep -c "^export " || true) + rejected_names=$(echo "$config_env_output" | sed -n 's/^export \([^= ]*\)=.*/\1/p' | head -n 10 | cut -c1-64 | tr '\n' ' ') + echo "[CONFIG] ERROR: Config service '$CONFIG_SVC' returned output with no valid export lines ($rejected_lines export line(s) rejected, names: ${rejected_names:-none}; values not logged)" >&2 fi fi if [ $config_failed -ne 0 ]; then diff --git a/tests/test-entrypoint-config-load-failure.sh b/tests/test-entrypoint-config-load-failure.sh index bb55a5c..bb0ddbc 100755 --- a/tests/test-entrypoint-config-load-failure.sh +++ b/tests/test-entrypoint-config-load-failure.sh @@ -38,6 +38,12 @@ case "$STUB_MODE" in garbled) echo "npm notice something"; echo "export my-key=1"; exit 0 ;; stderr_only) echo "npm warn noise" >&2; exit 0 ;; ok) echo "export FOO=bar"; echo "export BAZ=qux"; exit 0 ;; + leak_fail) echo "export DB_PASS='S3CR3T-canary'"; echo "boom 401 unauthorized" >&2; exit 2 ;; + leak_invalid_key) echo "export MY-KEY='S3CR3T-canary'"; exit 0 ;; + token_stderr) + echo "auth failed for ${OSC_ACCESS_TOKEN} with ghp_abcdefghijklmnopqrstuvwxyz0123456789 and eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.c2lnbmF0dXJl" >&2 + exit 2 ;; + leak_hint) echo "export X='expired 401'"; echo "boom" >&2; exit 2 ;; esac STUB chmod +x "$sandbox/bin/"* @@ -70,7 +76,20 @@ rc=$(run_block stderr_only "${ENVV[@]}") if [ "$rc" = "0" ] && grep -q "REACHED_PIP" "$sandbox/stdout"; then pass "stderr noise with empty stdout still continues"; else fail "stderr only (rc=$rc)"; fi rc=$(run_block ok "${ENVV[@]}") -if [ "$rc" = "0" ] && grep -q "REACHED_PIP FOO=bar" "$sandbox/stdout" && grep -q "Loaded 2 environment variable" "$sandbox/stdout"; then pass "valid exports are evaluated"; else fail "success path (rc=$rc)"; fi +if [ "$rc" = "0" ] && grep -q "REACHED_PIP FOO=bar" "$sandbox/stdout" && grep -q "Loaded 2 environment variable" "$sandbox/stdout" && ! grep -q qux "$sandbox/stdout" "$sandbox/stderr"; then pass "valid exports are evaluated and values are not logged"; else fail "success path (rc=$rc)"; fi + +# Secret-redaction paths: decrypted stdout must never reach either stream +rc=$(run_block leak_fail "${ENVV[@]}") +if [ "$rc" = "2" ] && grep -q 'Failed to load config (exit 2)' "$sandbox/stderr" && grep -q refresh-app-config "$sandbox/stderr" && ! grep -q 'S3CR3T-canary' "$sandbox/stdout" "$sandbox/stderr"; then pass "failure with secret on stdout does not log it"; else fail "leak_fail (rc=$rc)"; fi + +rc=$(run_block leak_invalid_key "${ENVV[@]}") +if [ "$rc" != "0" ] && ! grep -q REACHED_PIP "$sandbox/stdout" && grep -q 'no valid export lines' "$sandbox/stderr" && grep -q 'MY-KEY' "$sandbox/stderr" && ! grep -q 'S3CR3T-canary' "$sandbox/stdout" "$sandbox/stderr"; then pass "invalid key name logged without its value"; else fail "leak_invalid_key (rc=$rc)"; fi + +rc=$(run_block token_stderr OSC_ACCESS_TOKEN=osc-literal-token-9f3a CONFIG_SVC=store OSAAS_CLI_VERSION=4.35.0) +if [ "$rc" = "2" ] && grep -q '\[REDACTED\]' "$sandbox/stderr" && ! grep -qE 'osc-literal-token-9f3a|ghp_abcdefghijklmnopqrstuvwxyz0123456789|eyJhbGciOiJIUzI1NiJ9' "$sandbox/stdout" "$sandbox/stderr"; then pass "tokens in stderr are redacted"; else fail "token_stderr (rc=$rc)"; fi + +rc=$(run_block leak_hint "${ENVV[@]}") +if [ "$rc" = "2" ] && ! grep -q refresh-app-config "$sandbox/stderr" && ! grep -q 'expired 401' "$sandbox/stdout" "$sandbox/stderr"; then pass "stdout no longer drives the refresh hint"; else fail "leak_hint (rc=$rc)"; fi rc=$(run_block fail OSC_ACCESS_TOKEN=tok) if [ "$rc" = "0" ] && grep -q "REACHED_PIP" "$sandbox/stdout"; then pass "no CONFIG_SVC: block skipped"; else fail "no CONFIG_SVC (rc=$rc)"; fi