diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 04d63e0e862..08af11a128c 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -545,6 +545,43 @@ proc/checkhtml(var/t)
text = copytext(text, 1, MAX_PAPER_MESSAGE_LEN)
return text
+/proc/convert_pencode_arg(text, tag, arg)
+ arg = sanitize_simple(html_encode(arg), list("''"="","\""="", "?"=""))
+ // https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#rule-4---css-escape-and-strictly-validate-before-inserting-untrusted-data-into-html-style-property-values
+ var/list/style_attacks = list("javascript:", "expression", "byond:", "file:")
+
+ for(var/style_attack in style_attacks)
+ if(findtext(arg, style_attack))
+ // Do not attempt to render dangerous things
+ return text
+
+ if(tag == "class")
+ return ""
+
+ if(tag == "style")
+ return ""
+
+ if(tag == "img")
+ var/list/img_props = splittext(arg, ";")
+ if(img_props.len == 3)
+ return "
"
+ if(img_props.len == 2)
+ return "
"
+ return "
"
+
+ return text
+
+/proc/admin_pencode_to_html()
+ var/text = pencode_to_html(arglist(args))
+ var/regex/R = new(@"\[(.*?) (.*?)\]", "ge")
+ text = R.Replace(text, /proc/convert_pencode_arg)
+
+ text = replacetext(text, "\[/class\]", "")
+ text = replacetext(text, "\[/style\]", "")
+ text = replacetext(text, "\[/img\]", "")
+
+ return text
+
/proc/html_to_pencode(text)
text = replacetext(text, "
", "\n")
text = replacetext(text, "
", "\[center\]")
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 7687b22f71c..1176e733dde 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -644,7 +644,7 @@ var/list/admin_verbs_ticket = list(
if(!message)
return
for(var/mob/V in hearers(O))
- V.show_message(message, 2)
+ V.show_message(admin_pencode_to_html(message), 2)
log_admin("[key_name(usr)] made [O] at [O.x], [O.y], [O.z] make a sound")
message_admins("[key_name_admin(usr)] made [O] at [O.x], [O.y], [O.z] make a sound")
feedback_add_details("admin_verb","MS") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index e45224b175b..be136204739 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2186,7 +2186,7 @@
if(!input)
qdel(P)
return
- input = P.parsepencode(input) // Encode everything from pencode to html
+ input = admin_pencode_to_html(html_encode(input)) // Encode everything from pencode to html
var/customname = clean_input("Pick a title for the fax.", "Fax Title", , owner)
if(!customname)
diff --git a/code/modules/admin/verbs/adminpm.dm b/code/modules/admin/verbs/adminpm.dm
index 2352c442313..e2482c54d7c 100644
--- a/code/modules/admin/verbs/adminpm.dm
+++ b/code/modules/admin/verbs/adminpm.dm
@@ -110,7 +110,7 @@
if(!msg)
return
else
- msg = pencode_to_html(msg)
+ msg = admin_pencode_to_html(msg)
var/recieve_span = "playerreply"
var/send_pm_type = " "
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index b8d93950843..2c7c6181b1d 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -58,6 +58,9 @@
if(!msg)
return
+
+ msg = admin_pencode_to_html(msg)
+
if(usr)
if(usr.client)
if(usr.client.holder)
@@ -136,7 +139,7 @@
if( !msg )
return
- msg = pencode_to_html(msg)
+ msg = admin_pencode_to_html(msg)
to_chat(M, msg)
log_admin("DirectNarrate: [key_name(usr)] to ([key_name(M)]): [msg]")