diff --git a/go.mod b/go.mod
index d2a5569..62c2fd7 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ require (
 	github.com/google/uuid v1.6.0
 	github.com/issue9/assert/v4 v4.3.1
 	github.com/issue9/version v1.0.8
-	github.com/issue9/web v0.100.4
+	github.com/issue9/web v0.100.9
 	golang.org/x/text v0.21.0
 )
 
@@ -18,16 +18,16 @@ require (
 	github.com/issue9/errwrap v0.3.2 // indirect
 	github.com/issue9/localeutil v0.29.1 // indirect
 	github.com/issue9/logs/v7 v7.6.4 // indirect
-	github.com/issue9/mux/v9 v9.1.0 // indirect
+	github.com/issue9/mux/v9 v9.1.2 // indirect
 	github.com/issue9/query/v3 v3.1.3 // indirect
-	github.com/issue9/scheduled v0.21.3 // indirect
+	github.com/issue9/scheduled v0.22.0 // indirect
 	github.com/issue9/sliceutil v0.17.0 // indirect
-	github.com/issue9/source v0.11.6 // indirect
+	github.com/issue9/source v0.11.7 // indirect
 	github.com/issue9/term/v3 v3.3.2 // indirect
 	github.com/jellydator/ttlcache/v3 v3.3.0 // indirect
 	github.com/klauspost/compress v1.17.11 // indirect
 	golang.org/x/mod v0.22.0 // indirect
 	golang.org/x/sync v0.10.0 // indirect
-	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/sys v0.29.0 // indirect
 	golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
 )
diff --git a/go.sum b/go.sum
index f77e6ab..8560b48 100644
--- a/go.sum
+++ b/go.sum
@@ -18,26 +18,26 @@ github.com/issue9/localeutil v0.29.1 h1:C5vxqwcAFa9gzMWDni8KsZ2GYtKD+I20RMlKl78t
 github.com/issue9/localeutil v0.29.1/go.mod h1:daxDbw51s4GWKjz6pxhh8baHzWDJEUUcsVrqaKs/A8o=
 github.com/issue9/logs/v7 v7.6.4 h1:NFM4oM2ggLPPZNgDb8PAOSqELtJOx7PTwoFd3o814wk=
 github.com/issue9/logs/v7 v7.6.4/go.mod h1:6fIj8OABk/qWm1c3E9f9oXe0FWwRyoZNWh449D391Zk=
-github.com/issue9/mux/v9 v9.1.0 h1:KFvCugh4eQf/2QiLVJOa2AzOEdGpG5xe7+JiqZCYluE=
-github.com/issue9/mux/v9 v9.1.0/go.mod h1:lhKQh+boCGOP5yCEAQx7tb24nUsxT57ygU0gqD6/Vgg=
+github.com/issue9/mux/v9 v9.1.2 h1:Ng5uv8bwsduZV6w14gv0HR78ahBs8Fm0JWlc7Xjtbd4=
+github.com/issue9/mux/v9 v9.1.2/go.mod h1:CLtV5ZfIrAmfaC/qxn07wXc3GALI45GYCsdE3c/h9CQ=
 github.com/issue9/query/v3 v3.1.3 h1:Y6ETEYXxaKqhpM4lXPKCffhJ72VuKQbrAwgwHlacu0Y=
 github.com/issue9/query/v3 v3.1.3/go.mod h1:a/W/+7iel9K+5rRT4AFAKR8+OJeV5axeF6tK9My4lNA=
 github.com/issue9/rands/v3 v3.0.1 h1:EnX9WNushGgHCzoL/R5eBPaLfvjLO/c7CGHNgLK0JhY=
 github.com/issue9/rands/v3 v3.0.1/go.mod h1:n4mM2ts7NCpuxHwS9zorPITJBWEUGksXg6cTOH6yqS0=
-github.com/issue9/scheduled v0.21.3 h1:22R1jl8rI+wrepHKZA1GfpZ6rpj5DLxVj69f0Vzrwfc=
-github.com/issue9/scheduled v0.21.3/go.mod h1:o9Z7g50Ld1pOozYQ4RqRLKDrLcQP/dOhSd84A0c6d2w=
+github.com/issue9/scheduled v0.22.0 h1:qGk4+IbaxozIEaYX9mWEM1C//Zg/iI8DVT6tYIS/ibU=
+github.com/issue9/scheduled v0.22.0/go.mod h1:wVGvPy6kxstLwkfQIYEfpORl+gNKTW68ydFA4d78fMQ=
 github.com/issue9/sliceutil v0.17.0 h1:EtmVlldkAyGxS0O2TnxcAu3pgLJw74I5eh9DHT7TOZs=
 github.com/issue9/sliceutil v0.17.0/go.mod h1:CVpH4f228pICY7JImlztUPe/zf2w0EicEleU9YfFe00=
