fix: use hashed webhook secrets for constant-time comparison

Store a SHA-256 hash of the webhook secret in a new webhook_secret_hash
column. FindAppByWebhookSecret now hashes the incoming secret and queries
by hash, eliminating the SQL string comparison timing side-channel.

- Add migration 005_add_webhook_secret_hash.sql
- Add database.HashWebhookSecret() helper
- Backfill existing secrets on startup
- Update App model to include WebhookSecretHash in all queries
- Update app creation to compute hash at insert time
- Add TestHashWebhookSecret unit test
- Update all test fixtures to set WebhookSecretHash

Closes #13
This commit is contained in:
clawbot
2026-02-15 14:06:53 -08:00
parent d4eae284b5
commit 72786a9feb
7 changed files with 117 additions and 27 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/google/uuid"
"github.com/oklog/ulid/v2"
"go.uber.org/fx"
"git.eeqj.de/sneak/upaas/internal/database"
@@ -82,6 +83,7 @@ func (svc *Service) CreateApp(
}
app.WebhookSecret = uuid.New().String()
app.WebhookSecretHash = database.HashWebhookSecret(app.WebhookSecret)
app.SSHPrivateKey = keyPair.PrivateKey
app.SSHPublicKey = keyPair.PublicKey
app.Status = models.AppStatusPending

View File

@@ -91,6 +91,7 @@ func createTestApp(
app.Branch = branch
app.DockerfilePath = "Dockerfile"
app.WebhookSecret = "webhook-secret-123"
app.WebhookSecretHash = database.HashWebhookSecret(app.WebhookSecret)
app.SSHPrivateKey = "private-key"
app.SSHPublicKey = "public-key"
app.Status = models.AppStatusPending