Skip to content

Commit 688ab3c

Browse files
ericfitzclaude
andcommitted
fix(api): use WebhookUrlValidator in webhook creation handler
Replace inline case-sensitive string prefix checks with the proper WebhookUrlValidator, which handles case-insensitive schemes, hostname validation, and deny list checks. Also remove unused methods calculateCount, loadThreatMetadata, and toGormModel. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 562440e commit 688ab3c

File tree

6 files changed

+10
-48
lines changed

6 files changed

+10
-48
lines changed

.version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"major": 1,
33
"minor": 3,
4-
"patch": 1,
4+
"patch": 2,
55
"prerelease": ""
66
}

api/database_store_gorm.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -576,13 +576,6 @@ func (s *GormThreatModelStore) ListWithCounts(offset, limit int, filter func(Thr
576576
return results, total
577577
}
578578

579-
// calculateCount counts records in a table for a threat model using GORM
580-
func (s *GormThreatModelStore) calculateCount(tableName, threatModelID string) int {
581-
var count int64
582-
s.db.Table(tableName).Where("threat_model_id = ? AND deleted_at IS NULL", threatModelID).Count(&count)
583-
return int(count)
584-
}
585-
586579
// entityCounts holds pre-fetched counts for all sub-resources of a threat model.
587580
type entityCounts struct {
588581
DocumentCount int
@@ -1217,25 +1210,6 @@ func (s *GormThreatModelStore) loadThreats(threatModelID string) ([]Threat, erro
12171210
return threats, nil
12181211
}
12191212

1220-
// loadThreatMetadata loads metadata for a threat using GORM
1221-
func (s *GormThreatModelStore) loadThreatMetadata(threatID string) ([]Metadata, error) {
1222-
var metadataEntries []models.Metadata
1223-
result := s.db.Where("entity_type = ? AND entity_id = ?", "threat", threatID).Order("key ASC").Find(&metadataEntries)
1224-
if result.Error != nil {
1225-
return nil, result.Error
1226-
}
1227-
1228-
var metadata []Metadata
1229-
for _, entry := range metadataEntries {
1230-
metadata = append(metadata, Metadata{
1231-
Key: entry.Key,
1232-
Value: entry.Value,
1233-
})
1234-
}
1235-
1236-
return metadata, nil
1237-
}
1238-
12391213
// batchLoadThreatMetadata loads metadata for multiple threats in a single query.
12401214
func (s *GormThreatModelStore) batchLoadThreatMetadata(threatIDs []string) map[string][]Metadata {
12411215
result := make(map[string][]Metadata, len(threatIDs))

api/threat_store_gorm.go

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,19 +1037,6 @@ func (s *GormThreatStore) toGormModelForCreate(threat *Threat) *models.Threat {
10371037
return gm
10381038
}
10391039

1040-
// toGormModel converts an API Threat to a GORM model for UPDATE operations.
1041-
// This version sets timestamps from the API model for updates.
1042-
func (s *GormThreatStore) toGormModel(threat *Threat) *models.Threat {
1043-
gm := s.toGormModelForCreate(threat)
1044-
if threat.CreatedAt != nil {
1045-
gm.CreatedAt = *threat.CreatedAt
1046-
}
1047-
if threat.ModifiedAt != nil {
1048-
gm.ModifiedAt = *threat.ModifiedAt
1049-
}
1050-
return gm
1051-
}
1052-
10531040
// toAPIModel converts a GORM Threat model to an API model
10541041
func (s *GormThreatStore) toAPIModel(gm *models.Threat) *Threat {
10551042
mitigatedBool := gm.Mitigated.Bool()

api/version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ var (
5050
// Minor version number
5151
VersionMinor = "3"
5252
// Patch version number
53-
VersionPatch = "1"
53+
VersionPatch = "2"
5454
// VersionPreRelease is the pre-release label (e.g., "rc.0", "beta.1"), empty for stable releases
5555
VersionPreRelease = ""
5656
// GitCommit is the git commit hash from build

api/webhook_handlers.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"encoding/json"
55
"fmt"
66
"net/http"
7-
"strings"
87
"time"
98

109
"github.com/ericfitz/tmi/internal/slogging"
@@ -151,12 +150,11 @@ func (s *Server) CreateWebhookSubscription(c *gin.Context) {
151150
return
152151
}
153152

154-
// Validate URL scheme
155-
if !strings.HasPrefix(input.Url, "https://") {
156-
if !s.allowHTTPWebhooks || !strings.HasPrefix(input.Url, "http://") {
157-
c.JSON(http.StatusBadRequest, Error{Error: "webhook URL must use HTTPS"})
158-
return
159-
}
153+
// Validate webhook URL (scheme, hostname, deny list)
154+
urlValidator := NewWebhookUrlValidatorWithHTTP(GlobalWebhookUrlDenyListStore, s.allowHTTPWebhooks)
155+
if err := urlValidator.ValidateWebhookURL(input.Url); err != nil {
156+
c.JSON(http.StatusBadRequest, Error{Error: fmt.Sprintf("invalid webhook URL: %s", err.Error())})
157+
return
160158
}
161159

162160
// Generate secret if not provided

api/webhook_handlers_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,11 +690,14 @@ func TestCreateWebhookSubscription(t *testing.T) {
690690
origSubStore := GlobalWebhookSubscriptionStore
691691
origQuotaStore := GlobalWebhookQuotaStore
692692
origAdminStore := GlobalGroupMemberStore
693+
origDenyListStore := GlobalWebhookUrlDenyListStore
693694
defer func() {
694695
GlobalWebhookSubscriptionStore = origSubStore
695696
GlobalWebhookQuotaStore = origQuotaStore
696697
GlobalGroupMemberStore = origAdminStore
698+
GlobalWebhookUrlDenyListStore = origDenyListStore
697699
}()
700+
GlobalWebhookUrlDenyListStore = &mockDenyListStore{entries: []WebhookUrlDenyListEntry{}}
698701

699702
t.Run("Success_AdminCanCreate", func(t *testing.T) {
700703
mockSubStore := newMockWebhookSubscriptionStore()

0 commit comments

Comments
 (0)