Skip to content

Fix/xss rich text and statusmessage bypass#736

Open
kouts wants to merge 2 commits into
Baroshem:mainfrom
kouts:fix/xss-rich-text-and-statusmessage-bypass
Open

Fix/xss rich text and statusmessage bypass#736
kouts wants to merge 2 commits into
Baroshem:mainfrom
kouts:fix/xss-rich-text-and-statusmessage-bypass

Conversation

@kouts

@kouts kouts commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Types of changes

  • Bug fix (a non-breaking change which fixes an issue)
  • New feature (a non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Description

Resolves: #720

Replaces the XSS validator's stringify-and-compare detection with hook-based malicious payload detection. The old approach compared xss.process(JSON.stringify(body)) to the input and rejected on any difference, which caused false positives for legitimate rich-text HTML like e.g. <p style="text-align:center">hello</p>.

The new approach uses the xss library's hooks to detect specific malicious patterns:

  • Non-whitelisted tags (<script>, <iframe>, <svg>, etc.) via onIgnoreTag
  • Event handler attributes (on*) via onIgnoreTagAttr
  • Dangerous URL protocols (javascript:, vbscript:, non-image data:) via safeAttrValue with an inline regex

Also fixes a security bypass where user-controlled statusMessage: "Bad Request" in the request body would skip XSS validation entirely.

Defense-in-depth:

  • Recursive payload traversal with depth limit (20) to prevent stack overflow on maliciously nested payloads
  • FilterXSS instance reused per request instead of per-string
  • Regex blocks data:image/svg+xml (which can embed scripts) while allowing raster image formats (png, jpeg, gif, webp)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes (if not applicable, please state why)

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

@kouts is attempting to deploy a commit to the Baroshem's projects Team on Vercel.

A member of the Team first needs to authorize it.

Comment on lines -37 to -40
// Only skip XSS filtering if statusMessage === 'Bad Request' (for error propagation)
if (valueToFilter.statusMessage === 'Bad Request') {
return
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this check because statusMessage comes from the request body, so it's user controlled. An attacker could send { "statusMessage": "Bad Request", "payload": "<script>alert(1)</script>" } and bypass XSS validation entirely.

If middleware-to-middleware signaling is needed here, event.context would be the right mechanism since it can only be set by trusted server code.


const DANGEROUS_URL_ATTRS = new Set(['href', 'src', 'background', 'style'])
const DANGEROUS_PROTOCOLS = /^\s*(?:javascript|vbscript|data\s*:\s*(?!image\/(?!svg)))/i
const MAX_DEPTH = 20

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Depth limit here prevents stack overflow on maliciously nested payloads. Returns true (conservative) when exceeded, meaning deeply nested payloads are treated as suspicious rather than silently accepted. 20 is generous enough for any real API payload while capping recursion before it can crash...

@@ -11,38 +11,19 @@ export default defineEventHandler(async(event) => {
...rules.xssValidator,
escapeHtml: undefined

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

escapeHtml doesn't affect our detection, but the XssValidator config type has it as boolean | undefined while IFilterXSSOptions expects a function type. Explicitly overriding to undefined keeps TypeScript happy without an assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

XSS validator returns 400 for valid rich-text JSON payload in POST body

1 participant