Skip to content

fix(api): clean dynamic API HTTP and connection errors#45

Open
arseniy-pplx wants to merge 1 commit into
TangleML:masterfrom
arseniy-pplx:transfer/clean-dynamic-api-errors
Open

fix(api): clean dynamic API HTTP and connection errors#45
arseniy-pplx wants to merge 1 commit into
TangleML:masterfrom
arseniy-pplx:transfer/clean-dynamic-api-errors

Conversation

@arseniy-pplx

@arseniy-pplx arseniy-pplx commented Jul 20, 2026

Copy link
Copy Markdown

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 errors render as 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, timeout, proxy, and TLS errors render an actionable reason (e.g. connection failed: [Errno 111] Connection refused, read timed out, TLS error: ...).
  • URL sanitization strips 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/...).
  • Free-form exception text is scrubbed for embedded URLs and bare user:pass@host userinfo before display, so a crafted or third-party proxy/connect error string cannot leak credentials — while benign diagnostics (errno, TLS reason) are preserved.
  • Exit codes are a non-zero string exit (1) rather than the raw HTTP status, which was previously truncated to 8 bits (404 → 148, 512 → 0 false success).
  • Schema refresh/fetch failures reuse the same formatting and keep omitting the /openapi.json response 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 raw httpx exceptions. Changes are confined to api_transport.py and api_cli.py; the existing actionable cache-missing behavior and API command exit semantics are unchanged.

Context

Dynamic tangle api calls 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 preserved
  • uv 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 change
  • uvx ruff check on the touched files
  • uv lock --check; uv build
  • git diff --check
  • dynamic CLI smoke against a refused port with URL credentials renders Failed to reach GET http://<redacted>@127.0.0.1:1/api/pipeline_runs/: connection failed: [Errno 111] Connection refused, exit 1

body = exc.response.text
except Exception: # pragma: no cover - defensive: streamed/undecodable body
body = ""
detail = _bounded_detail(body)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(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 Volv-G left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(AI-assisted)

Approved per reviewer direction. The inline backend-detail redaction feedback remains available for follow-up.

@arseniy-pplx
arseniy-pplx force-pushed the transfer/clean-dynamic-api-errors branch from 8c7ac76 to ff9839d Compare July 24, 2026 11:00
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.
@arseniy-pplx
arseniy-pplx force-pushed the transfer/clean-dynamic-api-errors branch from ff9839d to ddb38d8 Compare July 24, 2026 11:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants