fix(api): clean dynamic API HTTP and connection errors#45
Conversation
| body = exc.response.text | ||
| except Exception: # pragma: no cover - defensive: streamed/undecodable body | ||
| body = "" | ||
| detail = _bounded_detail(body) |
There was a problem hiding this comment.
(AI-assisted)
Can we redact backend detail before appending it to the status error?
The URL is sanitized, but this path passes raw response.text into _bounded_detail(). A JSON error such as {"credential":"BODYSECRET"} is therefore emitted verbatim even though this module already has _content_to_text() / _redact_sensitive_values() for structured secret redaction. Backends and proxies can reflect submitted fields in validation or authentication errors, putting tokens/passwords into stderr and CI logs.
Could this run the response body through the existing structured redaction before whitespace normalization and truncation? The schema-fetch path should retain its stricter no-body behavior.
Volv-G
left a comment
There was a problem hiding this comment.
(AI-assisted)
Approved per reviewer direction. The inline backend-detail redaction feedback remains available for follow-up.
8c7ac76 to
ff9839d
Compare
Dynamic `tangle api <group> <op>` calls now fail with a concise, one-line, credential-safe message and a non-zero exit instead of a raw traceback or a truncated HTTP status exit code. - HTTP status errors report `HTTP <status> <reason> for <METHOD> <url>` with a slice of the backend body that is first run through structured secret redaction, then whitespace-normalized and length-bounded, so credentials a backend or proxy reflects into a validation/auth error never reach stderr. - Connection, timeout, proxy, and TLS errors report an actionable reason. - URLs are sanitized: userinfo, credential query params, Authorization, and presigned/SAS-URL signatures are redacted. IPv6 literals are re-bracketed so host and port survive redaction (`[2001:db8::1]:8443`). - Free-form exception text is scrubbed for embedded URLs and `user:pass@host` userinfo before display, so a crafted or third-party proxy/connect error cannot leak credentials while errno/TLS diagnostics are preserved. - Exit codes are now a non-zero string exit (1) rather than the HTTP status, which was truncated to 8 bits (404 -> 148, 512 -> 0). - Schema refresh/fetch failures reuse the same formatting and keep omitting the response body so an auth failure cannot reflect sent credentials. The original exception is preserved as the cause for Python callers, and programmatic clients still receive raw httpx exceptions.
ff9839d to
ddb38d8
Compare
Summary
Dynamic
tangle api <group> <op>calls (and the OpenAPI schema refresh/fetch path) now fail with a concise, one-line, credential-safe message and a non-zero exit code instead of a raw traceback or a truncated HTTP status exit code.HTTP <status> <reason> for <METHOD> <url>with a length-bounded slice of the backend body. The body is first run through the same structured secret redaction used for verbose logging, so a validation/auth error that reflects submitted fields (e.g.credential,token,api_key) cannot leak them to stderr or CI logs; non-JSON bodies are passed through unchanged.connection failed: [Errno 111] Connection refused,read timed out,TLS error: ...).user:pass@userinfo and redacts credential query params,Authorization, and presigned/SAS-URL signatures (AWS SigV4 / GCS / Azure). IPv6 literals are re-bracketed so host and port survive redaction (https://<redacted>@[2001:db8::1]:8443/...).user:pass@hostuserinfo before display, so a crafted or third-party proxy/connect error string cannot leak credentials — while benign diagnostics (errno, TLS reason) are preserved./openapi.jsonresponse body, so an auth failure cannot reflect the credentials just sent.The original exception is preserved as the cause (
raise ... from exc) for Python callers; programmatic clients still receive rawhttpxexceptions. Changes are confined toapi_transport.pyandapi_cli.py; the existing actionable cache-missing behavior and API command exit semantics are unchanged.Context
Dynamic
tangle apicalls previously surfaced backend failures either as a raw traceback or as an HTTP status pushed through the process exit code, where the value was truncated to 8 bits (404 → 148, 512 → 0 false success). This renders every failure as one concise, credential-safe line with a correct non-zero exit code, without changing the success path or the actionable cache-missing behavior.Testing
uv run pytest tests/test_api_transport.py— URL sanitization (userinfo, credential/query params, presigned signatures, IPv6 re-bracketing with userinfo+port), one-line status errors, bounded/normalized detail, structured redaction of reflected secrets in JSON response bodies with non-JSON detail preserved, detail omission, actionable request-error classification, embedded-secret scrubbing for proxy/connect exception text, and benign errno/TLS diagnostics preserveduv run pytest tests/test_api_cli.py— dynamic-operation error paths including end-to-end redaction of reflected response-body secrets; passes except one pre-existing failure (test_official_static_command_without_schema_fails_with_actionable_error) that reproduces identically on the base commit and is unrelated to this changeuvx ruff checkon the touched filesuv lock --check;uv buildgit diff --checkFailed to reach GET http://<redacted>@127.0.0.1:1/api/pipeline_runs/: connection failed: [Errno 111] Connection refused, exit 1