CRITICAL: API exposes webhook secret and SSH private key in app detail response #114

Closed
opened 2026-02-20 13:51:12 +01:00 by clawbot · 2 comments
Collaborator

Summary

The API endpoint GET /api/v1/apps/{id} returns the raw webhookSecret in the JSON response via appToAPI(). While the SSH private key is excluded from the API response (good), the webhook secret is not — and the web UI handler also passes the full App model (including SSHPrivateKey) to templates.

Location

  • internal/handlers/api.goappToAPI() includes WebhookSecret field
  • internal/handlers/app.goHandleAppDetail() passes entire App model to template

Impact

The webhook secret is the only authentication for triggering deployments. Anyone with API access (or who can CSRF the API per issue #112) can read all webhook secrets and trigger arbitrary deployments.

Additionally, the webhook URL pattern /webhook/{secret} means the secret appears in:

  • Server access logs
  • Reverse proxy logs
  • Browser history
  • Gitea webhook configuration (visible to repo admins)

Suggested Fix

  1. Remove WebhookSecret from appToAPI() response (or only show it once at creation time)
  2. Consider HMAC-based webhook verification instead of secret-in-URL (like GitHub webhooks use X-Hub-Signature-256 header)
  3. Ensure templates don't expose SSHPrivateKey in HTML source

Severity

CRITICAL — Webhook secrets are deployment credentials; exposing them enables unauthorized deployments.

## Summary The API endpoint `GET /api/v1/apps/{id}` returns the raw `webhookSecret` in the JSON response via `appToAPI()`. While the SSH private key is excluded from the API response (good), the webhook secret is not — and the web UI handler also passes the full `App` model (including `SSHPrivateKey`) to templates. ## Location - `internal/handlers/api.go` — `appToAPI()` includes `WebhookSecret` field - `internal/handlers/app.go` — `HandleAppDetail()` passes entire `App` model to template ## Impact The webhook secret is the **only authentication for triggering deployments**. Anyone with API access (or who can CSRF the API per issue #112) can read all webhook secrets and trigger arbitrary deployments. Additionally, the webhook URL pattern `/webhook/{secret}` means the secret appears in: - Server access logs - Reverse proxy logs - Browser history - Gitea webhook configuration (visible to repo admins) ## Suggested Fix 1. Remove `WebhookSecret` from `appToAPI()` response (or only show it once at creation time) 2. Consider HMAC-based webhook verification instead of secret-in-URL (like GitHub webhooks use `X-Hub-Signature-256` header) 3. Ensure templates don't expose `SSHPrivateKey` in HTML source ## Severity **CRITICAL** — Webhook secrets are deployment credentials; exposing them enables unauthorized deployments.
Owner

webhook secret is not private from the user. ssh private key should never leave the upaas instance. are you sure it isn’t sending the PUBLIC key?

webhook secret is not private from the user. ssh private key should never leave the upaas instance. are you sure it isn’t sending the PUBLIC key?
Author
Collaborator

Investigated the code. You're right on both counts:

  1. SSH key: The API only exposes SSHPublicKey, not the private key. The field in api.go:24 is SSHPublicKey string and it's populated from a.SSHPublicKey (line 49). The private key stays in the model/DB layer and is only used internally by the deploy service (deploy.go:907). Not a bug.

  2. Webhook secret: The API does expose it (api.go:23), but as you noted, it's not private from the user — they own the app and need the secret to configure their git provider's webhook.

Neither field is actually leaking sensitive data. Closing as not-a-bug.

Investigated the code. You're right on both counts: 1. **SSH key**: The API only exposes `SSHPublicKey`, not the private key. The field in `api.go:24` is `SSHPublicKey string` and it's populated from `a.SSHPublicKey` (line 49). The private key stays in the model/DB layer and is only used internally by the deploy service (`deploy.go:907`). Not a bug. 2. **Webhook secret**: The API does expose it (`api.go:23`), but as you noted, it's not private from the user — they own the app and need the secret to configure their git provider's webhook. Neither field is actually leaking sensitive data. Closing as not-a-bug.
Sign in to join this conversation.
No Milestone
No project
No Assignees
2 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#114
No description provided.