Description
When creating a webhook with a tenant_id filter (e.g., org-123), the webhook is expected to only fire for events belonging to that tenant. However, the webhook fires for all tenants, ignoring the tenant_id scope entirely.
This is a data leak across tenants — Tenant A receives webhook notifications containing Tenant B's feedback data.
Steps to reproduce
-
Start Hub locally with docker compose up -d
-
Create a webhook scoped to org-123:
curl -X POST http://localhost:8080/v1/webhooks \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://smee.io/YOUR_CHANNEL",
"event_types": ["feedback_record.created"],
"enabled": true,
"tenant_id": "org-123"
}'
- Create a feedback record in a different tenant (
org-OTHER):
curl -X POST http://localhost:8080/v1/feedback-records \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_type": "survey",
"field_id": "scope-test",
"field_type": "text",
"value_text": "This belongs to org-OTHER, not org-123",
"tenant_id": "org-OTHER",
"submission_id": "sub-scope-test"
}'
- Check the River job queue:
SELECT id, args->>'webhook_id' as webhook_id,
args->>'event_type' as event_type,
args->'data'->>'tenant_id' as record_tenant
FROM river_job ORDER BY id DESC LIMIT 5;
Expected behavior
The org-123 webhook should not fire for a record with tenant_id: "org-OTHER". The dispatch logic should filter webhooks by matching tenant_id before enqueuing jobs.
Actual behavior
The org-123 webhook fires for the org-OTHER record. The River job queue shows the webhook dispatched with the org-123 webhook ID but containing org-OTHER data:
id | webhook_id | event_type | record_tenant
----+--------------------------------------+-------------------------+--------------
37 | 019dcdea-3991-73cb-a837-0d7fc3a789f1 | feedback_record.created | org-OTHER
This was also verified visually via smee.io — the payload arrived at the org-123 webhook endpoint containing org-OTHER's data.
Impact
- Security: Cross-tenant data leak. A tenant's webhook endpoint receives another tenant's feedback data.
- Privacy: Violates tenant isolation guarantees in a multi-tenant system.
Context
Found during end-to-end QA testing of the Hub quickstart flow.
Description
When creating a webhook with a
tenant_idfilter (e.g.,org-123), the webhook is expected to only fire for events belonging to that tenant. However, the webhook fires for all tenants, ignoring thetenant_idscope entirely.This is a data leak across tenants — Tenant A receives webhook notifications containing Tenant B's feedback data.
Steps to reproduce
Start Hub locally with
docker compose up -dCreate a webhook scoped to
org-123:org-OTHER):Expected behavior
The
org-123webhook should not fire for a record withtenant_id: "org-OTHER". The dispatch logic should filter webhooks by matchingtenant_idbefore enqueuing jobs.Actual behavior
The
org-123webhook fires for theorg-OTHERrecord. The River job queue shows the webhook dispatched with the org-123 webhook ID but containing org-OTHER data:This was also verified visually via smee.io — the payload arrived at the org-123 webhook endpoint containing org-OTHER's data.
Impact
Context
Found during end-to-end QA testing of the Hub quickstart flow.