-github.com/issue9/source v0.11.6 h1:ABrURywr1zsoCneDEusLWO+/OCkXfcoS5x8Z2by/clg=
-github.com/issue9/source v0.11.6/go.mod h1:78T7Mm8FNHsZbSkET/w02A7a3L9w3OzwR+Tx/BILEA4=
+github.com/issue9/source v0.11.7 h1:wyZv2MExD1kem7FGxIy6/iSgDHqNpLox6dAZfP7VzKM=
+github.com/issue9/source v0.11.7/go.mod h1:cmLmHZdgj+ONnGNdnJp++94VKXTszNAD99hAYOC22bU=
 github.com/issue9/term/v3 v3.3.2 h1:heyRhfA6EMztqh5UHUZyKr/LatFZXbJ7YhE5RI5fPTs=
 github.com/issue9/term/v3 v3.3.2/go.mod h1:n5XiDESqvPeaLgHeG7H0JdIXU8/H5IZPEaH++0aHE5Q=
 github.com/issue9/unique/v2 v2.1.0 h1:lE9hstenrbBgDM/Iulv7d75qAYpgoDV8rSRoLmEBHOk=
 github.com/issue9/unique/v2 v2.1.0/go.mod h1:qZoDKnfu+7Q0yxhifVseRKD2Wea9Tc9zdXwALnFc54A=
 github.com/issue9/version v1.0.8 h1:IsNdDYdV8UGDGwwgp8H4RszJE0Ko26HjWg9pZzyOivs=
 github.com/issue9/version v1.0.8/go.mod h1:w8bQwODBOG5+iaS3qIJbElxxpp3Uo4x5F39qKBqwpdc=
-github.com/issue9/web v0.100.4 h1:JXZBwcot5cz0P/Ey31ZZJsXXbsMfw1rl8Ilg7+llEeQ=
-github.com/issue9/web v0.100.4/go.mod h1:TkimI9/soQkdihGKMh4eyChYLwrQ5IgzXV8CzLUJ04A=
+github.com/issue9/web v0.100.9 h1:BF1zdoYe6eZ7NiyY7gTKNGn6BFVoGeaeUyuALzbO1ZA=
+github.com/issue9/web v0.100.9/go.mod h1:YMjYbAeHeLROLWS8O/xifWqAxFexASCa1CMKdbZ78nc=
 github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc=
 github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw=
 github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
@@ -54,8 +54,8 @@ golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
 golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
 golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
-golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
+golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
diff --git a/sanitizer/html.go b/sanitizer/html.go
index 0655abf..a183151 100644
--- a/sanitizer/html.go
+++ b/sanitizer/html.go
@@ -11,7 +11,10 @@ import (
 	"unicode"
 )
 
-var nl2brReplacer = strings.NewReplacer("\r\n", "<br />", "\n", "<br />")
+var nl2brReplacer = strings.NewReplacer(
+	"\r\n", "<br />",
+	"\n", "<br />",
+)
 
 // NL2BR 将换行符转换为 <br />
 func NL2BR(v *string) { *v = nl2brReplacer.Replace(*v) }
@@ -58,9 +61,9 @@ LOOP:
 	*v = b.String()
 }
 
-func Escape(v *string) { *v = html.EscapeString(*v) }
+func EscapeHTML(v *string) { *v = html.EscapeString(*v) }
 
-func Unescape(v *string) { *v = html.UnescapeString(*v) }
+func UnescapeHTML(v *string) { *v = html.UnescapeString(*v) }
 
 // NL2P 将换行符转换为 <p> 包含的元素
 //
@@ -91,3 +94,11 @@ var p2nlReplacer = strings.NewReplacer(
 
 // P2NL 将 <p> 包含的元素替换为换行符
 func P2NL(v *string) { *v = p2nlReplacer.Replace(*v) }
+
+var scriptReplacer = strings.NewReplacer(
+	"<script>", "&lt;script&gt;<pre>",
+	"</script>", "</pre>&lt;/script&gt;",
+)
+
+// EscapeScript 仅转换 HTML 中 script 标签内的内容
+func EscapeScript(v *string) { *v = scriptReplacer.Replace(*v) }
diff --git a/sanitizer/html_test.go b/sanitizer/html_test.go
index 41663f4..4dc8830 100644
--- a/sanitizer/html_test.go
+++ b/sanitizer/html_test.go
@@ -97,3 +97,15 @@ func TestP2NL(t *testing.T) {
 	P2NL(&v)
 	a.Equal(v, "l1\n")
 }
+
+func TestEscapeScript(t *testing.T) {
+	a := assert.New(t, false)
+
+	v := "<p>123</p>"
+	EscapeScript(&v)
+	a.Equal(v, "<p>123</p>")
+
+	v = "<p>123</p><script>var x = 5</script>"
+	EscapeScript(&v)
+	a.Equal(v, "<p>123</p>&lt;script&gt;<pre>;var x = 5</pre>&lt;/script&gt;")
+}