Skip to content

fix(adapters): stop logging raw webhook bodies and message content (#187) - #245

Merged
patrick-chinchill merged 4 commits into
mainfrom
sync/4.41-log1
Sep 30, 2026
Merged

patrick-chinchill merged 4 commits into
mainfrom
sync/4.41-log1

Conversation

@patrick-chinchill

@patrick-chinchill patrick-chinchill commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Several adapters logged raw webhook bodies, or previews of them, at DEBUG. In some cases this happened before signature or JWT verification. Chat also logged author identity for every incoming message. With DEBUG enabled, unauthenticated input and user-authored content could be copied into log sinks. This PR ports upstream's log-hygiene hardening: handlers now log request-shape metadata only (byte length, content type, event type, whether a signature is present), never bodies or any slice of them.

What changed

Site Before After
GitHub handle_webhook body[:500] before verification; bodyPreview on invalid JSON {bodyBytes, contentType, eventType, signaturePresent} under "GitHub webhook signature verification failed" / "GitHub webhook request verified"; + jsonParseStatus: "error" on invalid JSON (replaces the separate "event type" log)
GChat handle_webhook full body before JWT verification "GChat webhook received" {bodyLength}
Slack handle_webhook body[:500] before verification nothing before verification; "Slack webhook received" {bodyLength} after verify_slack_request succeeds
Teams bridge body[:500] "Teams webhook received" {bodyLength}
Linear body[:500] before verification (Python-only); bodyPreview removed (restores parity); invalid-JSON logs {bodyBytes, contentType}
WhatsApp body[:500] before verification; bodyPreview removed; invalid-JSON logs {bodyBytes, contentType} (divergence)
Chat "Incoming message" author upstream key set: adapter, thread_id, message_id, is_bot, is_me
GChat "message event" / "Pub/Sub parsed message"; Chat/Slack/Discord slash-command logs sender display name, message text textLength only; no text or author (divergence, the issue's recommended default)

src/chat_sdk/shared/log_utils.py adds utf8_byte_length, the Python equivalent of Buffer.byteLength. It counts UTF-8 bytes rather than code points, returns 0 for None, and counts lone surrogates as 3 bytes, so computing a log field never raises.

Upstream mapping

Tests

  • Ported: the three upstream describe("handleWebhook") tests in adapter-github/src/index.test.ts, as tests/test_github_webhook.py::TestGitHubWebhookLogHygiene:

    • should not log raw payload content for invalid signatures
    • ... for invalid JSON
    • ... for valid webhooks

    They use the same sentinels and the same exact metadata assertions. The invalid-signature case uses a multi-byte payload.

  • Python-specific: tests/test_webhook_log_hygiene.py:

    • utf8_byte_length edge cases.
    • GChat: received plus message event, and Pub/Sub parsed message.
    • Slack: nothing logged on an invalid signature, bodyLength after verification, and the slash command.
    • Linear and WhatsApp: invalid signature, invalid JSON, and a valid WhatsApp request.
    • Discord slash command.
    • Chat: "Incoming message" and slash command.
  • tests/test_teams_bridge.py::TestLogging::test_logs_raw_body_at_debug was replaced, not supplemented, per principle 3. It asserted only logger.debug.called.

  • Shared helper: tests/_log_capture.py stringifies every recorded call on a MockLogger or MagicMock logger. The absence check covers the body both verbatim and JSON-escaped, since a plain substring check passes vacuously for JSON bodies.

  • Fail-first check: all 18 new or replaced tests fail against origin/main source and pass with this change.

Divergences (2, within budget)

Both have a row in docs/UPSTREAM_SYNC.md Known Non-Parity, a breadcrumb at each code site, regression tests, and a CHANGELOG entry:

  1. WhatsApp raw-body logging. Upstream 4.41.1 still logs a body preview before verification.
  2. Message-content debug logs (GChat message event and Pub/Sub, and slash-command text in Chat, Slack and Discord). Upstream still logs these. The action and reaction user/user_name logs stay at parity, per the issue's out-of-scope list.

The Linear change restores parity, because upstream Linear never had these logs.

Consumer impact

This changes DEBUG log content only. No routing, response or status behavior changes.

  • The "… webhook raw body" messages are gone, and GChat, Slack and Teams now emit "… webhook received" with bodyLength in UTF-8 bytes.
  • "GitHub webhook event type" becomes "GitHub webhook request verified".
  • bodyPreview becomes bodyBytes.
  • "Incoming message" loses author.

Anything that parses these lines needs updating.

Validation

  • ruff check and ruff format --check are clean.
  • The audit reports 0 hard failures, and no new duplicate warnings.
  • Strict fidelity at chat@4.31.0: 732/732.
  • pytest: 5171 passed, 13 skipped (after merging main at 0e129fe).
  • grep -rnE 'raw body"|bodyPreview' src/chat_sdk returns nothing.

Environment note (resolved on main by #251, which caps microsoft-teams-* below 2.1): uv.lock is gitignored, and a fresh uv sync now resolves microsoft-teams-*==2.1.0. Against 2.1.0, three existing Teams tests fail on main too, with App no longer having activity_sender:

  • test_teams_adapter.py::TestOutboundServiceUrlRouting::test_edit_message_retargets_real_activities_client
  • two in test_teams_native_streaming.py::TestCreateStreamer

The numbers above were run against the baseline SDK 2.0.13.4. The non-strict 4.41.1 fidelity count is not reported, because it waits on the #185 tooling.

Closes #187
Part of #184

Review

Two independent reviews. Every fix was mutation-verified: the named mutant survived before commit 999b3c0 and is killed after it.

Fixed

  • GitHub signaturePresent was untested (both reviewers). Added the parametrized test test_signature_present_reflects_header_presence_not_truthiness. A missing x-hub-signature-256 header gives False, and an empty header gives True, matching upstream signature !== null. The test asserts the 401 response and the exact "GitHub webhook signature verification failed" context. It kills the bool(signature) mutant and the hard-coded True mutant.
  • GChat isinstance(message_text, str) guard was untested. Added test_message_event_without_text_logs_zero_text_length. It sends an attachment-only message with no text key and asserts a 200 response, textLength: 0 and dispatch to process_message. It kills the unguarded len(message_text) mutant, which raised TypeError.
  • assert_body_not_logged missed short prefix previews. The helper now also rejects the body's leading and trailing 24-character slices, both verbatim and JSON-escaped. A body[:N] preview with N >= 24 is now caught wherever the payload's first sentinel sits. It kills the Slack body[:40] mutant. I tried a full sliding window first. It gave false positives: a short JSON fragment legitimately matched a logged identifier (the Pub/Sub ce-type value). So the helper checks anchored slices, and the sentinels spread through each payload cover mid-body excerpts.

Declined

  • Split the branch into fix(adapters) and diverge(adapters) commits (docs/UPSTREAM_SYNC.md "How to land a divergence" / "Review signal"). The convention violation is real, but splitting 182283b means rewriting published history and force-pushing. The wave's working rules say not to rebase or force-push, and another worktree currently has this branch checked out. main is squash-merged (every recent commit is one (#NNN) squash), so a per-commit split would not survive into main anyway. To give reviewers the same signal, both divergences are listed above under "Divergences (2, within budget)", each with its Known Non-Parity row, its CHANGELOG "Python-specific" entry and a # Divergence from upstream breadcrumb at the code site. The maintainer can use a diverge(adapters) note in the squash message when merging.

CI note (superseded, since #251 is merged into this branch and CI is now green): 3 Teams tests (test_teams_adapter.py::TestOutboundServiceUrlRouting::test_edit_message_retargets_real_activities_client and 2 in test_teams_native_streaming.py::TestCreateStreamer) fail with the lock-pinned microsoft-teams-* 2.1.0 SDK. The errors are App having no activity_sender and an unexpected service_url kwarg. This PR does not touch uv.lock, the Teams adapter or its streaming code, and the PR's earlier CI run failed the same way. This is outside #187. By topic it likely belongs to #216 (T1, per-service-URL clients) and #219 (T4, native streaming).

Merge gate

  • Branch state: merged origin/main (with fix(teams): cap microsoft-teams SDK below 2.1 #251) into the branch, which gives HEAD 0e129fe. The only conflict was in CHANGELOG.md. It was resolved by keeping one ## Unreleased (4.41 wave) heading with fix(teams): cap microsoft-teams SDK below 2.1 #251's Teams-cap bullet and this PR's Security and Python-specific sections. No source conflicts.
  • gpt-6-astra review: 1 round, on HEAD 0e129fe. Final verdict: "No actionable regressions found. Validation passed: 5,171 tests, lint, formatting, and test-quality audit; 13 tests were skipped. Upstream fidelity verification was unavailable because the pinned checkout was absent." The fidelity check it could not run was run locally against the pinned chat@4.31.0 checkout. See below.
  • Fixed in the gate: nothing. There were no findings.
  • Rebutted: nothing.
  • Bot comments: none actionable. CodeRabbit's only comment is its earlier "Draft PR not reviewed" notice. No CodeRabbit or gemini-code-assist review, and no inline comments, arrived in the 10+ minutes after the PR was marked ready.
  • Local validation on 0e129fe:
    • ruff check and ruff format are clean.
    • The audit reports 0 hard failures.
    • Strict fidelity at chat@4.31.0 passes: all TS tests have Python equivalents.
    • pytest: 5171 passed, 13 skipped.
  • CI on 0e129fe: all green.
    • Tests pass on 3.12 and 3.13.
    • Lint & Type Check passes. This is the ready_for_review run. The synchronize run was skipped because the PR was still a draft.
    • CodeQL and Analyze (actions, python) pass.

)

Port upstream log-hygiene hardening (fc7df9c4 / vercel/chat#500, logging
parts of f485255b / vercel/chat#877). Webhook handlers now log request-shape
metadata only (byte length, content type, event type, signature presence),
never bodies or slices of them:

- GitHub: {bodyBytes, contentType, eventType, signaturePresent} under
  "signature verification failed" / "request verified"; jsonParseStatus on
  invalid JSON.
- GChat, Slack (post-verification), Teams bridge: "... webhook received"
  {bodyLength}.
- Linear: drop the Python-only raw-body log; invalid-JSON logs bodyBytes.
- Chat "Incoming message": drop author, add is_bot (upstream key set).

Python-ahead divergences, recorded in docs/UPSTREAM_SYNC.md:
- WhatsApp drops its raw-body / bodyPreview logs (upstream still has them).
- GChat message-event / Pub/Sub logs and Chat/Slack/Discord slash-command
  logs carry textLength instead of message text / display names.

Adds shared/log_utils.utf8_byte_length (Buffer.byteLength equivalent that
never raises), ports upstream's three GitHub handleWebhook log tests, and
adds per-adapter sentinel-based regression tests.
@coderabbitai

coderabbitai Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 3 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 56838959-07a7-48da-ad92-a247f7924f0a

📥 Commits

Reviewing files that changed from the base of the PR and between 4af1b98 and 7cfa6b4.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • docs/UPSTREAM_SYNC.md
  • src/chat_sdk/adapters/discord/adapter.py
  • src/chat_sdk/adapters/github/adapter.py
  • src/chat_sdk/adapters/google_chat/adapter.py
  • src/chat_sdk/adapters/linear/adapter.py
  • src/chat_sdk/adapters/slack/adapter.py
  • src/chat_sdk/adapters/teams/bridge.py
  • src/chat_sdk/adapters/whatsapp/adapter.py
  • src/chat_sdk/chat.py
  • src/chat_sdk/shared/log_utils.py
  • tests/_log_capture.py
  • tests/test_github_webhook.py
  • tests/test_teams_bridge.py
  • tests/test_webhook_log_hygiene.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…-prefix previews (#187)

- GitHub: parametrized test for signaturePresent — missing header -> False,
  empty header -> True (mirrors upstream `signature !== null`); kills the
  bool(signature) and hard-coded True mutants.
- GChat: attachment-only message (no `text` key) returns 200 and logs
  textLength 0; kills the unguarded len(message_text) mutant.
- assert_body_not_logged: also reject the body's leading/trailing 24-char
  slices (verbatim and JSON-escaped), so a body[:N] preview is caught even
  when the payload's first sentinel sits further in; kills the Slack
  body[:40] mutant.
@patrick-chinchill
patrick-chinchill marked this pull request as ready for review September 30, 2026 06:40
@patrick-chinchill

Copy link
Copy Markdown
Collaborator Author

Merge gate: CI green (Lint & Type Check, test (3.12), test (3.13), CodeQL, Analyze (python), Analyze (actions)); local Codex review (gpt-6-astra, xhigh, --base origin/main) on 7cfa6b4 (merge of origin/main incl. #234/#235, no conflicts): "No actionable regressions found."; 1 astra round after the main merge (prior clean review on 0e129fe); bot reviews: no submitted reviews, CodeRabbit rate-limited. Merging with --admin (Protect Main requires a code-owner approval).

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.

[4.41/LOG1] Stop logging raw webhook bodies / PII across adapters

1 participant