Skip to content

Telegram chat_id is unmasked in egress audit records while WhatsApp numbers are masked #14540

Closed

Description

@mrveiss

The inconsistency

The WhatsApp seam masks the recipient before it reaches the audit record — _mask_phone, applied to the number used as channel_id.

The Telegram seam passes chat_id through unmasked, into both the audit record and the warning log line on a denial.

A Telegram chat_id is durable and person-identifying: it is stable for the life of the account and resolves directly to one user for anyone with bot access. Whether it is "PII" in the same sense as a phone number is a fair question — but the two seams answer that question differently, in the same file family, added in the same change, with nothing recording why.

Why this needs a decision rather than a patch

Masking it is not obviously right. An operator reading an audit trail needs enough to identify which conversation was blocked, and a masked chat_id may make the record useless for its purpose. The WhatsApp choice happens to work because a phone number is recognisable even partially masked.

So the question is what the audit record is for, and the answer should be written down once and applied to every platform, rather than decided per-seam by whoever writes it.

Note the precedent already in the code: the webhook seam deliberately records urlparse(url).hostname rather than the full URL, specifically so a token embedded in the path does not reach the record — and that choice is pinned by a test. That is the pattern to follow: decide, then assert.

Acceptance criteria

  • A stated rule for what identifies a channel in an egress audit record, applied to every platform
  • Telegram and WhatsApp agree with that rule
  • The choice is pinned by a test, as the webhook host-only rule already is
  • Denial log lines are covered too, not only the audit record

Provenance

Found by code review of #14334 (#14270), which noted the distinction may well be deliberate but is neither commented nor tested — unlike the mark_message_read exclusion and the webhook host-only rule, both of which are pinned.

Activity

  1. github-actions commented on Aug 18, 2026

    @github-actions
    Contributor

    👋 Auto-triage could not confidently classify this issue (no confident signal). Could a maintainer add the appropriate labels (frontend, backend, infrastructure, docs, testing and good-first-issue, intermediate, advanced)?

  2. mrveiss commented on Aug 18, 2026

    @mrveiss
    OwnerAuthor

    Delivered by PR #14569, squash-merged as 734c3a253.

    The stated rule, now in egress_governor.py's module docstring: a channel identifier directly usable outside this system on its own (a dialable number, a URL with an embedded token) is reduced before reaching evaluate(); an opaque identifier scoped to this system's own platform credential is recorded as-is. WhatsApp masked, webhook hostname-only, Telegram and Slack/Discord unmasked — all pinned by tests, log lines included.

    The docstring carries a caveat rather than overstating its case: for a 1:1 private chat, Telegram's chat_id is the user's global account ID, resolvable by any bot or client with contact access — materially weaker than a workspace-scoped Slack/Discord channel_id. The decision stands; the reasoning no longer claims they are equivalent.

    Asking for the Slack/Discord pin as a completeness matter found a live bug — verified against base:

    • the send path used _extract_channel_and_text, which reads message.channel for Slack and message.channel_id for Discord
    • the egress audit path read only message.channel_id

    So every Slack egress record has been written with an empty channel_id — not leaky, the opposite: an audit row that looks well-formed and identifies nothing. Fixed at integration_communication.py:202. Worth recording that completeness-checking, not severity-checking, is what surfaced it.

    Follow-up #14573: the email and Teams paths pass channel_id="" unconditionally — empty by construction rather than by bug, the inverse problem, needing a decision rather than a patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions