71366eb92d89bbb2cf87e2a9293ea019b63981a4
braney
  Fri Aug 21 10:22:02 2026 -0700
htmlSanitize: keep odd characters out of the messages we hand back, refs #38126

An attribute name in HTML can hold almost anything, and the name goes into a
message that hubCheck prints to a terminal.  Name the attribute only when it
reads like a name.

diff --git src/lib/htmlSanitize.c src/lib/htmlSanitize.c
index 07b85af685a..cf5f1187685 100644
--- src/lib/htmlSanitize.c
+++ src/lib/htmlSanitize.c
@@ -223,30 +223,42 @@
         }
     else if (*s == '=')
         expectValue = TRUE;
     else if (*s == '>')
         return s;
     }
 return NULL;
 }
 
 static boolean isNameChar(char c)
 /* Is c part of an element or attribute name? */
 {
 return isalnum((unsigned char)c) || c == ':' || c == '_' || c == '-' || c == '.';
 }
 
+static boolean allNameChars(char *s)
+/* Is every character in s one that belongs in a name?  A message we hand back names an
+ * attribute the page wrote, and that text can end up in a terminal. */
+{
+for (;  *s != 0;  ++s)
+    {
+    if (!isNameChar(*s))
+        return FALSE;
+    }
+return TRUE;
+}
+
 static char *tagName(char *s, char *name, int nameSize)
 /* Copy the element name starting at s into name, lower cased.  Return the first character
  * after the name. */
 {
 int i = 0;
 while (isNameChar(*s))
     {
     if (i < nameSize-1)
         name[i++] = *s;
     ++s;
     }
 name[i] = 0;
 tolowers(name);
 return s;
 }
@@ -572,31 +584,31 @@
 while ((s = nextAttribute(s, tagEnd, &name, &nameLen, &val, &valLen)) != NULL)
     {
     if (nameLen == 0 || nameLen > 128)
         continue;
     char attr[129];
     memcpy(attr, name, nameLen);
     attr[nameLen] = 0;
     tolowers(attr);
     char key[256];
     safef(key, sizeof key, "%s.%s", element, attr);
     if (hashLookup(attrHash, key) == NULL)
         {
         safef(key, sizeof key, "*.%s", attr);
         if (hashLookup(attrHash, key) == NULL)
             {
-            if (startsWith("on", attr))
+            if (startsWith("on", attr) && allNameChars(attr))
                 noteRemoved(san, "removed the attribute %s", attr);
             continue;
             }
         }
     char *value = cloneStringZ(val == NULL ? "" : val, valLen);
     if (sameString(attr, "style"))
         {
         char *style = filterStyle(value, san);
         if (style != NULL)
             {
             dyStringAppend(san->out, " style=\"");
             appendEscaped(san->out, style);
             dyStringAppendC(san->out, '"');
             freeMem(style);
             }