zsyp 0.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +59 -16
- package/index.js +4 -13
- package/lib/app.js +21 -0
- package/lib/filter.js +4 -5
- package/lib/from.js +3 -2
- package/lib/logger.js +11 -4
- package/lib/router.js +44 -0
- package/package.json +4 -3
- package/History.md +0 -10
- package/lib/csp.js +0 -36
package/Readme.md
CHANGED
|
@@ -1,38 +1,81 @@
|
|
|
1
1
|
[![NPM version][npm-image]][npm-url]
|
|
2
|
-
[![Build Status][
|
|
2
|
+
[![Build Status][build-image]][build-url]
|
|
3
3
|
[![Dependency Status][deps-image]][deps-url]
|
|
4
|
-
[![Dev Dependency Status][deps-dev-image]][deps-dev-url]
|
|
5
4
|
|
|
6
5
|
# zsyp
|
|
7
6
|
|
|
8
|
-
CSP violation reports logger.
|
|
7
|
+
[CSP] violation reports logger. Zsyp is a simple standalone web service that parses CPS violation reports
|
|
8
|
+
and stores them in MongoDB collection.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
12
|
```sh
|
|
13
|
-
|
|
13
|
+
npm install --global zsyp
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Environment
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
var zsyp = require('zsyp');
|
|
18
|
+
Zsyp is using [dotenv] and by default reads its environment from `/etc/default/zsyp`
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
- `ZSYP_PORT` - port number on which, defaults to 3090
|
|
21
|
+
- `ZSYP_DB` - [mongo URI] connection string, defaults to `mongodb://localhost/zsyp`
|
|
22
|
+
- `ZSYP_DOMAINS` - domain name or a regular expression used to filter CSP violation reports - can be left empty in which case all reports for all domains are logged
|
|
23
|
+
|
|
24
|
+
## Report format
|
|
25
|
+
|
|
26
|
+
```json5
|
|
27
|
+
{
|
|
28
|
+
"from": {
|
|
29
|
+
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS...", // User-Agent string
|
|
30
|
+
"browser": { // browser brand and version
|
|
31
|
+
"name": "Safari",
|
|
32
|
+
"version": "13"
|
|
33
|
+
},
|
|
34
|
+
"os": { // operating system info
|
|
35
|
+
"name": "Mac OS X",
|
|
36
|
+
"version": "10"
|
|
37
|
+
},
|
|
38
|
+
"ip": "1.2.3.4" // originator IP address
|
|
39
|
+
},
|
|
40
|
+
"csp-report": { // original CSP report
|
|
41
|
+
"document-uri": "https://example.com/page",
|
|
42
|
+
"referrer": "https://example.com/",
|
|
43
|
+
"violated-directive": "...",
|
|
44
|
+
"effective-directive": "...",
|
|
45
|
+
"original-policy": "...",
|
|
46
|
+
"blocked-uri": "",
|
|
47
|
+
"status-code": 0,
|
|
48
|
+
"source-file": "..."
|
|
49
|
+
}
|
|
50
|
+
}
|
|
22
51
|
```
|
|
23
52
|
|
|
53
|
+
|
|
54
|
+
## Logger
|
|
55
|
+
|
|
56
|
+
Reports are stored in `csp` collection. If you want to use [capped collection] create it
|
|
57
|
+
manually before running zsyp.
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
db.createCollection( "csp", { capped: true, size: 100000 } );
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
|
|
24
64
|
## License
|
|
25
65
|
|
|
26
66
|
MIT © [Damian Krzeminski](https://pirxpilot.me)
|
|
27
67
|
|
|
28
|
-
[npm-image]: https://img.shields.io/npm/v/zsyp.svg
|
|
29
|
-
[npm-url]: https://npmjs.org/package/zsyp
|
|
30
68
|
|
|
31
|
-
[
|
|
32
|
-
[
|
|
69
|
+
[CSP]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
|
70
|
+
[mongo URI]: https://docs.mongodb.com/manual/reference/connection-string
|
|
71
|
+
[capped collection]: https://docs.mongodb.com/manual/core/capped-collections/
|
|
72
|
+
[dotenv]: https://www.npmjs.com/package/dotenv
|
|
73
|
+
|
|
74
|
+
[npm-image]: https://img.shields.io/npm/v/zsyp
|
|
75
|
+
[npm-url]: https://npmjs.org/package/zsyp
|
|
33
76
|
|
|
34
|
-
[
|
|
35
|
-
[
|
|
77
|
+
[build-url]: https://github.com/pirxpilot/zsyp/actions/workflows/check.yaml
|
|
78
|
+
[build-image]: https://img.shields.io/github/workflow/status/pirxpilot/zsyp/check
|
|
36
79
|
|
|
37
|
-
[deps-
|
|
38
|
-
[deps-
|
|
80
|
+
[deps-image]: https://img.shields.io/librariesio/release/npm/zsyp
|
|
81
|
+
[deps-url]: https://libraries.io/npm/zsyp
|
package/index.js
CHANGED
|
@@ -1,24 +1,15 @@
|
|
|
1
1
|
require('dotenv').config({ path: '/etc/default/zsyp' });
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const makeLogger = require('./lib/logger');
|
|
3
|
+
const logger = require('./lib/logger');
|
|
4
|
+
const makeApp = require('./lib/app');
|
|
6
5
|
|
|
7
6
|
const {
|
|
8
7
|
ZSYP_PORT: PORT = 3090,
|
|
9
|
-
ZSYP_DOMAINS: domains,
|
|
10
|
-
ZSYP_DB: database = 'mongodb://localhost/zsyp'
|
|
11
8
|
} = process.env;
|
|
12
9
|
|
|
13
|
-
const app =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
app.use(function (req, res, next) {
|
|
17
|
-
req.log = log;
|
|
18
|
-
next();
|
|
10
|
+
const app = makeApp({
|
|
11
|
+
logger
|
|
19
12
|
});
|
|
20
|
-
app.use(router({ domains }));
|
|
21
|
-
|
|
22
13
|
|
|
23
14
|
module.exports = app;
|
|
24
15
|
|
package/lib/app.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const connect = require('@pirxpilot/connect');
|
|
2
|
+
const { json } = require('body-parser');
|
|
3
|
+
const router = require('./router');
|
|
4
|
+
|
|
5
|
+
module.exports = makeApp;
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
ZSYP_DOMAINS: domains,
|
|
9
|
+
} = process.env;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
function makeApp(opts) {
|
|
13
|
+
const app = connect();
|
|
14
|
+
|
|
15
|
+
app.use(json({ limit: 5000, type: ['*/json', 'application/csp-report'] }));
|
|
16
|
+
app.use('/csp', router({ ...opts, name: 'csp', domains }));
|
|
17
|
+
app.use('/event', router({ ...opts, name: 'event' }));
|
|
18
|
+
|
|
19
|
+
return app;
|
|
20
|
+
}
|
|
21
|
+
|
package/lib/filter.js
CHANGED
|
@@ -4,18 +4,17 @@ module.exports = makeFilter;
|
|
|
4
4
|
|
|
5
5
|
function makeFilter({ domains }) {
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
if (!domains) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
8
10
|
|
|
11
|
+
const domainRe = domains && new RegExp(domains);
|
|
9
12
|
return filter;
|
|
10
13
|
|
|
11
14
|
function filter({ body: csp }, res, next) {
|
|
12
|
-
if (!domainRe) {
|
|
13
|
-
return next();
|
|
14
|
-
}
|
|
15
15
|
const uri = csp['csp-report']['document-uri'];
|
|
16
16
|
const { hostname } = new URL(uri);
|
|
17
17
|
if (domainRe.test(hostname)) {
|
|
18
|
-
console.log('OK!!!!!!!!');
|
|
19
18
|
return next();
|
|
20
19
|
}
|
|
21
20
|
}
|
package/lib/from.js
CHANGED
|
@@ -3,7 +3,8 @@ const { parse } = require('useragent');
|
|
|
3
3
|
module.exports = from;
|
|
4
4
|
|
|
5
5
|
function from(req, res, next) {
|
|
6
|
-
const
|
|
6
|
+
const { headers, body } = req;
|
|
7
|
+
const ua = body?.from?.ua ?? headers['user-agent'];
|
|
7
8
|
const {
|
|
8
9
|
family,
|
|
9
10
|
major,
|
|
@@ -20,7 +21,7 @@ function from(req, res, next) {
|
|
|
20
21
|
name: os.family,
|
|
21
22
|
version: os.major
|
|
22
23
|
},
|
|
23
|
-
ip:
|
|
24
|
+
ip: body?.from?.ip ?? headers['x-forwarded-for'] ?? req.connection.remoteAddress
|
|
24
25
|
};
|
|
25
26
|
if (device.family !== 'Other') {
|
|
26
27
|
data.device = device.family;
|
package/lib/logger.js
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
|
-
const
|
|
1
|
+
const mniam = require('mniam');
|
|
2
2
|
const debug = require('debug')('zsyp:logger');
|
|
3
3
|
|
|
4
4
|
module.exports = makeLogger;
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const {
|
|
7
|
+
ZSYP_DB: database = 'mongodb://localhost/zsyp'
|
|
8
|
+
} = process.env;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const db = mniam.db(database);
|
|
12
|
+
|
|
13
|
+
function makeLogger({ name }) {
|
|
14
|
+
const collection = db.collection({ name });
|
|
8
15
|
|
|
9
16
|
return log;
|
|
10
17
|
|
|
11
18
|
function log(report) {
|
|
12
19
|
debug('saving %j', report);
|
|
13
|
-
|
|
20
|
+
collection.insertOne(report, function(err) {
|
|
14
21
|
if (err) {
|
|
15
22
|
console.error(err);
|
|
16
23
|
}
|
package/lib/router.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const Router = require('router');
|
|
2
|
+
const debug = require('debug')('zsyp:router');
|
|
3
|
+
|
|
4
|
+
const from = require('./from');
|
|
5
|
+
const makeFilter = require('./filter');
|
|
6
|
+
|
|
7
|
+
function respond(req, res, next) {
|
|
8
|
+
res.statusCode = 204; // empty
|
|
9
|
+
res.end();
|
|
10
|
+
next();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = function (opts) {
|
|
14
|
+
const router = new Router({
|
|
15
|
+
strict: true,
|
|
16
|
+
caseSensitive: true
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const stack = [
|
|
20
|
+
from,
|
|
21
|
+
respond
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const filter = makeFilter(opts);
|
|
25
|
+
if (filter) {
|
|
26
|
+
stack.push(filter);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logger = opts.logger(opts);
|
|
30
|
+
|
|
31
|
+
router.post('/', ...stack, log);
|
|
32
|
+
|
|
33
|
+
return router;
|
|
34
|
+
|
|
35
|
+
function log({ from, body }) {
|
|
36
|
+
const report = {
|
|
37
|
+
...body,
|
|
38
|
+
from
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
debug('Logging report %j', report);
|
|
42
|
+
logger(report);
|
|
43
|
+
}
|
|
44
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zsyp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CSP violation reports logger.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Damian Krzeminski",
|
|
@@ -19,14 +19,15 @@
|
|
|
19
19
|
"@pirxpilot/connect": "^4.0.0",
|
|
20
20
|
"body-parser": "~1",
|
|
21
21
|
"debug": "~2 || ~3 || ~4",
|
|
22
|
-
"dotenv": "~
|
|
22
|
+
"dotenv": "~16",
|
|
23
23
|
"mniam": "^2.1.0",
|
|
24
24
|
"router": "~1",
|
|
25
|
+
"supertest": "~6",
|
|
25
26
|
"useragent": "^2.3.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"jshint": "~2",
|
|
29
|
-
"tape": "~
|
|
30
|
+
"tape": "~5"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
33
|
"test": "make check"
|
package/History.md
DELETED
package/lib/csp.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
const Router = require('router');
|
|
2
|
-
const { json } = require('body-parser');
|
|
3
|
-
|
|
4
|
-
const from = require('./from');
|
|
5
|
-
const makeFilter = require('./filter');
|
|
6
|
-
|
|
7
|
-
function respond(req, res, next) {
|
|
8
|
-
res.statusCode = 204; // empty
|
|
9
|
-
res.end();
|
|
10
|
-
next();
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function log({ from, body: csp, log }) {
|
|
15
|
-
const report = {
|
|
16
|
-
from,
|
|
17
|
-
...csp
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
log(report);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = function (opts) {
|
|
24
|
-
const router = new Router();
|
|
25
|
-
const filter = makeFilter(opts);
|
|
26
|
-
|
|
27
|
-
router.post('/csp',
|
|
28
|
-
from,
|
|
29
|
-
json({ limit: 5000, type: [ '*/json', 'application/csp-report' ] }),
|
|
30
|
-
respond,
|
|
31
|
-
filter,
|
|
32
|
-
log
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
return router;
|
|
36
|
-
};
|