Skip to content

feat(security): add XSS sanitization and security headers#1455

Open
kelvinkipruto wants to merge 8 commits intomainfrom
ft/security-fixes
Open

feat(security): add XSS sanitization and security headers#1455
kelvinkipruto wants to merge 8 commits intomainfrom
ft/security-fixes

Conversation

@kelvinkipruto
Copy link
Copy Markdown
Contributor

Description

  • Sanitize user-provided embed HTML with xss library
  • Add Content-Security-Policy and other security headers
  • Normalize CORS/CSRF origins and set default allowed origin
  • Update HelplineCard, RowCard, and ActionBanner components to use sanitized embed code

Fixes # (issue)

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

Screenshots

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation

- Sanitize user-provided embed HTML with xss library
- Add Content-Security-Policy and other security headers
- Normalize CORS/CSRF origins and set default allowed origin
- Update HelplineCard, RowCard, and ActionBanner components to use sanitized embed code
@kilemensi
Copy link
Copy Markdown
Member

It's cool to sanitize @kelvinkipruto if it's not a lot of work. Remember, we're not getting content from end users i.e. the web like reddit. We're rendering content from a CMS managed and reviewed by trusted content editors.

@kelvinkipruto kelvinkipruto marked this pull request as ready for review April 17, 2026 12:44
@kelvinkipruto
Copy link
Copy Markdown
Contributor Author

@codex @claude Review

@claude

This comment was marked as resolved.

@kilemensi
Copy link
Copy Markdown
Member

Remember, we're not getting content from end users i.e. the web like reddit. We're rendering content from a CMS managed and reviewed by trusted content editors.

My question still remains @kelvinkipruto , why go through all of this if the content comes from our CMS which we have full control of? The content doesn't come from the wild web out there.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

The xss library is no longer needed as we rely on CSP headers for security.
This removes the dependency, the sanitizeEmbedHtml utility, and updates
components to use raw embed code. Also makes HSTS header opt-in via
environment variable to support HTTP-only preview deployments.
@kelvinkipruto
Copy link
Copy Markdown
Contributor Author

@claude @codex review

@claude

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

refactor(security): update CSP comment for clarity
chore(deps): remove unused xss dependency and update lockfile
@kelvinkipruto
Copy link
Copy Markdown
Contributor Author

@codex @claude Review

@claude

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Member

@kilemensi kilemensi left a comment

Choose a reason for hiding this comment

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

LGTM


May be we should links/description (one line) for each security setting we're doing so that it's easier to know why the settings are the way they are?

maxWidth={false}
onClose={onClose}
open={Boolean(embedCode && open)}
open={Boolean(hasEmbed && open)}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't this used inside HelplineCard and we've already checked embedCode, etc. there?

Comment on lines +28 to +29
`script-src ${scriptSrc.join(" ")}`,
"style-src 'self' 'unsafe-inline' https:",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Aren't script-src and style-src just the same value?

// we are ready to harden `script-src` further.
`script-src ${scriptSrc.join(" ")}`,
"style-src 'self' 'unsafe-inline' https:",
].join("; ");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why not just use

  `
    ...;
    ...;
  `

on the whole thing instead of

  [
    '...',
    '...',
  ].join('; ')

?

}

function isExplicitlyEnabled(value) {
return ["1", "true", "yes"].includes(value?.trim()?.toLowerCase());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lets just use true/false... that's what all other env vars use.

},
];

if (nodeEnv === "production" && enableHsts) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't you have isProduction already at the top?


const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const defaultAllowedOrigin = site.url.replace(/\/+$/, "");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
const defaultAllowedOrigin = site.url.replace(/\/+$/, "");
const serverUrl = normalizeOrigins(site.url);
  1. I think serverUrl is much clearer than defaultAllowedOrigin
  2. Use the created normalizeOrigin to trim trailing / (should it be normalize or normalise)?

process.env.PAYLOAD_CSRF?.split(",")
.map((d) => d.trim())
.filter(Boolean) ?? [];
const cors = normalizeOrigins(process.env.PAYLOAD_CORS);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

May be:

Suggested change
const cors = normalizeOrigins(process.env.PAYLOAD_CORS);
const cors = normalizeOrigins(process.env.PAYLOAD_CORS?.trim() || serverUrl);

So that there is no need to check for if the return array is empty later on?

@kilemensi
Copy link
Copy Markdown
Member

Please update the title / description as we're no longer doing XSS @kelvinkipruto

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.

3 participants