The Discourse webhook receiver stores members' email addresses in the raw event log #163

Closed
opened 2026-10-05 03:38:38 +00:00 by cgalo5758 · 1 comment
Owner

What happens

The Discourse webhook receiver writes each verified event body unchanged into core.webhook_events.payload (internal/integrations/discourse/web/webhook.go:125-132). A user_created event carries the forum user's email address, which the receiver reads to match the forum account to a person (webhook.go:177-200), and the whole body is kept. Nothing removes old rows: the monthly partitions of core.webhook_events are created but never dropped, so every address stays as long as the database does.

What should happen

The stored payload holds no personal data, as the data model requires for this column ("Must not contain PII", design/data-model.md:2681). The Stripe receiver already strips a list of keys (email, name, phone, address and others) before storing (internal/integrations/stripe/web/webhook.go:23-32). The Discourse receiver should store only the fields processing needs, or scrub the body the same way, and the rows already stored should be cleaned.

Where

  • internal/integrations/discourse/web/webhook.go, the insert into core.webhook_events.
  • Existing rows where provider = 'discourse' and event_type = 'user_created'.

Steps

  1. Connect a Discourse forum with its webhook pointed at the console.
  2. Create a user on the forum.
  3. Run SELECT payload->'user'->>'email' FROM core.webhook_events WHERE provider = 'discourse' AND event_type = 'user_created'; and the address comes back.

Why it matters

Members' email addresses sit in a table no page shows and nothing deletes. Erasing a person from the console leaves their address behind, and every database backup carries it.

### What happens The Discourse webhook receiver writes each verified event body unchanged into `core.webhook_events.payload` (`internal/integrations/discourse/web/webhook.go:125-132`). A `user_created` event carries the forum user's email address, which the receiver reads to match the forum account to a person (`webhook.go:177-200`), and the whole body is kept. Nothing removes old rows: the monthly partitions of `core.webhook_events` are created but never dropped, so every address stays as long as the database does. ### What should happen The stored payload holds no personal data, as the data model requires for this column ("Must not contain PII", `design/data-model.md:2681`). The Stripe receiver already strips a list of keys (email, name, phone, address and others) before storing (`internal/integrations/stripe/web/webhook.go:23-32`). The Discourse receiver should store only the fields processing needs, or scrub the body the same way, and the rows already stored should be cleaned. ### Where - `internal/integrations/discourse/web/webhook.go`, the insert into `core.webhook_events`. - Existing rows where `provider = 'discourse'` and `event_type = 'user_created'`. ### Steps 1. Connect a Discourse forum with its webhook pointed at the console. 2. Create a user on the forum. 3. Run `SELECT payload->'user'->>'email' FROM core.webhook_events WHERE provider = 'discourse' AND event_type = 'user_created';` and the address comes back. ### Why it matters Members' email addresses sit in a table no page shows and nothing deletes. Erasing a person from the console leaves their address behind, and every database backup carries it.
cgalo5758 added this to the Public launch milestone 2026-10-05 03:38:38 +00:00
cgalo5758 added the
kind
bug
area/discourse
priority
high
securityprivacy
labels 2026-10-05 03:38:38 +00:00
Author
Owner

Fixed in 5219a45a. The Discourse receiver now stores only the forum ids its processing reads: {"user":{"id":…}} for user_created, and {"group_user":{"group_id":…,"user_id":…}} for user_added_to_group and user_removed_from_group; a missing id is stored as 0. A body that does not parse, or lacks the object its event names, is stored as {} and recorded failed. Processing still reads the delivered body as before, so in email mode the forum account is still matched to a person by its address; nothing reads a stored Discourse payload back.

One answer changes: a signed body that is not JSON, or is empty, used to fail the insert, answer 500 and be delivered again, which could only fail again. It now gets a failed row and a 200, as every event the receiver records does.

Rows stored before that commit are not rewritten, because no known deployment has stored a Discourse event. How long webhook events are kept at all is #249. A rule shared by every receiver, with personal fields marked in the integration contract's message definitions, is proposed on #171, since the Discourse receiver leaves the console with #174.

Two neighbouring fixes rode along in 8e1127ed: an error on the forum-link path no longer names a person by email address (see #162), and the billing views' "Last updated" stamp now follows Stripe's events only, so a forum event no longer makes billing look fresh. The other copies of personal data in Discourse errors are #250.

Fixed in 5219a45a. The Discourse receiver now stores only the forum ids its processing reads: `{"user":{"id":…}}` for `user_created`, and `{"group_user":{"group_id":…,"user_id":…}}` for `user_added_to_group` and `user_removed_from_group`; a missing id is stored as 0. A body that does not parse, or lacks the object its event names, is stored as `{}` and recorded `failed`. Processing still reads the delivered body as before, so in email mode the forum account is still matched to a person by its address; nothing reads a stored Discourse payload back. One answer changes: a signed body that is not JSON, or is empty, used to fail the insert, answer 500 and be delivered again, which could only fail again. It now gets a `failed` row and a 200, as every event the receiver records does. Rows stored before that commit are not rewritten, because no known deployment has stored a Discourse event. How long webhook events are kept at all is #249. A rule shared by every receiver, with personal fields marked in the integration contract's message definitions, is proposed on #171, since the Discourse receiver leaves the console with #174. Two neighbouring fixes rode along in 8e1127ed: an error on the forum-link path no longer names a person by email address (see #162), and the billing views' "Last updated" stamp now follows Stripe's events only, so a forum event no longer makes billing look fresh. The other copies of personal data in Discourse errors are #250.
cgalo5758 referenced this issue from a commit 2026-10-22 23:33:36 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wiki-cafe/member-console#163