Summary
The POST /api/integrations/webhooks/{webhook_id}/ping endpoint fetches the target webhook by primary key alone without verifying that the webhook belongs to the authenticated user. Any authenticated user can supply an arbitrary webhook_id to confirm webhook existence, leak the webhook's OAuth provider type, and in some cases trigger a ping delivery on behalf of another user.
Details
autogpt_platform/backend/backend/api/features/integrations/router.py (line 610):
@router.post("/webhooks/{webhook_id}/ping")
async def webhook_ping(
webhook_id: Annotated[str, Path(title="Our ID for the webhook")],
user_id: Annotated[str, Security(get_user_id)],
):
webhook = await get_webhook(webhook_id) # ← no ownership check
webhook_manager = get_webhook_manager(webhook.provider)
credentials = (
await creds_manager.get(user_id, webhook.credentials_id)
if webhook.credentials_id
else None
)
await webhook_manager.trigger_ping(webhook, credentials)
The get_webhook() function is explicitly documented as unsafe for user-facing endpoints:
async def get_webhook(webhook_id: str, ...) -> Webhook:
"""
⚠️ No `user_id` check: DO NOT USE without check in user-facing endpoints.
"""
The correct ownership check pattern exists in the same file (line 994):
webhook = await get_webhook(webhook_id, include_relations=True)
if not webhook or webhook.user_id != user_id: # ← correct guard (omitted above)
return
Affected commit: 94ebce7f633f40af8fd59e5cdaea75159cf2e6d1
PoC
# Attacker authenticates as any registered user
TOKEN="<attacker-jwt>"
VICTIM_WEBHOOK_ID="<guessed-or-enumerated-uuid>"
curl -s -o /dev/null -w "%{http_code}" \
-X POST "https://app.agpt.co/api/integrations/webhooks/$VICTIM_WEBHOOK_ID/ping" \
-H "Authorization: Bearer $TOKEN"
# 404 → webhook does not exist
# 200 → webhook exists (provider does not support ping → returns false)
# 500 → webhook exists AND is a GitHub webhook (ValueError: "Credentials are required")
reproduce.zip
Impact
Any authenticated AutoGPT Platform user is affected. An attacker with a valid account can:
- Enumerate webhooks — distinguish HTTP 404 (not found) vs. 200/500 (exists) for any UUID, confirming webhook ownership across all users
- Leak OAuth provider type — the HTTP 500 error message varies by provider (GitHub raises
ValueError; others raise NotImplementedError), revealing which OAuth service owns the victim's webhook
- Trigger unsolicited ping delivery — for providers where
trigger_ping succeeds without credentials, the attacker causes a spurious webhook event at the victim's registered endpoint
Summary
The
POST /api/integrations/webhooks/{webhook_id}/pingendpoint fetches the target webhook by primary key alone without verifying that the webhook belongs to the authenticated user. Any authenticated user can supply an arbitrarywebhook_idto confirm webhook existence, leak the webhook's OAuth provider type, and in some cases trigger a ping delivery on behalf of another user.Details
autogpt_platform/backend/backend/api/features/integrations/router.py(line 610):The
get_webhook()function is explicitly documented as unsafe for user-facing endpoints:The correct ownership check pattern exists in the same file (line 994):
Affected commit:
94ebce7f633f40af8fd59e5cdaea75159cf2e6d1PoC
reproduce.zip
Impact
Any authenticated AutoGPT Platform user is affected. An attacker with a valid account can:
ValueError; others raiseNotImplementedError), revealing which OAuth service owns the victim's webhooktrigger_pingsucceeds without credentials, the attacker causes a spurious webhook event at the victim's registered endpoint