all files / esecurity/lib/middleware/ csp.js

97.06% Statements 99/102
96.63% Branches 86/89
100% Functions 8/8
97.75% Lines 87/89
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142          46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46× 46×   46×   46× 48×     46×                               46×     46×   44×   44×     41× 42×   42×   42× 66× 66×     12× 12×       44×       42×     41×   41× 41× 41× 41× 41× 41× 41× 41× 41× 41× 41×         41×   41× 39×   41×   41× 47× 47×     41×      
 
var util = require("util"),
    path = require("path"),
    fs = require("fs");
 
module.exports = function CspConstructor(opts) {
 
    opts = opts || {};
    opts.headers = opts.headers || ["standard"];
    opts.reportOnly = opts.reportOnly || false;
    opts.rules_secure = opts.rules_secure || false;
    opts.defaultSrc = opts.defaultSrc || false;
    opts.scriptSrc = opts.scriptSrc || false;
    opts.objectSrc = opts.objectSrc || false;
    opts.styleSrc = opts.styleSrc || false;
    opts.imgSrc = opts.imgSrc || false;
    opts.mediaSrc = opts.mediaSrc || false;
    opts.frameSrc = opts.frameSrc || false;
    opts.fontSrc = opts.fontSrc || false;
    opts.connectSrc = opts.connectSrc || false;
    opts.sandbox = opts.sandbox || false;
    opts.reportUri = opts.reportUri || false;
    opts.reportFilename = opts.reportFilename || "/tmp/esecurity_cspreport";
    opts.onReport = opts.onReport || onReport;
    
    if (!util.isArray(opts.headers))
        opts.headers = [opts.headers];
    
    opts.headers.forEach(function (header, key) {
        opts.headers[key] = String(header).toLowerCase();
    });
    
    if (opts.rules_secure) {
        opts.defaultSrc = ["none"];
        opts.scriptSrc = ["self", "www.google-analytics.com", "ajax.googleapis.com"];
        opts.objectSrc = false;
        opts.styleSrc = ["self"];
        opts.imgSrc = ["self"];
        opts.mediaSrc = ["self"];
        opts.frameSrc = false;
        opts.fontSrc = false;
        opts.connectSrc = ["self"];
        opts.sandbox = false;
    }
    
    function onReport(data) {
        
        if (!Object.keys(data).length)
            return;
 
        data = JSON.stringify(data || {});
        
        var msg = "[" + new Date() + "] " + data + "\n";
        
        // backward compatibility (nodejs <= 0.6)
        Iif (!fs.appendFile) {
            var report = fs.createWriteStream(opts.reportFilename, {'flags': 'a'});
            report.end(msg);
        }
        else {
            fs.appendFile(opts.reportFilename, msg, function (err) {
              Iif (err) throw err;
            });
        }
    }
    
    return function csp(req, res, next) {
        
        // self-awareness
        if (req._esecurity_csp)
            return next();
 
        req._esecurity_csp = true;
        
        if (opts.reportUri && opts.onReport && "POST" === req.method && opts.reportUri === req.path) {
            opts.onReport(req.body);
            return res.status(200).end();
        }
        
        var sanitize = function (src) {
            var srcSanitized = [];
            
            if (!util.isArray(src))
                src = [src];
            
            src.forEach(function (elem) {
                elem = String(elem).trim();
                switch (elem.toLowerCase()) {
                    case "none":
                        srcSanitized.push("'none'");
                        break;
                    case "self":
                        srcSanitized.push("'self'");
                        break;
                    case "unsafe-inline":
                        srcSanitized.push("'unsafe-inline'");
                        break;
                    case "unsafe-eval":
                        srcSanitized.push("'unsafe-eval'");
                        break;
                    default:
                        srcSanitized.push(elem);
                }
            });
            
            return srcSanitized.join(" ");
        };
        
        var cspHeader = [];
        
        if (opts.defaultSrc) cspHeader.push('default-src ' + sanitize(opts.defaultSrc));
        if (opts.scriptSrc) cspHeader.push('script-src ' + sanitize(opts.scriptSrc));
        if (opts.objectSrc) cspHeader.push('object-src ' + sanitize(opts.objectSrc));
        if (opts.styleSrc) cspHeader.push('style-src ' + sanitize(opts.styleSrc));
        if (opts.imgSrc) cspHeader.push('img-src ' + sanitize(opts.imgSrc));
        if (opts.mediaSrc) cspHeader.push('media-src ' + sanitize(opts.mediaSrc));
        if (opts.frameSrc) cspHeader.push('frame-src ' + sanitize(opts.frameSrc));
        if (opts.fontSrc) cspHeader.push('font-src ' + sanitize(opts.fontSrc));
        if (opts.connectSrc) cspHeader.push('connect-src ' + sanitize(opts.connectSrc));
        if (opts.sandbox) cspHeader.push('sandbox ' + sanitize(opts.sandbox));
        if (opts.reportUri) cspHeader.push('report-uri ' + sanitize(opts.reportUri));
        
        /*if (!cspHeader.length)
            return next();*/
            
        var headerNames = [];
        
        if (~opts.headers.indexOf("standard"))
            headerNames.push("Content-Security-Policy");
        
        if (~opts.headers.indexOf("experimental"))
            headerNames = headerNames.concat(["X-Content-Security-Policy", "X-Webkit-CSP"]);
        
        headerNames.forEach(function (header) {
            if (opts.reportOnly) header += "-Report-Only";
            res.set(header, cspHeader.join(";"));
        });
        
        return next();
    };
};