Skip to content

Instantly share code, notes, and snippets.

@ericf
Last active August 29, 2015 14:05
Show Gist options
  • Save ericf/d70ff68ec33497f0f211 to your computer and use it in GitHub Desktop.
Save ericf/d70ff68ec33497f0f211 to your computer and use it in GitHub Desktop.

Revisions

  1. ericf revised this gist Aug 22, 2014. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions CSP + SPDY Push.md
    Original file line number Diff line number Diff line change
    @@ -16,9 +16,9 @@ Our front-end server do **not** use sessions, and this configuration and initial

    ### CSP 1 --> Level 2

    There exist issues and non-backwards compatible changes to CSP in Level 2 that will adversely affect CSP Level 1 browsers. CSP Level 2 allows for inline `<script>` elements that contain a `nonce` attribute or the hash of their contents as a source in the `Content-Security-Policy` HTTP header. This is not backwards compatible with CSP Level 1 since there is no way to whitelist these `<script>` elements.
    ~~There exist issues and non-backwards compatible changes to CSP in Level 2 that will adversely affect CSP Level 1 browsers.~~ CSP Level 2 allows for inline `<script>` elements that contain a `nonce` attribute or the hash of their contents as a source in the `Content-Security-Policy` HTTP header. ~~This is not backwards compatible with CSP Level 1 since there is no way to whitelist these `<script>` elements.~~ To be backwards compatible with CSP Level 1, `script-src 'unsafe-inline'` can be added to the CSP header and if there also is a `'nonce-'` or `'sha256-'` source, it will negate `'unsafe-inline'` in CSP Level 2 browsers.

    To us, this means that we must choose a solution that is compatible with both CSP Level 1 and Level 2 in order to have the greatest assurnace that our app's will function properly.
    To us, this means that we must choose a solution that is compatible with both CSP Level 1 and Level 2 in order to have the greatest assurance that our app's will function properly. The approach of using both `'unsafe-inline'` _with_ `'sha265-'` should work.

    **Is there a way to detect a client's CSP level of support on the server?**

  2. ericf revised this gist Aug 21, 2014. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions CSP + SPDY Push.md
    Original file line number Diff line number Diff line change
    @@ -20,6 +20,8 @@ There exist issues and non-backwards compatible changes to CSP in Level 2 that w

    To us, this means that we must choose a solution that is compatible with both CSP Level 1 and Level 2 in order to have the greatest assurnace that our app's will function properly.

    **Is there a way to detect a client's CSP level of support on the server?**


    ## SPDY Push

  3. ericf revised this gist Aug 21, 2014. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions CSP + SPDY Push.md
    Original file line number Diff line number Diff line change
    @@ -14,6 +14,12 @@ When we create the initial rendering of app on the server, we are using [Express

    Our front-end server do **not** use sessions, and this configuration and initial state data we need to pass to the client needs to be tied to the HTML document. Therefore we need a way to externalize our dynamiclly generated per-page/user JavaScript, while keeping it tightly linked to the HTML document response we're sending the to browser.

    ### CSP 1 --> Level 2

    There exist issues and non-backwards compatible changes to CSP in Level 2 that will adversely affect CSP Level 1 browsers. CSP Level 2 allows for inline `<script>` elements that contain a `nonce` attribute or the hash of their contents as a source in the `Content-Security-Policy` HTTP header. This is not backwards compatible with CSP Level 1 since there is no way to whitelist these `<script>` elements.

    To us, this means that we must choose a solution that is compatible with both CSP Level 1 and Level 2 in order to have the greatest assurnace that our app's will function properly.


    ## SPDY Push

  4. ericf created this gist Aug 21, 2014.
    131 changes: 131 additions & 0 deletions CSP + SPDY Push.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,131 @@
    SPDY Push for Externalizing Dynamic Inline Scripts for CSP
    ==========================================================

    This is a rough outline of some thoughts on how to leverage [SPDY Push][SPDY] externalize inline `<script>`s that contain dynamic, page/user-specific configuration and state data to align with [CSP][].


    ## Problem

    We are building web apps where the initial rendering (HTML) is built server-side, and in order for the client-side JavaScript app to take over, the app's configuration and initial state are written to an inline `<script>` element.

    We want to align with CSP and remove all inline `<script>` elements — or nonce/hash our inline `<script>` elements for [CSP Level 2][].

    When we create the initial rendering of app on the server, we are using [Express State][] to pass configuration data and the inital-state used to render the initial view to the client. Express State does this by dynamically generating JavaScript which is then put into an inline `<script>` element.

    Our front-end server do **not** use sessions, and this configuration and initial state data we need to pass to the client needs to be tied to the HTML document. Therefore we need a way to externalize our dynamiclly generated per-page/user JavaScript, while keeping it tightly linked to the HTML document response we're sending the to browser.


    ## SPDY Push

    SPDY and [HTTP/2][] specify a mechanism for servers to [pre-emptively *push* responses][server-push] for resources that will be requested from the initially requested resource; e.g., a server can push the response body of an external `<script>` that the original HTML document being requested will end up requesting.

    This server push mechanism allows us to externalize our dynamically generated JavaScript for CSP, while also tying it to the original request for the HTML document — all without needing to use sessions or hacks.


    ## Example

    Here is some example code for an [Express][] app using [node-spdy][] and [a modified version of Express State][express-state-no-locals]:

    **server.js:**

    ```js
    'use strict';

    var express = require('express'),
    exphbs = require('express-handlebars'),
    expstate = require('express-state'),
    csp = require('content-security-policy'),
    fs = require('fs'),
    logger = require('morgan'),
    spdy = require('spdy');

    // -- Setup Express App --------------------------------------------------------

    var app = express(),
    router = express.Router();

    expstate.extend(app);

    app.engine('hbs', exphbs({
    defaultLayout: 'main',
    extname : 'hbs'
    }));

    app.set('view engine', 'hbs');
    app.set('state namespace', 'APP');
    app.set('title', 'Express SPDY Test');

    // -- Middleware ---------------------------------------------------------------

    app.use(logger('tiny'));
    app.use(csp.getCSP({'default-src': csp.SRC_SELF}));
    app.use(router);
    app.use(express.static('./public'));

    // -- Routes -------------------------------------------------------------------

    router.get('/', function (req, res, next) {
    if (req.isSpdy) {
    res.locals.isSpdy = true;

    res.expose('This content was SPDY Push-ed!', 'spdy');

    res.push('/bootstrap.js', {
    'Content-Type': 'application/javascript'
    }).on('acknowledge', function () {
    this.end(res.exposed.state.toString());
    });
    }

    res.render('home');
    });

    // -- Locals -------------------------------------------------------------------

    app.locals.title = app.get('title');
    app.expose(app.get('title'), 'title');

    // -- SPDY Server --------------------------------------------------------------

    var server = spdy.createServer({
    key : fs.readFileSync('./keys/spdy-key.pem'),
    cert: fs.readFileSync('./keys/spdy-cert.pem')
    }, app);

    server.listen(3000, function () {
    console.log('express-spdy listening on: %d', 3000);
    });
    ```

    **main.hbs:**

    ```handlebars
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <title>{{title}}</title>
    </head>
    <body>
    {{{body}}}
    {{#if isSpdy}}
    <script src="/bootstrap.js"></script>
    {{/if}}
    <script src="/app.js"></script>
    </body>
    </html>
    ```


    [CSP]: http://www.w3.org/TR/CSP/
    [CSP Level 2]: http://www.w3.org/TR/CSP11/
    [Express]: http://expressjs.com
    [Express State]: https://github.com/yahoo/express-state
    [express-state-no-locals]: https://github.com/yahoo/express-state/tree/no-locals
    [HTTP/2]: http://http2.github.io/http2-spec/
    [node-spdy]: https://github.com/indutny/node-spdy
    [server-push]: http://http2.github.io/http2-spec/#PushResources
    [SPDY]: http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3