MCPcopy Create free account
hub / github.com/Mnexa-AI/e2a / validateWebhookFields

Method validateWebhookFields

internal/httpapi/webhooks.go:84–147  ·  view source on GitHub ↗

validateWebhookFields replicates the legacy validation. The two security-critical checks are reused from their canonical homes (SSRF via agent.ValidateWebhookURL, event allowlist via webhookpub.IsValidEventType) so they can't drift; the charset/length/ownership checks are explicit.

(ctx context.Context, userID, url string, events []string, f WebhookFiltersView, description string)

Source from the content-addressed store, hash-verified

82// agent.ValidateWebhookURL, event allowlist via webhookpub.IsValidEventType)
83// so they can't drift; the charset/length/ownership checks are explicit.
84func (s *Server) validateWebhookFields(ctx context.Context, userID, url string, events []string, f WebhookFiltersView, description string) *ErrorEnvelope {
85 if url != "" {
86 if err := agent.ValidateWebhookURL(url); err != nil {
87 return NewError(http.StatusBadRequest, "invalid_webhook_url", "invalid url: "+err.Error())
88 }
89 if len(url) > webhookMaxURLLen {
90 return NewError(http.StatusBadRequest, "invalid_request", "url too long (max 2048 chars)")
91 }
92 }
93 if len(events) == 0 {
94 return NewError(http.StatusBadRequest, "invalid_request", "events must be a non-empty array")
95 }
96 for _, e := range events {
97 if !webhookpub.IsValidEventType(e) {
98 return NewError(http.StatusBadRequest, "invalid_event_type",
99 fmt.Sprintf("unknown event type %q (allowed: %s)", e, strings.Join(webhookpub.AllEventTypes, ", ")))
100 }
101 }
102 if len(description) > webhookMaxDescriptionLen {
103 return NewError(http.StatusBadRequest, "invalid_request", "description too long (max 200 chars)")
104 }
105 if strings.ContainsAny(description, "\r\n") {
106 return NewError(http.StatusBadRequest, "invalid_request", "description must not contain CR or LF")
107 }
108 if len(f.AgentIDs) > webhookMaxAgentIDs {
109 return NewError(http.StatusBadRequest, "invalid_request", fmt.Sprintf("filters.agent_ids exceeds cap of %d", webhookMaxAgentIDs))
110 }
111 if len(f.ConversationIDs) > webhookMaxConversationIDs {
112 return NewError(http.StatusBadRequest, "invalid_request", fmt.Sprintf("filters.conversation_ids exceeds cap of %d", webhookMaxConversationIDs))
113 }
114 if len(f.Labels) > webhookMaxLabels {
115 return NewError(http.StatusBadRequest, "invalid_request", fmt.Sprintf("filters.labels exceeds cap of %d", webhookMaxLabels))
116 }
117 for _, a := range f.AgentIDs {
118 if a == "" || len(a) > webhookMaxFilterValueLen {
119 return NewError(http.StatusBadRequest, "invalid_request", "filters.agent_ids contains empty entry or one over 200 chars")
120 }
121 }
122 // agent_ids must reference agents the caller owns.
123 if err := s.assertAgentsOwned(ctx, userID, f.AgentIDs); err != nil {
124 return err
125 }
126 for _, c := range f.ConversationIDs {
127 if c == "" || len(c) > webhookMaxFilterValueLen {
128 return NewError(http.StatusBadRequest, "invalid_request", "filters.conversation_ids contains empty entry or one over 200 chars")
129 }
130 for _, r := range c {
131 if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '-' || r == '_') {
132 return NewError(http.StatusBadRequest, "invalid_request", fmt.Sprintf("filters.conversation_ids[%q]: invalid character", c))
133 }
134 }
135 }
136 for _, l := range f.Labels {
137 if l == "" || len(l) > 64 {
138 return NewError(http.StatusBadRequest, "invalid_request", "filters.labels contains empty entry or one over 64 chars")
139 }
140 for _, r := range l {
141 if !((r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == ':' || r == '-' || r == '_') {

Callers 2

handleUpdateWebhookMethod · 0.95
handleCreateWebhookMethod · 0.95

Calls 5

assertAgentsOwnedMethod · 0.95
ValidateWebhookURLFunction · 0.92
IsValidEventTypeFunction · 0.92
NewErrorFunction · 0.85
ErrorMethod · 0.45

Tested by

no test coverage detected