diff --git a/go.mod b/go.mod
index 9e6db71f1..0238adc52 100644
--- a/go.mod
+++ b/go.mod
@@ -68,7 +68,7 @@ require (
 	github.com/superseriousbusiness/activity v1.9.0-gts
 	github.com/superseriousbusiness/httpsig v1.2.0-SSB
 	github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8
-	github.com/tdewolff/minify/v2 v2.21.1
+	github.com/tdewolff/minify/v2 v2.21.2
 	github.com/technologize/otel-go-contrib v1.1.1
 	github.com/tetratelabs/wazero v1.8.1
 	github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
@@ -213,7 +213,7 @@ require (
 	github.com/subosito/gotenv v1.6.0 // indirect
 	github.com/superseriousbusiness/go-jpeg-image-structure/v2 v2.0.0-20220321154430-d89a106fdabe // indirect
 	github.com/superseriousbusiness/go-png-image-structure/v2 v2.0.1-SSB // indirect
-	github.com/tdewolff/parse/v2 v2.7.18 // indirect
+	github.com/tdewolff/parse/v2 v2.7.19 // indirect
 	github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
 	github.com/toqueteos/webbrowser v1.2.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
diff --git a/go.sum b/go.sum
index f00a34326..d4835c21a 100644
--- a/go.sum
+++ b/go.sum
@@ -539,10 +539,10 @@ github.com/superseriousbusiness/httpsig v1.2.0-SSB h1:BinBGKbf2LSuVT5+MuH0XynHN9
 github.com/superseriousbusiness/httpsig v1.2.0-SSB/go.mod h1:+rxfATjFaDoDIVaJOTSP0gj6UrbicaYPEptvCLC9F28=
 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8 h1:nTIhuP157oOFcscuoK1kCme1xTeGIzztSw70lX9NrDQ=
 github.com/superseriousbusiness/oauth2/v4 v4.3.2-SSB.0.20230227143000-f4900831d6c8/go.mod h1:uYC/W92oVRJ49Vh1GcvTqpeFqHi+Ovrl2sMllQWRAEo=
-github.com/tdewolff/minify/v2 v2.21.1 h1:AAf5iltw6+KlUvjRNPAPrANIXl3XEJNBBzuZom5iCAM=
-github.com/tdewolff/minify/v2 v2.21.1/go.mod h1:PoqFH8ugcuTUvKqVM9vOqXw4msxvuhL/DTmV5ZXhSCI=
-github.com/tdewolff/parse/v2 v2.7.18 h1:uSqjEMT2lwCj5oifBHDcWU2kN1pbLrRENgFWDJa57eI=
-github.com/tdewolff/parse/v2 v2.7.18/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
+github.com/tdewolff/minify/v2 v2.21.2 h1:VfTvmGVtBYhMTlUAeHtXM7XOsW0JT/6uMwUPPqgUs9k=
+github.com/tdewolff/minify/v2 v2.21.2/go.mod h1:Olje3eHdBnrMjINKffDsil/3NV98Iv7MhWf7556WQVg=
+github.com/tdewolff/parse/v2 v2.7.19 h1:7Ljh26yj+gdLFEq/7q9LT4SYyKtwQX4ocNrj45UCePg=
+github.com/tdewolff/parse/v2 v2.7.19/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
 github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
 github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
 github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
diff --git a/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go b/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go
index 46e6bdafd..3c9da22d3 100644
--- a/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go
+++ b/vendor/github.com/tdewolff/parse/v2/buffer/lexer.go
@@ -2,7 +2,6 @@ package buffer
 
 import (
 	"io"
-	"io/ioutil"
 )
 
 var nullBuffer = []byte{0}
@@ -18,7 +17,7 @@ type Lexer struct {
 	restore func()
 }
 
-// NewLexer returns a new Lexer for a given io.Reader, and uses ioutil.ReadAll to read it into a byte slice.
+// NewLexer returns a new Lexer for a given io.Reader, and uses io.ReadAll to read it into a byte slice.
 // If the io.Reader implements Bytes, that is used instead.
 // It will append a NULL at the end of the buffer.
 func NewLexer(r io.Reader) *Lexer {
@@ -30,7 +29,7 @@ func NewLexer(r io.Reader) *Lexer {
 			b = buffer.Bytes()
 		} else {
 			var err error
-			b, err = ioutil.ReadAll(r)
+			b, err = io.ReadAll(r)
 			if err != nil {
 				return &Lexer{
 					buf: nullBuffer,
diff --git a/vendor/github.com/tdewolff/parse/v2/common.go b/vendor/github.com/tdewolff/parse/v2/common.go
index e0795304c..1883d1bd4 100644
--- a/vendor/github.com/tdewolff/parse/v2/common.go
+++ b/vendor/github.com/tdewolff/parse/v2/common.go
@@ -317,9 +317,13 @@ func replaceEntities(b []byte, i int, entitiesMap map[string][]byte, revEntities
 		}
 	} else {
 		for ; j < len(b) && j-i-1 <= MaxEntityLength && b[j] != ';'; j++ {
+			if !(b[j] >= '0' && b[j] <= '9' || b[j] >= 'a' && b[j] <= 'z' || b[j] >= 'A' && b[j] <= 'Z') {
+				// invalid character reference character
+				break
+			}
 		}
-		if j <= i+1 || len(b) <= j {
-			return b, j - 1
+		if len(b) <= j || j == i+1 || b[j] != ';' {
+			return b, i
 		}
 
 		var ok bool
@@ -399,7 +403,7 @@ func ReplaceMultipleWhitespaceAndEntities(b []byte, entitiesMap map[string][]byt
 	if j == 0 {
 		return b
 	} else if j == 1 { // only if starts with whitespace
-		b[k-1] = b[0]
+		b[k-1] = b[0] // move newline to end of whitespace
 		return b[k-1:]
 	} else if k < len(b) {
 		j += copy(b[j:], b[k:])
diff --git a/vendor/github.com/tdewolff/parse/v2/html/lex.go b/vendor/github.com/tdewolff/parse/v2/html/lex.go
index b24d4dcd2..8774ea264 100644
--- a/vendor/github.com/tdewolff/parse/v2/html/lex.go
+++ b/vendor/github.com/tdewolff/parse/v2/html/lex.go
@@ -362,7 +362,8 @@ func (l *Lexer) shiftBogusComment() []byte {
 
 func (l *Lexer) shiftStartTag() (TokenType, []byte) {
 	for {
-		if c := l.r.Peek(0); (c < 'a' || 'z' < c) && (c < 'A' || 'Z' < c) && (c < '0' || '9' < c) && c != '-' {
+		// spec says only a-zA-Z0-9, but we're lenient here
+		if c := l.r.Peek(0); c == ' ' || c == '>' || c == '/' && l.r.Peek(1) == '>' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == 0 && l.r.Err() != nil || 0 < len(l.tmplBegin) && l.at(l.tmplBegin...) {
 			break
 		}
 		l.r.Move(1)
diff --git a/vendor/github.com/tdewolff/parse/v2/input.go b/vendor/github.com/tdewolff/parse/v2/input.go
index 924f14f0c..586ad7306 100644
--- a/vendor/github.com/tdewolff/parse/v2/input.go
+++ b/vendor/github.com/tdewolff/parse/v2/input.go
@@ -2,7 +2,6 @@ package parse
 
 import (
 	"io"
-	"io/ioutil"
 )
 
 var nullBuffer = []byte{0}
@@ -18,7 +17,7 @@ type Input struct {
 	restore func()
 }
 
-// NewInput returns a new Input for a given io.Input and uses ioutil.ReadAll to read it into a byte slice.
+// NewInput returns a new Input for a given io.Input and uses io.ReadAll to read it into a byte slice.
 // If the io.Input implements Bytes, that is used instead. It will append a NULL at the end of the buffer.
 func NewInput(r io.Reader) *Input {
 	var b []byte
@@ -29,7 +28,7 @@ func NewInput(r io.Reader) *Input {
 			b = buffer.Bytes()
 		} else {
 			var err error
-			b, err = ioutil.ReadAll(r)
+			b, err = io.ReadAll(r)
 			if err != nil {
 				return &Input{
 					buf: nullBuffer,
diff --git a/vendor/modules.txt b/vendor/modules.txt
index a5465ec96..68fb93086 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -832,11 +832,11 @@ github.com/superseriousbusiness/oauth2/v4/generates
 github.com/superseriousbusiness/oauth2/v4/manage
 github.com/superseriousbusiness/oauth2/v4/models
 github.com/superseriousbusiness/oauth2/v4/server
-# github.com/tdewolff/minify/v2 v2.21.1
+# github.com/tdewolff/minify/v2 v2.21.2
 ## explicit; go 1.18
 github.com/tdewolff/minify/v2
 github.com/tdewolff/minify/v2/html
-# github.com/tdewolff/parse/v2 v2.7.18
+# github.com/tdewolff/parse/v2 v2.7.19
 ## explicit; go 1.13
 github.com/tdewolff/parse/v2
 github.com/tdewolff/parse/v2/buffer