@@ -6,6 +6,8 @@ See the accompanying LICENSE file for terms.
66
77'use strict' ;
88
9+ var crypto = require ( 'crypto' ) ;
10+
911// Generate an internal UID to make the regexp pattern harder to guess.
1012var UID_LENGTH = 16 ;
1113var UID = generateUID ( ) ;
@@ -15,6 +17,8 @@ var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
1517var IS_PURE_FUNCTION = / f u n c t i o n .* ?\( / ;
1618var IS_ARROW_FUNCTION = / .* ?= > .* ?/ ;
1719var UNSAFE_CHARS_REGEXP = / [ < > \/ \u2028 \u2029 ] / g;
20+ // Regex to match </script> and </SCRIPT> (case-insensitive) for XSS protection
21+ var SCRIPT_CLOSE_REGEXP = / < \/ s c r i p t > / gi;
1822
1923var RESERVED_SYMBOLS = [ '*' , 'async' ] ;
2024
@@ -32,8 +36,23 @@ function escapeUnsafeChars(unsafeChar) {
3236 return ESCAPED_CHARS [ unsafeChar ] ;
3337}
3438
39+ // Escape function body for XSS protection while preserving arrow function syntax
40+ function escapeFunctionBody ( str ) {
41+ // Escape </script> sequences (case-insensitive) - the main XSS risk
42+ // This must be done first before other replacements
43+ str = str . replace ( SCRIPT_CLOSE_REGEXP , function ( match ) {
44+ return '\\u003C\\u002Fscript\\u003E' ;
45+ } ) ;
46+ // Also escape </SCRIPT> and other case variations
47+ str = str . replace ( / < \/ S C R I P T > / g, '\\u003C\\u002FSCRIPT\\u003E' ) ;
48+ // Escape line terminators (these are always unsafe)
49+ str = str . replace ( / \u2028 / g, '\\u2028' ) ;
50+ str = str . replace ( / \u2029 / g, '\\u2029' ) ;
51+ return str ;
52+ }
53+
3554function generateUID ( ) {
36- var bytes = crypto . getRandomValues ( new Uint8Array ( UID_LENGTH ) ) ;
55+ var bytes = crypto . randomBytes ( UID_LENGTH ) ;
3756 var result = '' ;
3857 for ( var i = 0 ; i < UID_LENGTH ; ++ i ) {
3958 result += bytes [ i ] . toString ( 16 ) ;
@@ -138,12 +157,18 @@ module.exports = function serialize(obj, options) {
138157 return value ;
139158 }
140159
141- function serializeFunc ( fn ) {
160+ function serializeFunc ( fn , options ) {
142161 var serializedFn = fn . toString ( ) ;
143162 if ( IS_NATIVE_CODE_REGEXP . test ( serializedFn ) ) {
144163 throw new TypeError ( 'Serializing native function: ' + fn . name ) ;
145164 }
146165
166+ // Escape unsafe HTML characters in function body for XSS protection
167+ // This must preserve arrow function syntax (=>) while escaping </script>
168+ if ( options && options . unsafe !== true ) {
169+ serializedFn = escapeFunctionBody ( serializedFn ) ;
170+ }
171+
147172 // pure functions, example: {key: function() {}}
148173 if ( IS_PURE_FUNCTION . test ( serializedFn ) ) {
149174 return serializedFn ;
@@ -261,6 +286,6 @@ module.exports = function serialize(obj, options) {
261286
262287 var fn = functions [ valueIndex ] ;
263288
264- return serializeFunc ( fn ) ;
289+ return serializeFunc ( fn , options ) ;
265290 } ) ;
266291}
0 commit comments