fix(security): escape newlines in SSE log streaming to prevent event injection
- commit
- 9da36c11433613da460431a198df6e8a00560411
- author
- bnema · · 2026-02-01 14:32 UTC
- parent
- b7669ff
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
1 file · +7 -2
M internal/adapters/in/http/admin/handler.go +7 -2
| @@ -893,7 +893,10 @@ func (h *Handler) streamProcessLogs(w http.ResponseWriter, r *http.Request, line | |||
| 893 | 893 | if !ok { | |
| 894 | 894 | return | |
| 895 | 895 | } | |
| 896 | - | _, _ = fmt.Fprintf(w, "data: %s\n\n", line) | |
| 896 | + | // SECURITY: Escape newlines in log lines to prevent SSE event injection. | |
| 897 | + | // Per the SSE spec, multi-line data must use separate "data:" prefixes. | |
| 898 | + | escaped := strings.ReplaceAll(line, "\n", "\ndata: ") | |
| 899 | + | _, _ = fmt.Fprintf(w, "data: %s\n\n", escaped) | |
| 897 | 900 | flusher.Flush() | |
| 898 | 901 | case <-ctx.Done(): | |
| 899 | 902 | return | |
| @@ -933,7 +936,9 @@ func (h *Handler) streamContainerLogs(w http.ResponseWriter, r *http.Request, lo | |||
| 933 | 936 | if !ok { | |
| 934 | 937 | return | |
| 935 | 938 | } | |
| 936 | - | _, _ = fmt.Fprintf(w, "data: %s\n\n", line) | |
| 939 | + | // SECURITY: Escape newlines in log lines to prevent SSE event injection. | |
| 940 | + | escaped := strings.ReplaceAll(line, "\n", "\ndata: ") | |
| 941 | + | _, _ = fmt.Fprintf(w, "data: %s\n\n", escaped) | |
| 937 | 942 | flusher.Flush() | |
| 938 | 943 | case <-ctx.Done(): | |
| 939 | 944 | return | |
| @@ -893,7 +893,10 @@ func (h *Handler) streamProcessLogs(w http.ResponseWriter, r *http.Request, line | |||||
| 893 | if !ok { | 893 | if !ok { | ||
| 894 | return | 894 | return | ||
| 895 | } | 895 | } | ||
| 896 | - | _, _ = fmt.Fprintf(w, "data: %s\n\n", line) | 896 | + | // SECURITY: Escape newlines in log lines to prevent SSE event injection. |
| 897 | + | // Per the SSE spec, multi-line data must use separate "data:" prefixes. | |||
| 898 | + | escaped := strings.ReplaceAll(line, "\n", "\ndata: ") | |||
| 899 | + | _, _ = fmt.Fprintf(w, "data: %s\n\n", escaped) | |||
| 897 | flusher.Flush() | 900 | flusher.Flush() | ||
| 898 | case <-ctx.Done(): | 901 | case <-ctx.Done(): | ||
| 899 | return | 902 | return | ||
| @@ -933,7 +936,9 @@ func (h *Handler) streamContainerLogs(w http.ResponseWriter, r *http.Request, lo | |||||
| 933 | if !ok { | 936 | if !ok { | ||
| 934 | return | 937 | return | ||
| 935 | } | 938 | } | ||
| 936 | - | _, _ = fmt.Fprintf(w, "data: %s\n\n", line) | 939 | + | // SECURITY: Escape newlines in log lines to prevent SSE event injection. |
| 940 | + | escaped := strings.ReplaceAll(line, "\n", "\ndata: ") | |||
| 941 | + | _, _ = fmt.Fprintf(w, "data: %s\n\n", escaped) | |||
| 937 | flusher.Flush() | 942 | flusher.Flush() | ||
| 938 | case <-ctx.Done(): | 943 | case <-ctx.Done(): | ||
| 939 | return | 944 | return | ||