UNPKG

15.4 kBMarkdownView Raw
1<img align="right" src="https://cdn.rawgit.com/mikesamuel/template-tag-common/7f0159bda72d616af30645d49c3c9203c963c0a6/images/logo.png" alt="Sisyphus Logo">
2
3# Web Contract Types
4
5[![Build Status](https://travis-ci.org/mikesamuel/web-contract-types.svg?branch=master)](https://travis-ci.org/mikesamuel/web-contract-types)
6[![Dependencies Status](https://david-dm.org/mikesamuel/web-contract-types/status.svg)](https://david-dm.org/mikesamuel/web-contract-types)
7[![npm](https://img.shields.io/npm/v/web-contract-types.svg)](https://www.npmjs.com/package/web-contract-types)
8[![Coverage Status](https://coveralls.io/repos/github/mikesamuel/web-contract-types/badge.svg?branch=master)](https://coveralls.io/github/mikesamuel/web-contract-types?branch=master)
9[![Install Size](https://packagephobia.now.sh/badge?p=web-contract-types)](https://packagephobia.now.sh/result?p=web-contract-types)
10[![Known Vulnerabilities](https://snyk.io/test/github/mikesamuel/web-contract-types/badge.svg?targetFile=package.json)](https://snyk.io/test/github/mikesamuel/web-contract-types?targetFile=package.json)
11
12Security contract types for common web application languages: HTML, JavaScript, URLs.
13
14<!-- scripts/make-md-toc.pl replaces the below and test/check-markdown.js keeps this up-to-date. -->
15
16<!-- TOC -->
17
18* [Installation](#hdr-installation)
19* [Configuration](#hdr-configuration)
20 * [For applications](#hdr-for-applications)
21 * [For library authors](#hdr-for-library-authors)
22* [Contracts](#hdr-contracts)
23 * [TrustedHTML](#hdr-trustedhtml)
24 * [TrustedResourceURL](#hdr-trustedresourceurl)
25 * [TrustedScript](#hdr-trustedscript)
26 * [TrustedURL](#hdr-trustedurl)
27* [Creating Trusted values](#hdr-creating-trusted-values)
28* [Verifying Trusted values](#hdr-verifying-trusted-values)
29* [API](#hdr-api)
30 * [class TrustedHTML](#hdr-class-trustedhtml)
31 * [TrustedHTML.concat](#hdr-trustedhtml-concat)
32 * [TrustedHTML.empty](#hdr-trustedhtml-empty)
33 * [TrustedHTML.escape](#hdr-trustedhtml-escape)
34 * [TrustedHTML.fromScript](#hdr-trustedhtml-fromscript)
35 * [class TrustedResourceURL](#hdr-class-trustedresourceurl)
36 * [TrustedResourceURL.fromScript](#hdr-trustedresourceurl-fromscript)
37 * [class TrustedScript](#hdr-class-trustedscript)
38 * [TrustedScript.expressionFromJSON](#hdr-trustedscript-expressionfromjson)
39 * [class TrustedURL](#hdr-class-trustedurl)
40 * [TrustedURL.innocuousURL](#hdr-trustedurl-innocuousurl)
41 * [TrustedURL.sanitize](#hdr-trustedurl-sanitize)
42
43<!-- /TOC -->
44
45## Installation <a name="hdr-installation"></a>
46
47```bash
48$ npm install web-contract-types
49```
50
51## Configuration <a name="hdr-configuration"></a>
52
53### For applications <a name="hdr-for-applications"></a>
54
55These types are [Mintable][] so the application's main module should do some
56setup to guard which modules can create values that meet a contract.
57
58This helps an application team, in conjunction with security specialists,
59keep a bound on how much code needs review to check that contracts hold.
60
61The applications main file should do, as early as possible, something like:
62
63```js
64// In application main file.
65require('node-sec-patterns').authorize(require('./package.json'));
66```
67
68which opts into access checks for mintable type constructors, and
69tells it to use the "mintable" property of `./package.json` to
70determine which modules may create which contract types.
71
72The APIs below require access to the module's own minters, so the
73minimal additions to package.json are
74
75```json
76{
77 ...
78 "mintable": {
79 "grants": {
80 "web-contract-types/TrustedHTML": [ "web-contract-types" ],
81 "web-contract-types/TrustedResourceURL": [ "web-contract-types" ],
82 "web-contract-types/TrustedScript": [ "web-contract-types" ],
83 "web-contract-types/TrustedURL": [ "web-contract-types" ]
84 }
85 }
86}
87```
88
89This says "this application trusts module web-contract-types to mint values
90that meet the contracts "web-contract-types/TrustedHTML", etc. This relies
91on the fact that `class TrustedHTML` has a static `contractKey` property with
92the value `"web-contract-types/TrustedHTML"`.
93
94This can be a bit verbose, so if you trust the web-contract-types project and
95its development practices, you can second any grants that it self-nominates for:
96
97```json
98{
99 ...
100 "mintable": {
101 "grants": {},
102 "second": [
103 "web-contract-types"
104 ]
105 }
106}
107```
108
109This says "for each item in
110`require('web-contract-types/package.json').mintable.selfNominate` add
111`"web-contract-types"` to that contract keys grant list".
112If the seconded name ends in `.json` then `/package.json` is not implicitly
113added to the end, so module authors might provide self-nominates for differing
114levels of trust.
115
116To see what this grants you can do the below, but keep in mind that a
117package might change its self nominations in future versions so by
118seconding self-nominated grants you are expressing confidence in
119future development practices:
120
121```sh
122$ node -e 'console.log(JSON.stringify(require("web-contract-types/package.json").mintable.selfNominate, null, 2))'
123```
124
125See [Mintable][] for more details.
126
127### For library authors <a name="hdr-for-library-authors"></a>
128
129Library code should *not* call `authorize` as in the example code for
130application maintainers above.
131
132Library code may self nominate by including a list of contract keys
133that the package needs to mint values for. In package.json
134
135```js
136{
137 ...
138 "mintable": {
139 "selfNominate": [
140 "contractKey0",
141 "contractKey1"
142 ]
143 }
144}
145```
146
147Library authors are responsible for guaranteeing that they only mint
148values that meet the type's contract even when passed untrusted
149inputs.
150
151Library authors may assume that any inputs that pass a mintable types
152verifier pass that type's contract, and are not responsible for
153failure to preserve a contract given a verified input that does not
154meet its type contract.
155
156See [Mintable][] for more details.
157
158## Contracts <a name="hdr-contracts"></a>
159
160### TrustedHTML <a name="hdr-trustedhtml"></a>
161
162A string that is safe to use in HTML context in DOM APIs and HTML documents.
163
164A TrustedHTML is a string-like object that carries the security type contract
165that its value as a string will not cause untrusted script execution when
166evaluated as HTML in a browser.
167
168Values of this type are guaranteed to be safe to use in HTML contexts,
169such as, assignment to the innerHTML DOM property, or interpolation into
170a HTML template in HTML PC_DATA context, in the sense that the use will not
171result in a Cross-Site-Scripting vulnerability.
172
173Instances must be created by `Mintable.minterFor(TrustedHTML)`.
174
175When checking types, use `Mintable.verifierFor(TrustedHTML)` and do not rely on
176`instanceof`.
177
178
179### TrustedResourceURL <a name="hdr-trustedresourceurl"></a>
180
181A URL which is under application control and from which script, CSS, and
182other resources that represent executable code, can be fetched.
183
184Given that the URL can only be constructed from strings under application
185control and is used to load resources, bugs resulting in a malformed URL
186should not have a security impact and are likely to be easily detectable
187during testing. Given the wide number of non-RFC compliant URLs in use,
188stricter validation could prevent some applications from being able to use
189this type.
190
191Instances must be created by `Mintable.minterFor(TrustedResourceURL)`.
192
193When checking types, use `Mintable.verifierFor(TrustedResourceURL)` and do
194not rely on `instanceof`.
195
196### TrustedScript <a name="hdr-trustedscript"></a>
197
198A string-like object which represents JavaScript code and that carries the
199security type contract that its value, as a string, will not cause execution
200of unconstrained attacker controlled code (XSS) when evaluated as JavaScript
201in a browser.
202
203A TrustedScript's string representation can safely be interpolated as the
204content of a script element within HTML. The TrustedScript string should not be
205escaped before interpolation.
206
207Note that the TrustedScript might contain text that is attacker-controlled but
208that text should have been interpolated with appropriate escaping,
209sanitization and/or validation into the right location in the script, such
210that it is highly constrained in its effect (for example, it had to match a
211set of whitelisted words).
212
213Instances must be created by `Mintable.minterFor(TrustedScript)`.
214
215When checking types, use `Mintable.verifierFor(TrustedScript)` and do
216not rely on `instanceof`.
217
218### TrustedURL <a name="hdr-trustedurl"></a>
219
220A string that is safe to use in URL context in DOM APIs and HTML documents.
221
222A TrustedURL is a string-like object that carries the security type contract
223that its value as a string will not cause untrusted script execution
224when evaluated as a hyperlink URL in a browser.
225
226Values of this type are guaranteed to be safe to use in URL/hyperlink
227contexts, such as assignment to URL-valued DOM properties, in the sense that
228the use will not result in a Cross-Site-Scripting vulnerability. Similarly,
229TrustedURLs can be interpolated into the URL context of an HTML template (e.g.,
230inside a href attribute). However, appropriate HTML-escaping must still be
231applied.
232
233Instances must be created by `Mintable.minterFor(TrustedURL)`.
234
235When checking types, use `Mintable.verifierFor(TrustedURL)` and do not rely on
236`instanceof`.
237
238
239## Creating Trusted values <a name="hdr-creating-trusted-values"></a>
240
241```js
242require('module-keys/cjs').polyfill(module, require, module.id);
243
244const { Mintable } = require('node-sec-patterns');
245const { TrustedHTML } = require('web-contract-types');
246
247const makeTrustedHTML = require.keys.unbox(
248 Mintable.minterFor(TrustedHTML),
249 () => true,
250 String);
251```
252
253This boilerplate can be tiresome, but this should only happen in an applications
254secure kernel.
255
256Do not grant access to `makeTrustedHTML` widely. That defeats the purpose of
257guarding constructors to minimize the amount of code that could result in a
258security vulnerability.
259
260See [Mintable][] for more details.
261
262## Verifying Trusted values <a name="hdr-verifying-trusted-values"></a>
263
264Any JavaScript code that can access a class can create an object that
265is an `instanceof` that class.
266
267To prevent accepting a contract forged by code outside your secure kernel,
268check types thus:
269
270```js
271const { TrustedHTML } = require('web-contract-types');
272
273if (TrustedHTML.is(x)) {
274 // x is not a forgery
275 // May assume x meets its type contract.
276} else {
277 // Do not assume x meets the TrustedHTML type contract.
278}
279```
280
281
282## API <a name="hdr-api"></a>
283
284### class TrustedHTML <a name="hdr-class-trustedhtml"></a>
285
286The contract type for TrustedHTML. See [contract](#hdr-trustedhtml) above.
287
288### TrustedHTML.concat <a name="hdr-trustedhtml-concat"></a>
289
290```js
291const { TrustedHTML } = require('web-contract-types');
292TrustedHTML.concat(x, y, z);
293```
294
295Takes any number of *TrustedHTML* inputs and returns a *TrustedHTML* output
296whose content is the concatenation of the inputs' content.
297
298Throws a *TypeError* if any input does not verify as *TrustedHTML*
299
300### TrustedHTML.empty <a name="hdr-trustedhtml-empty"></a>
301
302A *TrustedHTML* instance that represents the empty document fragment.
303
304```js
305const { TrustedHTML } = require('web-contract-types');
306TrustedHTML.empty;
307```
308
309### TrustedHTML.escape <a name="hdr-trustedhtml-escape"></a>
310
311Given a string, returns a *TrustedHTML* instance that represents a text
312node with that textContent.
313
314Given a TrustedHTML instance, returns it unchanged.
315
316The content is equivalent to the input but with `'<'` replaced with `'&lt;'`,
317and other HTML metacharacters replaced with similar character references.
318
319```js
320const { TrustedHTML } = require('web-contract-types');
321TrustedHTML.escape('Hello, <World!>').content === 'Hello, &lt;World!&gt;';
322```
323
324### TrustedHTML.fromScript <a name="hdr-trustedhtml-fromscript"></a>
325
326```js
327const { TrustedHTML } = require('web-contract-types');
328TrustedHTML.fromScript(myTrustedResourceURL)
329```
330
331Given a *TrustedResourceURL*, returns a `TrustedHTML` instance like `<script src=...></script>`.
332
333Given a *TrustedScript*, returns a `TrustedHTML` instance like `<script>...</script>`.
334
335May also take a second options argument that allows specifying:
336
337* `type`: May be "module" to specify that the src is an ES6 module not a script
338* `defer`: If truthy, the output script element has the defer attribute.
339* `async`: If truthy, the output script element has the async attribute.
340* `nonce`: Unescaped text of a Conent-Security-Policy nonce.
341
342### class TrustedResourceURL <a name="hdr-class-trustedresourceurl"></a>
343
344The contract type for TrustedResourceURL. See [contract](#hdr-trustedresourceurl) above.
345
346### TrustedResourceURL.fromScript <a name="hdr-trustedresourceurl-fromscript"></a>
347
348```js
349const { TrustedResourceURL } = require('web-contract-types');
350
351TrustedResourceURL.fromScript(myTrustedScript)
352// ~ data:text/javascript,...
353```
354
355If the input is a *TrustedScript* returns a *TrustedResourceUrl* with scheme `data:`,
356content type text/javascript, and a data segment that is the script's content.
357
358If the input is not a *TrustedScript*, throws a *TypeError*.
359
360### class TrustedScript <a name="hdr-class-trustedscript"></a>
361
362The contract type for TrustedScript. See [contract](#hdr-trustedscript) above.
363
364### TrustedScript.expressionFromJSON <a name="hdr-trustedscript-expressionfromjson"></a>
365
366```js
367const { TrustedScript } = require('web-contract-types');
368
369const dataObject = { "foo": [ "bar" ] };
370
371TrustedScript.expressionFromJSON(dataObject)
372// ~ ({ "foo", [ "bar" ] })
373```
374
375Forwards its arguments to `JSON.stringify` and returns a *TrustedScript*
376whose content is a parenthesized JavaScript expression that produces
377similar data.
378
379It forwards all arguments, so accepts the same [optional arguments][JSON args]
380as `JSON.stringify`.
381
382* value
383* replacer
384* space
385
386It throws an exception when `JSON.stringify` does -- for example, reference cycles.
387
388### class TrustedURL <a name="hdr-class-trustedurl"></a>
389
390The contract type for TrustedURL. See [contract](#hdr-trustedurl) above.
391
392### TrustedURL.innocuousURL <a name="hdr-trustedurl-innocuousurl"></a>
393
394```js
395const { TrustedURL } = require('web-contract-types');
396TrustedURL.innocuousURL
397```
398
399A URL that will have no effect when loaded. May be used as a placeholder.
400
401### TrustedURL.sanitize <a name="hdr-trustedurl-sanitize"></a>
402
403```js
404const { TrustedURL } = require('web-contract-types');
405TrustedURL.sanitize('http://example.com/').content === 'http://example.com';
406```
407
408Given a string, returns a *TrustedURL* with that string's content if the
409string is a relative URL or has a scheme in
410
411* http
412* https
413* mailto
414* tel
415
416Given a *TrustedURL* returns its input unchanged.
417
418If the input does not pass one of the given conditions, returns its second
419argument unchanged, or if that argument is falsey, returns `TrustedURL.innocuousURL`.
420
421
422[Mintable]: https://npmjs.com/package/node-sec-patterns
423[JSON args]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Parameters