Vulnerability
Remix

XSS in Remix Applications

Remix inherits React's JSX auto-escaping but adds server-side loaders and actions that fetch and serialize data into the HTML response. If loader data contains unsanitized user content that gets rendered with dangerouslySetInnerHTML or injected into meta tags, XSS vulnerabilities emerge.

Scan Your Remix App

How XSS Manifests in Remix

Remix loader functions fetch data server-side and serialize it into the page. If a loader returns user-generated HTML content that the component renders with dangerouslySetInnerHTML, XSS is introduced. The meta() function in Remix route modules can be exploited if user data from the loader is placed directly into meta tags without sanitization. Attackers can break out of attribute contexts. Resource routes that return HTML responses (using new Response() with text/html content type) are vulnerable when they interpolate user data without encoding. Similarly, action functions that return HTML error messages can reflect user input.

Real-World Impact

A Remix e-commerce site fetched product descriptions from a database in a loader and rendered them with dangerouslySetInnerHTML for rich formatting. A compromised seller account injected a script that redirected buyers to a fake checkout page, capturing payment information for hundreds of transactions before detection.

Step-by-Step Fix

1

Sanitize loader data

Sanitize HTML content in the loader before it reaches the component.

import { json, type LoaderFunctionArgs } from '@remix-run/node';
import DOMPurify from 'isomorphic-dompurify';

export async function loader({ params }: LoaderFunctionArgs) {
  const post = await getPost(params.slug);
  return json({
    ...post,
    content: DOMPurify.sanitize(post.content, {
      ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
      ALLOWED_ATTR: ['href', 'target', 'rel'],
    }),
  });
}
2

Secure meta functions

Sanitize any user-derived data used in meta tags.

import type { MetaFunction } from '@remix-run/node';

export const meta: MetaFunction<typeof loader> = ({ data }) => {
  // Ensure no HTML/script injection in meta content
  const safeTitle = data?.title?.replace(/[<>"'&]/g, '') || '';
  const safeDesc = data?.description?.replace(/[<>"'&]/g, '') || '';
  return [
    { title: safeTitle },
    { name: 'description', content: safeDesc },
  ];
};
3

Add CSP headers in entry.server.tsx

Set Content Security Policy headers in your Remix server entry.

// entry.server.tsx
import { RemixServer } from '@remix-run/react';
import { renderToString } from 'react-dom/server';
import type { EntryContext } from '@remix-run/node';

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  const markup = renderToString(
    <RemixServer context={remixContext} url={request.url} />
  );
  responseHeaders.set('Content-Type', 'text/html');
  responseHeaders.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self'"
  );
  return new Response('<!DOCTYPE html>' + markup, {
    status: responseStatusCode,
    headers: responseHeaders,
  });
}

Prevention Best Practices

1. Sanitize all HTML content returned from loaders before rendering with dangerouslySetInnerHTML. 2. Validate and encode loader data used in meta() functions. 3. Set CSP headers using the entry.server.tsx handleRequest function. 4. Never return unsanitized user input in resource routes with HTML content type. 5. Use React components instead of raw HTML when possible.

How to Test

1. Search for dangerouslySetInnerHTML in your Remix components. 2. Trace loader data to see if user content flows into unsafe rendering. 3. Test meta() outputs by injecting " onfocus=alert(1) autofocus=" into data fields. 4. Check resource routes returning HTML for unescaped user data. 5. Use Vibe App Scanner to automatically detect XSS patterns in your Remix application.

Frequently Asked Questions

Does Remix have built-in XSS protection?

Remix uses React for rendering, so JSX auto-escaping protects text content. However, Remix adds server-side loaders and actions that can introduce XSS through data serialization. dangerouslySetInnerHTML, meta functions, and resource routes all need manual sanitization.

Are Remix loaders safe from XSS?

Loaders themselves do not cause XSS - the vulnerability occurs when loader data is rendered unsafely in components. If a loader returns HTML content that is rendered with dangerouslySetInnerHTML, XSS occurs. Sanitize HTML in the loader before returning it.

How do I add security headers in Remix?

Add headers in your entry.server.tsx handleRequest function, or use a Remix middleware/loader headers export. For CSP specifically, set the Content-Security-Policy header in the response headers before returning the rendered HTML.

Is Your Remix App Vulnerable to XSS?

VAS automatically scans for xss vulnerabilities in Remix applications and provides step-by-step remediation guidance with code examples.

Scans from $5, results in minutes. Get actionable fixes tailored to your Remix stack.