Instantly share code, notes, and snippets.
Last active
September 27, 2025 03:01
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save saeedvir/d9b80ff857481dcce26b7610442dfb3f to your computer and use it in GitHub Desktop.
secured and improved version of strip_tags php function in javascript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
php version : https://gist.github.com/saeedvir/f00d76c33a95c1fbecb297df4a286ebc | |
*/ | |
function strip_tags(input, allowed) { | |
if (input == null || input === '') return ''; | |
// Convert to string and handle non-string inputs safely | |
const str = String(input); | |
// Parse allowed tags into a Set for faster lookup | |
const allowedTags = new Set(); | |
if (allowed) { | |
const matches = String(allowed).toLowerCase().match(/<([a-z][a-z0-9]*)\b[^>]*>/g) || []; | |
matches.forEach(tag => { | |
const tagName = tag.match(/<([a-z][a-z0-9]*)/i); | |
if (tagName && tagName[1]) { | |
allowedTags.add(tagName[1].toLowerCase()); | |
} | |
}); | |
} | |
// Improved regex patterns | |
const tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi; | |
const commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi; | |
const scriptStyleTags = /<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>/gi; | |
// Remove dangerous content first | |
let result = str.replace(commentsAndPhpTags, '') | |
.replace(scriptStyleTags, ''); | |
// Process remaining tags | |
result = result.replace(tags, function ($0, $1) { | |
if (!$1) return ''; // No tag name found | |
const tagName = $1.toLowerCase(); | |
const isAllowed = allowedTags.has(tagName); | |
if (!isAllowed) return ''; | |
// Secure the allowed tag by removing dangerous attributes | |
return secureTag($0, tagName); | |
}); | |
return result; | |
} | |
// Helper function to secure allowed tags by removing dangerous attributes | |
function secureTag(tag, tagName) { | |
// Self-closing tags don't need processing | |
if (tag.endsWith('/>')) { | |
return removeDangerousAttributes(tag, tagName); | |
} | |
// For opening tags, remove dangerous attributes | |
if (tag.startsWith('</')) { | |
return tag; // Closing tags are safe | |
} | |
return removeDangerousAttributes(tag, tagName); | |
} | |
function removeDangerousAttributes(tag, tagName) { | |
// List of dangerous attributes and protocols | |
const dangerousAttrs = [ | |
'onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', | |
'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', | |
'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', | |
'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', | |
'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', | |
'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', | |
'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', | |
'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', | |
'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', | |
'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', | |
'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', | |
'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', | |
'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', | |
'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', | |
'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', | |
'onsubmit', 'onunload', 'fscommand', 'seeksegmenttime' | |
]; | |
const dangerousProtocols = ['javascript:', 'vbscript:', 'data:', 'livescript:']; | |
// Remove event handlers and dangerous attributes | |
let safeTag = tag.replace(/\s+([a-z][a-z0-9_-]*)\s*=\s*("[^"]*"|'[^']*'|[^"'\s>]*)/gi, | |
function (match, attrName, attrValue) { | |
attrName = attrName.toLowerCase(); | |
// Remove event handlers | |
if (dangerousAttrs.includes(attrName)) { | |
return ''; | |
} | |
// Remove dangerous protocols from href, src, action, etc. | |
if (attrName === 'href' || attrName === 'src' || attrName === 'action' || attrName === 'background') { | |
const value = attrValue.replace(/^["']|["']$/g, '').toLowerCase(); | |
if (dangerousProtocols.some(proto => value.startsWith(proto))) { | |
return ''; | |
} | |
} | |
// Remove style attribute with dangerous expressions | |
if (attrName === 'style') { | |
const styleValue = attrValue.replace(/^["']|["']$/g, ''); | |
if (/(expression|javascript|vbscript|url\s*\([^)]*javascript:)/i.test(styleValue)) { | |
return ''; | |
} | |
} | |
return match; | |
}); | |
// Remove any remaining event handlers that might have been missed | |
dangerousAttrs.forEach(attr => { | |
const regex = new RegExp(`\\s+${attr}\\s*=\\s*("[^"]*"|'[^']*'|[^"'\s>]*)`, 'gi'); | |
safeTag = safeTag.replace(regex, ''); | |
}); | |
return safeTag; | |
} | |
// Enhanced version with additional security features | |
function strip_tags_secure(input, allowed, options = {}) { | |
const defaults = { | |
encodeUnsafeChars: true, | |
removeEmptyTags: true, | |
preserveContent: true | |
}; | |
const config = { ...defaults, ...options }; | |
let result = strip_tags(input, allowed); | |
// Additional security measures | |
if (config.encodeUnsafeChars) { | |
// Encode potentially dangerous characters that could be used in XSS | |
result = result.replace(/</g, '<') | |
.replace(/>/g, '>') | |
.replace(/"/g, '"') | |
.replace(/'/g, ''') | |
.replace(/\//g, '/'); | |
} | |
if (config.removeEmptyTags) { | |
// Remove empty tags that might be used for injection | |
result = result.replace(/<([a-z][a-z0-9]*)([^>]*)\s*>\s*<\/\1>/gi, ''); | |
} | |
return result; | |
} | |
// Basic usage (same as original) | |
strip_tags('<p>Hello <b>world</b></p>', '<p><b>'); | |
// Secure version with additional protection | |
strip_tags_secure( | |
'<p onclick="alert(\'xss\')">Hello <img src="javascript:alert(1)"></p>', | |
'<p><img>', | |
{ encodeUnsafeChars: true } | |
); | |
// Test against common XSS vectors | |
const xssTests = [ | |
'<script>alert("xss")</script>', | |
'<img src="x" onerror="alert(1)">', | |
'<p style="expression(alert(1))">Test</p>', | |
'<a href="javascript:alert(1)">Click</a>' | |
]; | |
xssTests.forEach(test => { | |
console.log(strip_tags(test, '<p><img><a>')); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment