Skip to content

feat(tracing): enrich tracing records with HTTP headers + secure cURL copy in devtools#987

Merged
danh91 merged 7 commits intopatch-2026.1.17from
feat/tracing-http-headers-enrichment
Mar 3, 2026
Merged

feat(tracing): enrich tracing records with HTTP headers + secure cURL copy in devtools#987
danh91 merged 7 commits intopatch-2026.1.17from
feat/tracing-http-headers-enrichment

Conversation

@danh91
Copy link
Copy Markdown
Member

@danh91 danh91 commented Mar 2, 2026

Summary

Carrier API tracing records now capture full HTTP request and response headers. Sensitive values are redacted before storage. The devtools Copy as cURL button now generates a complete, reproducible command with all -H flags.


Problem

Karrio's tracing system logged carrier API request/response bodies but discarded HTTP headers entirely, creating debugging blind spots:

  • Carrier auth failures — could not tell if Authorization was missing or malformed
  • Rate limit diagnosis — X-RateLimit-Remaining, Retry-After headers were lost
  • cURL copy was incomplete — only hardcoded Content-Type, never the actual auth headers

Changes

modules/sdk/karrio/core/utils/redaction.py (new)

Centralised redaction utility:

  • redact_headers() — redacts Authorization (keeps scheme: Bearer val_xxx), X-Api-Key, X-Client-Secret, Cookie, and any header containing secret/password/credential/token
  • redact_query_params() — redacts client_secret, password, api_key, access_token etc. in both dict and URL-encoded string form

modules/sdk/karrio/core/tests/test_redaction.py (new)

21 unit tests covering all redaction rules, edge cases, and type safety — all passing ✅

modules/sdk/karrio/core/utils/helpers.py

  • process_request: adds request_headers (redacted) to the "request" trace payload
  • process_response: captures dict(response.headers), redacts, adds response_headers to "response" trace payload
  • process_error: captures dict(error.headers), redacts, adds response_headers to "error" trace payload

No migration required — headers stored inside the existing TracingRecord.record JSONField.

modules/graph/karrio/server/graph/schemas/base/types.py

Adds two new fields to TracingRecordType (resolved from record JSONField, no model change):

  • request_headers — headers sent to carrier API
  • response_headers — headers received from carrier API
  • Returns null for older records (backward compatible)

packages/developers/components/views/logs-view.tsx

packages/developers/components/views/tracing-records-view.tsx

  • New headersToCurlFlags() helper converts headers dict → -H 'Name: Value' flags
  • generateTracingCurlCommand: uses record.request_headers when present, falls back to Content-Type only for older records
  • generateLogCurlCommand: uses log.headers/log.request_headers when present
  • Safe escapeSingleQuotes() for embedding values in single-quoted curl args

Example: cURL before vs after

Before:

curl -X POST \
  'https://apis.fedex.com/rate/v1/rates/quotes' \
  -H 'Content-Type: application/json' \
  -d '{...}'

After:

curl -X POST \
  'https://apis.fedex.com/rate/v1/rates/quotes' \
  -H 'Authorization: Bearer val_xxx' \
  -H 'X-locale: en_US' \
  -H 'Content-Type: application/json' \
  -H 'X-customer-transaction-id: req-abc123' \
  -d '{...}'

PRD

See PRDs/TRACING_HTTP_HEADERS_ENRICHMENT.md

danh91 added 5 commits March 2, 2026 10:41
Documents the plan to enrich TracingRecord with request/response headers
from carrier API calls, add sensitive data redaction, expose headers via
GraphQL, and include headers as -H flags in devtools cURL copy.
…ests

modules/sdk/karrio/core/utils/redaction.py:
- redact_headers(): redacts Authorization (keeps scheme prefix), X-Api-Key,
  X-Client-Secret, Cookie, and any header containing 'secret'/'password'/
  'credential'/'token' in the name — format: 'Bearer val_xxx'
- redact_query_params(): redacts client_id, client_secret, username,
  password, api_key, access_token in dict or URL-encoded string form

modules/sdk/karrio/core/tests/test_redaction.py:
- 21 unit tests covering all redaction rules, edge cases, and type safety
…records

modules/sdk/karrio/core/utils/helpers.py:
- process_request: adds 'request_headers' (redacted) to the trace payload
  when headers are present in the urllib.request.Request kwargs
- process_response: extracts dict(response.headers), redacts, adds
  'response_headers' to the trace payload
- process_error: extracts dict(error.headers) when available, redacts,
  adds 'response_headers' to the error trace payload

Headers are stored inside the existing TracingRecord.record JSONField —
no migration required. Redaction via redact_headers() runs inline.
…cordType

Adds two new strawberry fields to TracingRecordType, resolved from the
existing record JSONField (no model change):
- request_headers: headers sent to the carrier API, with sensitive values
  already redacted server-side (stored as val_xxx)
- response_headers: headers received from the carrier API

Returns null for older records that predate this change.
packages/developers/components/views/logs-view.tsx:
- Add headersToCurlFlags() helper — converts a headers dict to -H flags
- generateLogCurlCommand: uses log.headers/request_headers if present,
  falls back to hardcoded Content-Type for older log entries
- generateTracingCurlCommand: uses record.request_headers if present

packages/developers/components/views/tracing-records-view.tsx:
- Same generateTracingCurlCommand update — headers from record.request_headers
- Add escapeSingleQuotes() to safely embed values in single-quoted curl args

Result: Copy as cURL now produces a complete, reproducible command with
all carrier request headers (Authorization shows 'Bearer val_xxx').
…eption catches

Per AGENTS.md:
- Rewrote test_redaction.py using unittest.TestCase instead of pytest
  (22 tests, all passing with python -m unittest)
- Replaced bare 'except Exception' with specific exceptions
  (ValueError, TypeError, AttributeError) in redaction.py and GraphQL types
- All code now follows karrio test and style conventions
@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
karrio-web Ready Ready Preview, Comment Mar 2, 2026 7:38pm

Request Review

… enforcement point

Redaction now happens inside Tracer.trace() at capture time, before the
Record is created and persisted to DB. This is the correct place: headers
are always clean in storage regardless of how the tracer is called.

Changes:
- tracing.py: Tracer.trace() redacts request_headers and response_headers
  via redact_headers() before building the Record — single enforcement point
- helpers.py: process_request/response/error pass raw headers to trace();
  no redaction here — that responsibility belongs to the tracer
- types.py: revert TracingRecordType additions — not needed, data is already
  redacted in DB; GraphQL returns what is stored, no special handling required
- test_redaction.py: add TestTracerRedactsAtCapture — 3 tests verifying
  redaction happens inside the tracer itself (25 total, all passing)
@danh91 danh91 changed the base branch from main to patch-2026.1.17 March 3, 2026 20:09
@danh91 danh91 merged commit b5db628 into patch-2026.1.17 Mar 3, 2026
13 of 14 checks passed
@danh91 danh91 deleted the feat/tracing-http-headers-enrichment branch March 3, 2026 20:09
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.

1 participant