-
Notifications
You must be signed in to change notification settings - Fork 0
Sanitize profile HTML and CSS #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -3,6 +3,16 @@ const router = express.Router(); | |||||||||||||||||||||||||||||||||||||||||||||
| const User = require('../models/User'); | ||||||||||||||||||||||||||||||||||||||||||||||
| const ScrapyardItem = require('../models/ScrapyardItem'); | ||||||||||||||||||||||||||||||||||||||||||||||
| const Feed = require('feed').Feed; | ||||||||||||||||||||||||||||||||||||||||||||||
| let DOMPurify; | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const createDOMPurify = require('dompurify'); | ||||||||||||||||||||||||||||||||||||||||||||||
| const { JSDOM } = require('jsdom'); | ||||||||||||||||||||||||||||||||||||||||||||||
| const window = new JSDOM('').window; | ||||||||||||||||||||||||||||||||||||||||||||||
| DOMPurify = createDOMPurify(window); | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||
| console.warn('DOMPurify module not found, using fallback sanitization'); | ||||||||||||||||||||||||||||||||||||||||||||||
| DOMPurify = { sanitize: (input) => input.replace(/</g, '<').replace(/>/g, '>') }; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fallback sanitization method used here is very basic and might not be sufficient for all cases of XSS attacks. It only replaces Recommendation:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 issue (security): Fallback sanitization is too naive This method only escapes angle brackets and can be bypassed. For production, use a robust sanitizer or fail closed to ensure security. |
||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+7
to
+15
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Only fallback on missing DOMPurify dependency Check specifically for
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Middleware to ensure user is authenticated | ||||||||||||||||||||||||||||||||||||||||||||||
| const ensureAuthenticated = (req, res, next) => { | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -147,10 +157,17 @@ router.get('/edit/html', ensureAuthenticated, (req, res) => { | |||||||||||||||||||||||||||||||||||||||||||||
| router.post('/edit/html', ensureAuthenticated, async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { profileHtml } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Validate that profileHtml is a string before sanitizing Add a type check to return a 400 error if profileHtml is not a string to prevent runtime issues with invalid input. |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const sanitizedHtml = DOMPurify.sanitize(profileHtml, { | ||||||||||||||||||||||||||||||||||||||||||||||
| ALLOWED_TAGS: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'br', 'ul', 'ol', 'li', | ||||||||||||||||||||||||||||||||||||||||||||||
| 'strong', 'em', 'a', 'img', 'div', 'span', 'blockquote', 'code', 'pre'], | ||||||||||||||||||||||||||||||||||||||||||||||
| ALLOWED_ATTR: ['href', 'src', 'alt', 'title', 'class', 'id', 'style'], | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 issue (security): Allowing inline style attribute can introduce CSS-based attacks Remove 'style' from ALLOWED_ATTR or ensure CSS in style attributes is properly sanitized to mitigate CSS injection risks. |
||||||||||||||||||||||||||||||||||||||||||||||
| FORBID_TAGS: ['script', 'iframe', 'object', 'embed'], | ||||||||||||||||||||||||||||||||||||||||||||||
| FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover'] | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+160
to
+166
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 suggestion (security): Enforce safe URL protocols in HTML sanitization Configure DOMPurify with an
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Update the user's profile HTML | ||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findById(req.user._id); | ||||||||||||||||||||||||||||||||||||||||||||||
| user.profileHtml = profileHtml; | ||||||||||||||||||||||||||||||||||||||||||||||
| user.profileHtml = sanitizedHtml; | ||||||||||||||||||||||||||||||||||||||||||||||
| await user.save(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| req.flash('success_msg', 'Profile HTML updated successfully'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -179,10 +196,15 @@ router.get('/edit/css', ensureAuthenticated, (req, res) => { | |||||||||||||||||||||||||||||||||||||||||||||
| router.post('/edit/css', ensureAuthenticated, async (req, res) => { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| const { profileCss } = req.body; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| const sanitizedCss = DOMPurify.sanitize(profileCss, { | ||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🚨 issue (security): DOMPurify doesn't sanitize CSS content DOMPurify does not sanitize CSS rules. Use a dedicated CSS sanitizer to allow only safe properties, remove unsafe at-rules, and block dangerous expressions like |
||||||||||||||||||||||||||||||||||||||||||||||
| ALLOWED_TAGS: [], | ||||||||||||||||||||||||||||||||||||||||||||||
| ALLOWED_ATTR: [], | ||||||||||||||||||||||||||||||||||||||||||||||
| FORBID_TAGS: ['script', 'iframe', 'object', 'embed'] | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Update the user's profile CSS | ||||||||||||||||||||||||||||||||||||||||||||||
| const user = await User.findById(req.user._id); | ||||||||||||||||||||||||||||||||||||||||||||||
| user.profileCss = profileCss; | ||||||||||||||||||||||||||||||||||||||||||||||
| user.profileCss = sanitizedCss; | ||||||||||||||||||||||||||||||||||||||||||||||
| await user.save(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| req.flash('success_msg', 'Profile CSS updated successfully'); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (
use-object-destructuring)Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide