Add some limits

This commit is contained in:
binwiederhier
2025-07-19 16:46:53 +02:00
parent 57df16dd62
commit dde07adbdc
9 changed files with 77 additions and 13 deletions

View File

@@ -123,7 +123,6 @@ var (
errHTTPBadRequestTemplateDisallowedFunctionCalls = &errHTTP{40044, http.StatusBadRequest, "invalid request: template contains disallowed function calls, e.g. template, call, or define", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateExecuteFailed = &errHTTP{40045, http.StatusBadRequest, "invalid request: template execution failed", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestInvalidUsername = &errHTTP{40046, http.StatusBadRequest, "invalid request: invalid username", "", nil}
errHTTPBadRequestTemplateDirectoryNotConfigured = &errHTTP{40046, http.StatusBadRequest, "invalid request: template directory not configured", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateFileNotFound = &errHTTP{40047, http.StatusBadRequest, "invalid request: template file not found", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPBadRequestTemplateFileInvalid = &errHTTP{40048, http.StatusBadRequest, "invalid request: template file invalid", "https://ntfy.sh/docs/publish/#message-templating", nil}
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}

View File

@@ -145,6 +145,7 @@ const (
unifiedPushTopicLength = 14 // Length of UnifiedPush topics, including the "up" part
messagesHistoryMax = 10 // Number of message count values to keep in memory
templateMaxExecutionTime = 100 * time.Millisecond // Maximum time a template can take to execute, used to prevent DoS attacks
templateMaxOutputBytes = 1024 * 1024 // Maximum number of bytes a template can output, used to prevent DoS attacks
templateFileExtension = ".yml" // Template files must end with this extension
)
@@ -1127,7 +1128,7 @@ func (s *Server) handleBodyAsTemplatedTextMessage(m *message, template templateM
return err
}
}
if len(m.Message) > s.config.MessageSizeLimit {
if len(m.Title) > s.config.MessageSizeLimit || len(m.Message) > s.config.MessageSizeLimit {
return errHTTPBadRequestTemplateMessageTooLarge
}
return nil
@@ -1188,7 +1189,8 @@ func (s *Server) replaceTemplate(tpl string, source string) (string, error) {
return "", errHTTPBadRequestTemplateInvalid.Wrap("%s", err.Error())
}
var buf bytes.Buffer
if err := t.Execute(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), data); err != nil {
limitWriter := util.NewLimitWriter(util.NewTimeoutWriter(&buf, templateMaxExecutionTime), util.NewFixedLimiter(templateMaxOutputBytes))
if err := t.Execute(limitWriter, data); err != nil {
return "", errHTTPBadRequestTemplateExecuteFailed.Wrap("%s", err.Error())
}
return strings.TrimSpace(buf.String()), nil

View File

@@ -3143,6 +3143,42 @@ message: |
require.Equal(t, "Custom message 1391", m.Message)
}
func TestServer_MessageTemplate_Repeat9999_TooLarge(t *testing.T) {
t.Parallel()
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic", `{}`, map[string]string{
"X-Message": `{{ repeat 9999 "mystring" }}`,
"X-Template": "1",
})
require.Equal(t, 400, response.Code)
require.Equal(t, 40041, toHTTPError(t, response.Body.String()).Code)
require.Contains(t, toHTTPError(t, response.Body.String()).Message, "message or title is too large after replacing template")
}
func TestServer_MessageTemplate_Repeat10001_TooLarge(t *testing.T) {
t.Parallel()
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic", `{}`, map[string]string{
"X-Message": `{{ repeat 10001 "mystring" }}`,
"X-Template": "1",
})
require.Equal(t, 400, response.Code)
require.Equal(t, 40045, toHTTPError(t, response.Body.String()).Code)
require.Contains(t, toHTTPError(t, response.Body.String()).Message, "repeat count 10001 exceeds limit of 10000")
}
func TestServer_MessageTemplate_Until100_000(t *testing.T) {
t.Parallel()
s := newTestServer(t, newTestConfig(t))
response := request(t, s, "POST", "/mytopic", `{}`, map[string]string{
"X-Message": `{{ range $i, $e := until 100_000 }}{{end}}`,
"X-Template": "1",
})
require.Equal(t, 400, response.Code)
require.Equal(t, 40045, toHTTPError(t, response.Body.String()).Code)
require.Contains(t, toHTTPError(t, response.Body.String()).Message, "too many iterations")
}
func newTestConfig(t *testing.T) *Config {
conf := NewConfig()
conf.BaseURL = "http://127.0.0.1:12345"