SECURITY: Webhook secret exposed in plain text in app detail page and request logs #36

Closed
opened 2026-02-16 06:56:32 +01:00 by clawbot · 0 comments
Collaborator

Severity: MEDIUM

Files

  • internal/handlers/app.go line 130: webhookURL := "https://" + request.Host + "/webhook/" + application.WebhookSecret
  • templates/app_detail.html: displays full webhook URL with secret
  • internal/middleware/middleware.go (Logging middleware): logs full URL including webhook secret on every request

Description

The webhook secret is embedded directly in the URL path (/webhook/{secret}) and:

  1. Displayed in plain text in the app detail page UI, copyable to clipboard
  2. Logged in full by the request logging middleware on every webhook invocation
  3. Stored in plain text in the database (the webhook_secret column), alongside the hash

While the hash-based lookup is good (avoids timing attacks), the plain-text secret is still stored and logged.

Suggested Fix

  1. Logging: Redact the secret from logged URLs. The logging middleware should mask /webhook/* paths:
url := request.URL.String()
if strings.HasPrefix(url, "/webhook/") {
    url = "/webhook/[REDACTED]"
}
  1. Storage: Consider not storing the plain-text secret after initial display. Show it once on creation, then only store the hash. The user can regenerate if lost.
  2. UI: Add a "show/hide" toggle rather than displaying the full URL by default.
## Severity: MEDIUM ## Files - `internal/handlers/app.go` line 130: `webhookURL := "https://" + request.Host + "/webhook/" + application.WebhookSecret` - `templates/app_detail.html`: displays full webhook URL with secret - `internal/middleware/middleware.go` (Logging middleware): logs full URL including webhook secret on every request ## Description The webhook secret is embedded directly in the URL path (`/webhook/{secret}`) and: 1. **Displayed in plain text** in the app detail page UI, copyable to clipboard 2. **Logged in full** by the request logging middleware on every webhook invocation 3. **Stored in plain text** in the database (the `webhook_secret` column), alongside the hash While the hash-based lookup is good (avoids timing attacks), the plain-text secret is still stored and logged. ## Suggested Fix 1. **Logging**: Redact the secret from logged URLs. The logging middleware should mask `/webhook/*` paths: ```go url := request.URL.String() if strings.HasPrefix(url, "/webhook/") { url = "/webhook/[REDACTED]" } ``` 2. **Storage**: Consider not storing the plain-text secret after initial display. Show it once on creation, then only store the hash. The user can regenerate if lost. 3. **UI**: Add a "show/hide" toggle rather than displaying the full URL by default.
sneak closed this issue 2026-02-16 07:01:37 +01:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: sneak/upaas#36
No description provided.