├── .gitignore ├── .jsbeautifyrc ├── .jshintrc ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── README.md ├── conf ├── env.json.dist ├── env.schema.json └── logger.json.dist ├── csp-logger.js ├── lib ├── config.js ├── healthz.js ├── index.js ├── report.js ├── server.js ├── store.js ├── stores │ ├── console.js │ ├── logger.js │ ├── mongo.js │ ├── nil.js │ └── sql.js └── test.js ├── package.json └── test └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | conf/env.json 3 | env.json 4 | conf/logger.json 5 | logger.json 6 | csp.log 7 | TODO.txt 8 | yarn.lock 9 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_size": 2, 3 | "indent_char": " ", 4 | "indent_level": 0, 5 | "indent_with_tabs": false, 6 | "preserve_newlines": true, 7 | "max_preserve_newlines": 2, 8 | "jslint_happy": true, 9 | "brace_style": "collapse", 10 | "keep_array_indentation": false, 11 | "keep_function_indentation": false, 12 | "space_before_conditional": true, 13 | "break_chained_methods": false, 14 | "eval_code": false, 15 | "unescape_strings": false, 16 | "wrap_line_length": 0 17 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "module": true, 4 | "define": true, 5 | "require": true 6 | }, 7 | "node": true, 8 | "bitwise": true, 9 | "browser": true, 10 | "curly": true, 11 | "eqeqeq": true, 12 | "freeze": true, 13 | "immed": true, 14 | "indent": 2, 15 | "latedef": true, 16 | "newcap": true, 17 | "noempty": true, 18 | "quotmark": "single", 19 | "trailing": true, 20 | "undef": true, 21 | "unused": "vars" 22 | } 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | notifications: 5 | email: false 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Community Participation Guidelines 2 | 3 | This repository is governed by Mozilla's code of conduct and etiquette guidelines. 4 | For more details, please read the 5 | [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). 6 | 7 | ## How to Report 8 | For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. 9 | 10 | 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Reporting issues 4 | 5 | - **Search for existing issues.** Please check to see if someone else has reported the same issue. 6 | - **Share as much information as possible.** Include operating system and version, browser and version. Also, include steps to reproduce the bug. 7 | 8 | ## Project Setup 9 | 10 | Refer to the [README](). 11 | 12 | ## Code Style 13 | 14 | ### JavaScript 15 | 16 | JS files must pass JSHint using the provided [.jshintrc]() settings. 17 | 18 | Additionally, JS files need to be run through [JSBeautify](https://github.com/einars/js-beautify) with the provided [.jsbeautifyrc](). 19 | 20 | **TL;DR** Run `npm run beautify` before pushing a commit. It will validate and beautify your JS. 21 | 22 | #### Variable Naming 23 | 24 | - `lowerCamelCase` General variables 25 | - `UpperCamelCase` Constructor functions 26 | 27 | ## Pull requests 28 | 29 | - Try not to pollute your pull request with unintended changes – keep them simple and small. If possible, squash your commits. 30 | - If your PR resolves an issue, include **closes #ISSUE_NUMBER** in your commit message (or a [synonym](https://help.github.com/articles/closing-issues-via-commit-messages)). 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSP Logger 2 | 3 | A basic service for logging [content security policy](https://developer.mozilla.org/en-US/docs/Security/CSP) violations. 4 | 5 | It handles saving violation reports for you. You can save logs to any SQL database or to any appender that log4js is capable of - mainly log files with rotation features and configuration. Logging to console and intercepting in your own application is supported too. 6 | 7 | ## Usage 8 | 9 | Configure your CSP to report to the `/csp` route of this service. Incoming reports will be logged to your designated storage. 10 | 11 | There are multiple ways to use csp-logger 12 | 13 | **As a comand line tool** 14 | 15 | ```bash 16 | npm install -g csp-logger 17 | csp-logger -c config.json 18 | ``` 19 | 20 | **As a standalone app** 21 | 22 | Clone this repository and run 23 | ```bash 24 | npm install . 25 | node csp-logger.js -c config.json 26 | ``` 27 | 28 | **As a module in your application** 29 | 30 | ```javascript 31 | var cspLogger = require("csp-logger")("config.json"); //accepts a matching object too 32 | 33 | cspLogger.interceptReport(function(report,req){ 34 | //this runs before the report is stored 35 | //react to CSP reports or return a modified version 36 | 37 | }); 38 | ``` 39 | 40 | ## Configuration 41 | 42 | To get an example configuration file run csp-logger with following arguments: `-c example.json --example` 43 | Configuration is a json file containing the following: 44 | 45 | - `store` (String) - Choose a storage implementation from `lib/stores`, which currently gives you the choice of `sql`, `logger`, `console` or `nil` 46 | - `domainWhitelist` (Array of Strings) - A whitelist of domains that will have CSP exceptions logged. 47 | - `sourceBlacklist` (Array of Strings) - A list of sources to block from being recorded. 48 | 49 | ### Store configurations: 50 | 51 | **logger** 52 | 53 | - `configuration` (String) - path to log4js configuration file. Logger name is `csp` 54 | 55 | **sql** 56 | 57 | - `dbDialect` (String) - Either `mysql`, `sqlite` or `postgres`. 58 | - `dbHost` (String) - SQL server hostname. 59 | - `dbName` (String) - Database name. 60 | - `dbPort` (Number) - Port number of SQL server. 61 | - `dbUsername` (String) - Username with write permissions for DB. 62 | - `dbPassword` (String) - Password. 63 | 64 | **console** 65 | 66 | Just logs with `console.warn` 67 | 68 | **nil** 69 | 70 | Does nothing (useful when csp-logger is used as a module) 71 | 72 | ## Module API 73 | 74 | The module returns a function accepting 3 arguments: 75 | 76 | | configuration | required | Configure csp-logger - string path to configuration file or object matching the expected configuration | 77 | | server to use | optional | Bind it to the same port as your app - anything that can be passed as `server` to `express().listen(server)` | 78 | | testing | optional | boolean stating if you want page throwing violations to be served at `/` for testing | 79 | 80 | ```javascript 81 | var cspLogger = require("csp-logger")("config.json", server, true); 82 | ``` 83 | 84 | ### Intercepting violation reports 85 | 86 | An initialized csp-logger instance exports two things: 87 | 88 | `cspLogger.Report` 89 | 90 | A report constructor. You can use it to base your implementation of report on it. 91 | 92 | `cspLogger.interceptReport` 93 | 94 | Sets a callback that will be called before each report is stored. 95 | If the callback returns a new object that implement `getLog` and `getRaw` methods - the new instance will be stored instead of the original report. 96 | 97 | 98 | ```javascript 99 | var cspLogger = require("csp-logger")("config.json"); 100 | 101 | function MyReport(report, username){ 102 | this.report = report; 103 | this.username = username 104 | } 105 | 106 | MyReport.prototype.getLog = function(){ 107 | var log = this.report.getLog(); 108 | log+="\n username: "+this.username; 109 | return log; 110 | }; 111 | 112 | cspLogger.interceptReport(function(report, req){ 113 | var username = getUsername(req); 114 | var myReport = new MyReport(report, username); 115 | return myReport; 116 | }); 117 | ``` 118 | 119 | Overriding `getRaw` requires the output to match SQL schema, so all modifications should be done only to existing fields. `other` field (type: TEXT) is prepared for the purpose of extensions. 120 | 121 | ## Testing your policies 122 | 123 | You can try it out with any policies by running `node csp-logger.js -c yourconfig.json --test` as it serves `test/index.html` file on the root path alongside the `/csp` route. 124 | -------------------------------------------------------------------------------- /conf/env.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "store": "logger", 3 | "logger": { 4 | "configuration": "log4js.json" 5 | }, 6 | "sql": { 7 | "dbDialect": "mysql", 8 | "dbHost": "localhost", 9 | "dbName": "csp", 10 | "dbPort": 3306, 11 | "dbUsername": "USERNAME", 12 | "dbPassword": "PASSWORD" 13 | }, 14 | "health": { 15 | "enabled": true 16 | }, 17 | "allowedOrigin": null, 18 | "port": 2600, 19 | "domainWhitelist": ["localhost:10179", "localhost:2600"], 20 | "sourceBlacklist": ["chrome-extension://gighmmpiobklfepjocnamgkkbiglidom"] 21 | } -------------------------------------------------------------------------------- /conf/env.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "$schema": "http://json-schema.org/draft-03/schema", 4 | "id": "cspLoggerEnv", 5 | "required": true, 6 | "properties": { 7 | "store": { 8 | "type": "string", 9 | "id": "store", 10 | "required": true 11 | }, 12 | "logger": { 13 | "type": "object", 14 | "id": "logger", 15 | "required": false, 16 | "properties": { 17 | "configuration": { 18 | "type": "string", 19 | "id": "configuration", 20 | "required": true 21 | } 22 | } 23 | }, 24 | "sql": { 25 | "id": "sqlDBDefinition", 26 | "type": "object", 27 | "required": false, 28 | "properties": { 29 | "dbDialect": { 30 | "type": "string", 31 | "id": "dbDialect", 32 | "required": true 33 | }, 34 | "dbHost": { 35 | "type": "string", 36 | "id": "dbHost", 37 | "required": true 38 | }, 39 | "dbName": { 40 | "type": "string", 41 | "id": "dbName", 42 | "required": true 43 | }, 44 | "dbPassword": { 45 | "type": "string", 46 | "id": "dbPassword", 47 | "required": true 48 | }, 49 | "dbPort": { 50 | "type": "number", 51 | "minimum": 1, 52 | "maximum": 65535, 53 | "id": "dbPort", 54 | "required": true 55 | }, 56 | "dbUsername": { 57 | "type": "string", 58 | "id": "dbUsername", 59 | "required": true 60 | } 61 | } 62 | }, 63 | "health": { 64 | "type": "object", 65 | "id": "health", 66 | "required": false, 67 | "properties": { 68 | "enabled": { 69 | "type": "boolean", 70 | "id": "enabled", 71 | "required": false 72 | }, 73 | "endpoint": { 74 | "type": "string", 75 | "id": "endpoint", 76 | "required": false 77 | } 78 | } 79 | }, 80 | "domainWhitelist": { 81 | "type": "array", 82 | "id": "domainWhitelist", 83 | "required": true, 84 | "items": { 85 | "type": "string", 86 | "id": "domainWhitelist/0", 87 | "required": true 88 | } 89 | }, 90 | "port": { 91 | "type": "number", 92 | "id": "port", 93 | "required": false 94 | }, 95 | "sourceBlacklist": { 96 | "type": "array", 97 | "id": "sourceBlacklist", 98 | "required": false, 99 | "items": { 100 | "type": "string", 101 | "id": "sourceBlacklist/0", 102 | "required": false 103 | } 104 | } 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /conf/logger.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "appenders": [{ 3 | "category": "csp", 4 | "type": "file", 5 | "filename": "/tmp/csp.log", 6 | "absolute": true, 7 | "maxLogSize": 10485760, 8 | "backups": 3 9 | }], 10 | "levels": { 11 | "csp": "INFO" 12 | } 13 | } -------------------------------------------------------------------------------- /csp-logger.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('optimist') 4 | .usage('Usage: $0 --config [file path]') 5 | .demand('c') 6 | .alias('c', 'config') 7 | .describe('c', 'Path to configuration file') 8 | .describe('test', 'Enables testing mode') 9 | .describe('example', 'Writes example config to given file instead of reading it and exits') 10 | .demand(['c']) 11 | .argv; 12 | 13 | if (argv.example) { 14 | var config = require('./lib/config').dropExample(argv.c); 15 | } else { 16 | var config = require('./lib/config').load(argv.c); 17 | 18 | var server = require('./lib/server').init(config); 19 | 20 | if (argv.test) { 21 | console.log('Testing page enabled'); 22 | require('./lib/test')(server); 23 | } 24 | 25 | var store = require('./lib/store').init(config); 26 | 27 | require('./lib/healthz')(server, config); 28 | 29 | server.listen(config.port || 2600, function (reportObject, req) { 30 | store.save(reportObject); 31 | }); 32 | } -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var JSV = require('JSV').JSV; 3 | var schema = require('../conf/env.schema.json'); 4 | var demoConf = require.resolve('../conf/env.json.dist'); 5 | 6 | function processConf(config, teardown) { 7 | 8 | var configValidationReport = JSV.createEnvironment().validate(config, schema); 9 | 10 | // Validate env.json based on env.schema.json 11 | if (configValidationReport.errors.length > 0) { 12 | if (teardown) { 13 | console.error('env.json is invalid'); 14 | console.error(configValidationReport.errors); 15 | process.exit(1); 16 | } else { 17 | throw new Error(configValidationReport.errors); 18 | } 19 | } 20 | 21 | return config; 22 | } 23 | 24 | module.exports = { 25 | load: function (confPath) { 26 | var config = JSON.parse(fs.readFileSync(confPath, { 27 | encoding: 'utf8' 28 | })); 29 | return processConf(config, true); 30 | }, 31 | set: function (config) { 32 | return processConf(config, false); 33 | }, 34 | dropExample: function (confPath) { 35 | fs.createReadStream(demoConf).pipe(fs.createWriteStream(confPath)); 36 | } 37 | }; -------------------------------------------------------------------------------- /lib/healthz.js: -------------------------------------------------------------------------------- 1 | module.exports = function (server, config, healthCheck) { 2 | if (config && config.health && config.health.enabled) { 3 | server.extend(function (app) { 4 | app.get(config.health.endpoint || '/healthz', function (req, res) { 5 | var health = healthCheck ? healthCheck(config) : { 6 | 'healthy': true, 7 | 'response': {} 8 | }; 9 | res.status(health.healthy ? 200 : 503).json(health.response || {}); 10 | }); 11 | }); 12 | } 13 | }; -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Report = require('./report'); 2 | 3 | module.exports = function (conf, existingServerToUse, testing) { 4 | var config; 5 | var handler; 6 | 7 | var healthCheck = function () { 8 | return { 9 | 'healthy': true, 10 | 'response': {} 11 | }; 12 | }; 13 | 14 | if (typeof conf === 'string') { 15 | config = require('./config').load(conf); 16 | } else { 17 | config = require('./config').set(conf); 18 | } 19 | 20 | var server = require('./server').init(config); 21 | if (testing) { 22 | console.log('Testing page enabled'); 23 | require('./test')(server); 24 | } 25 | var store = require('./store').init(config); 26 | 27 | require('./healthz')(server, config, function () { 28 | return healthCheck(config); 29 | }); 30 | 31 | server.listen(existingServerToUse || config.port || 2600, function (reportObject, req) { 32 | if (handler) { 33 | var reportExtended = handler(reportObject, req); 34 | if (reportExtended) { 35 | reportObject = reportExtended; 36 | } 37 | } 38 | 39 | store.save(reportObject); 40 | }); 41 | 42 | return { 43 | Report: Report, 44 | interceptReport: function (callback) { 45 | handler = callback; 46 | }, 47 | healthCheck: function (callback) { 48 | healthCheck = callback; 49 | } 50 | }; 51 | 52 | }; -------------------------------------------------------------------------------- /lib/report.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | function validate(data) { 4 | //note: browsers send different data 5 | 6 | //safety reasons, prevent DOS 7 | _.each(data, function (e) { 8 | if (e.length > 10240) { 9 | throw new TypeError('Report data item too big'); 10 | } 11 | }); 12 | //TODO: expand this if needed. What makes a complete report? 13 | if (!data['document-uri']) { 14 | throw new TypeError('Malformed report data'); 15 | } 16 | 17 | } 18 | 19 | function Report(reportData) { 20 | validate(reportData); 21 | this.data = { 22 | documentURI: reportData['document-uri'], 23 | violatedDirective: reportData['violated-directive'], 24 | originalPolicy: reportData['original-policy'], 25 | blockedURI: reportData['blocked-uri'], 26 | sourceFile: reportData['source-file'], 27 | lineNumber: reportData['line-number'], 28 | statusCode: reportData['status-code'], 29 | scriptSample: reportData['script-sample'], 30 | referrer: reportData.referrer, 31 | userAgent: reportData.userAgent, 32 | other: '' 33 | }; 34 | 35 | } 36 | 37 | Report.prototype.getSQLDefinition = function (sequelize) { 38 | return { 39 | documentURI: sequelize.STRING, 40 | violatedDirective: sequelize.STRING, 41 | originalPolicy: sequelize.TEXT, 42 | blockedURI: sequelize.STRING, 43 | sourceFile: sequelize.STRING, 44 | lineNumber: sequelize.INTEGER, 45 | statusCode: sequelize.INTEGER, 46 | scriptSample: sequelize.TEXT, 47 | referrer: sequelize.STRING, 48 | userAgent: sequelize.TEXT, 49 | other: sequelize.TEXT 50 | }; 51 | }; 52 | 53 | Report.prototype.getRaw = function () { 54 | return this.data; 55 | }; 56 | 57 | function getLogLine(text, value) { 58 | 59 | return (value) ? ' ' + text + value + '\n' : ''; 60 | 61 | } 62 | 63 | Report.prototype.getLog = function () { 64 | var logString = 'violation of [' + this.data.violatedDirective + ']' + ((this.data.originalPolicy) ? ' from [' + this.data.originalPolicy + ']' : '') + '\n' + 65 | ' on ' + this.data.documentURI + ' by ' + this.data.blockedURI + ((this.data.sourceFile) ? ' in ' + this.data.sourceFile + (this.data.lineNumber) ? ' line:' + this.data.lineNumber : '' : '') + '\n'; 66 | 67 | logString += getLogLine('script: ', this.data.scriptSample); 68 | logString += getLogLine('agent: ', this.data.userAgent); 69 | logString += getLogLine('referrer: ', this.data.referrer); 70 | 71 | return logString; 72 | }; 73 | 74 | module.exports = Report; -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var Report = require('./report'); 2 | var _ = require('lodash'); 3 | var url = require('url'); 4 | var express = require('express'); 5 | 6 | function init(config) { 7 | 8 | var app = express(); 9 | var storeViolation; 10 | 11 | function isAllowed(domain, source) { 12 | return ( 13 | _.includes(config.domainWhitelist, domain) && !_.includes(config.sourceBlacklist, source) 14 | ); 15 | } 16 | 17 | //CORS is not the best of ideas for CSP logging server 18 | //It allows easy DDOS to cover other malicious activity 19 | //TODO: consider removing this functionality at all 20 | if (config.allowedOriginString) { 21 | app.all('*', function (req, res, next) { 22 | res.header('Access-Control-Allow-Origin', config.allowedOrigin); 23 | res.header('Access-Control-Allow-Headers', 'X-Requested-With'); 24 | next(); 25 | }); 26 | } 27 | 28 | //old .json middleware failed on application/csp-report content type from chrome 29 | //also, express4 doesn't ship with middlewares anymore 30 | app.use('/csp', function (req, res, next) { 31 | var text = ''; 32 | req.setEncoding('utf8'); 33 | req.on('data', function (chunk) { 34 | text += chunk; 35 | }); 36 | req.on('end', function () { 37 | try { 38 | req.body = JSON.parse(text); 39 | } catch (e) { 40 | //no body for you 41 | } 42 | next(); 43 | }); 44 | }); 45 | 46 | app.post('/csp', function (req, res) { 47 | //console.log(req.headers, req.body); 48 | var userAgent = req.headers['user-agent']; 49 | var reportData; 50 | var report; 51 | var violatorDomain; 52 | var bodyObj = req.body; 53 | 54 | if (bodyObj && bodyObj['csp-report']) { 55 | reportData = bodyObj['csp-report']; 56 | violatorDomain = url.parse(reportData['document-uri']).host; 57 | 58 | if (isAllowed(violatorDomain, reportData['source-file'])) { 59 | reportData.userAgent = userAgent; 60 | report = new Report(reportData); 61 | 62 | if (storeViolation) { 63 | storeViolation(report, req); 64 | } 65 | } 66 | 67 | } 68 | 69 | res.end(); 70 | 71 | }); 72 | 73 | return { 74 | extend: function (cb) { 75 | cb(app); 76 | }, 77 | listen: function (port, callback) { 78 | storeViolation = callback; 79 | app.listen(port); 80 | console.log('Server listening on port ' + port); 81 | } 82 | }; 83 | 84 | } 85 | 86 | module.exports = { 87 | init: init 88 | }; -------------------------------------------------------------------------------- /lib/store.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | init: function (config) { 3 | var store; 4 | 5 | switch (config.store) { 6 | case 'sql': 7 | store = require('./stores/sql')(config); 8 | break; 9 | case 'mongo': 10 | store = require('./stores/mongo')(config); 11 | break; 12 | case 'logger': 13 | store = require('./stores/logger')(config); 14 | break; 15 | case 'console': 16 | store = require('./stores/console')(config); 17 | break; 18 | case 'disabled': 19 | store = require('./stores/nil')(config); 20 | break; 21 | default: 22 | console.log('Defaulting to console logger'); 23 | store = require('./stores/console')(config); 24 | } 25 | 26 | return store; 27 | 28 | } 29 | }; -------------------------------------------------------------------------------- /lib/stores/console.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | 3 | return { 4 | save: function (report) { 5 | //I hardcoded log level to warn, because that's what csp log is. 6 | //Proper configuration will allow mixing it into a file with other logs 7 | console.warn(report.getLog()); 8 | } 9 | }; 10 | }; -------------------------------------------------------------------------------- /lib/stores/logger.js: -------------------------------------------------------------------------------- 1 | var log4js = require('log4js'); 2 | 3 | module.exports = function (config) { 4 | //loads configuration from a given external file. 5 | //log4js has a nice feature that it reloads theconfiguration if it changes while the app is running 6 | //TODO: check log4js for memory leaks, it used to have some 7 | log4js.configure(config.logger.configuration); 8 | 9 | var logger = log4js.getLogger('csp'); 10 | 11 | return { 12 | save: function (report) { 13 | //I hardcoded log level to warn, because that's what csp log is. 14 | //Proper configuration will allow mixing it into a file with other logs 15 | logger.warn(report.getLog()); 16 | } 17 | }; 18 | }; -------------------------------------------------------------------------------- /lib/stores/mongo.js: -------------------------------------------------------------------------------- 1 | var MongoClient = require('mongodb').MongoClient; 2 | 3 | module.exports = function (config) { 4 | var url = 'mongodb://' + config.sql.dbHost + ':' + config.sql.dbPort; 5 | 6 | MongoClient.connect(url, function(err, db) { 7 | if (err) { 8 | throw err; 9 | } 10 | 11 | var dbo = db.db(config.sql.dbName); 12 | dbo.createCollection('violations', function(err, res) { 13 | if (err) { 14 | throw err; 15 | } 16 | 17 | console.log('Collection created!'); 18 | db.close(); 19 | }); 20 | }); 21 | 22 | return { 23 | save: function (report) { 24 | 25 | MongoClient.connect(url, function(err, db) { 26 | if (err) { 27 | throw err; 28 | } 29 | 30 | var dbo = db.db(config.sql.dbName); 31 | 32 | dbo.collection('violations').insertOne(report, function(err, res) { 33 | if (err) { 34 | throw err; 35 | } 36 | 37 | console.log('1 document inserted'); 38 | db.close(); 39 | }); 40 | }); 41 | } 42 | }; 43 | }; -------------------------------------------------------------------------------- /lib/stores/nil.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | return { 3 | save: function (report) { 4 | //nothing! 5 | } 6 | }; 7 | }; -------------------------------------------------------------------------------- /lib/stores/sql.js: -------------------------------------------------------------------------------- 1 | var Sequelize = require('sequelize'); 2 | var Report = require('../report'); 3 | 4 | module.exports = function (config) { 5 | var sequelizeDB = new Sequelize(config.sql.dbName, config.sql.dbUsername, config.sql.dbPassword, { 6 | host: config.sql.dbHost, 7 | dialect: config.sql.dbDialect, 8 | port: config.sql.dbPort 9 | }); 10 | 11 | // Authenticate and connect to DB 12 | sequelizeDB 13 | .authenticate() 14 | .then(function () { 15 | console.log('Connection has been established successfully.'); 16 | }) 17 | .catch(function (err) { 18 | console.error('Unable to connect to the database:', err); 19 | }); 20 | 21 | // Define schema 22 | var cspViolation = sequelizeDB.define('cspViolation', Report.prototype.getSQLDefinition(Sequelize)); 23 | 24 | // Create table 25 | sequelizeDB.sync({}).then(function (err) { 26 | console.log('Table created.'); 27 | }) 28 | .catch(function (err) { 29 | console.error('An error occurred while creating the table.'); 30 | }); 31 | 32 | function storeViolation(report) { 33 | // Directly create record in DB 34 | cspViolation.create(report.getRaw()).then(function () { 35 | console.log('Violation stored.'); 36 | }); 37 | } 38 | return { 39 | save: storeViolation 40 | }; 41 | }; 42 | -------------------------------------------------------------------------------- /lib/test.js: -------------------------------------------------------------------------------- 1 | var filename = require.resolve('../test/index.html'); 2 | module.exports = function (server) { 3 | server.extend(function (app) { 4 | app.get('/', function (req, res) { 5 | res.header('Content-Security-Policy-Report-Only', 'default-src "self"; report-uri /csp;'); 6 | res.status(200).sendfile(filename); 7 | }); 8 | 9 | }); 10 | 11 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "csp-logger", 3 | "version": "0.4.0", 4 | "description": "Content Security Policy Logging Service", 5 | "main": "lib/index.js", 6 | "bin": { 7 | "csp-logger": "csp-logger.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/mozilla/csp-logger.git" 12 | }, 13 | "scripts": { 14 | "test": "npm run lint", 15 | "beautify": "find lib/ conf/ csp-logger.js -name '*.js*' -print0 | xargs -0 js-beautify -r --config .jsbeautifyrc", 16 | "lint": "find lib/ csp-logger.js -name '*.js' -print0 | xargs -0 jshint --config .jshintrc" 17 | }, 18 | "keywords": [ 19 | "csp", 20 | "security", 21 | "content security policy" 22 | ], 23 | "author": "Mozilla", 24 | "contributors": [ 25 | { 26 | "name": "Gavin Lazar Suntop", 27 | "email": "gavin@gsuntop.com" 28 | }, 29 | { 30 | "name": "Zbyszek Tenerowicz", 31 | "email": "naugtur@gmail.com" 32 | }, 33 | { 34 | "name": "kpcyrd", 35 | "email": "git@rxv.cc" 36 | } 37 | ], 38 | "bugs": { 39 | "url": "https://github.com/mozilla/csp-logger/issues" 40 | }, 41 | "homepage": "https://github.com/mozilla/csp-logger", 42 | "dependencies": { 43 | "JSV": "~4.0.2", 44 | "express": "^4.4.0", 45 | "lodash": "^4.17.15", 46 | "log4js": "^5.1.0", 47 | "mongodb": "^3.3.2", 48 | "mysql2": "^1.7.0", 49 | "optimist": "^0.6.1", 50 | "sequelize": "^5.18.4" 51 | }, 52 | "devDependencies": { 53 | "js-beautify": "^1.6.11", 54 | "jshint": "^2.9.4", 55 | "pre-commit": "^1.2.2" 56 | }, 57 | "license": "http://www.mozilla.org/MPL/2.0/", 58 | "engines": { 59 | "node": ">=10.16.3" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |