nuxt-csp-report 1.0.0-alpha.2 → 1.0.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
CHANGED
|
@@ -1,176 +1,185 @@
|
|
|
1
|
-
# Nuxt CSP Report
|
|
2
|
-
|
|
3
|
-
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
-
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
-
[![License][license-src]][license-href]
|
|
6
|
-
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
-
|
|
8
|
-
A Nuxt module for collecting, normalizing, and persisting Content Security Policy (CSP) reports.
|
|
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
|
-
* Description: Optional.
|
|
116
|
-
|
|
117
|
-
###
|
|
118
|
-
* Type: `
|
|
119
|
-
*
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
*
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
#
|
|
146
|
-
pnpm
|
|
147
|
-
|
|
148
|
-
#
|
|
149
|
-
pnpm
|
|
150
|
-
|
|
151
|
-
#
|
|
152
|
-
pnpm
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
[
|
|
176
|
-
[
|
|
1
|
+
# Nuxt CSP Report
|
|
2
|
+
|
|
3
|
+
[![npm version][npm-version-src]][npm-version-href]
|
|
4
|
+
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
5
|
+
[![License][license-src]][license-href]
|
|
6
|
+
[![Nuxt][nuxt-src]][nuxt-href]
|
|
7
|
+
|
|
8
|
+
A Nuxt module for collecting, normalizing, and persisting Content Security Policy (CSP) reports.
|
|
9
|
+
|
|
10
|
+
**Compatibility:** Nuxt 3 (3.12.0 and newer) and Nuxt 4.
|
|
11
|
+
|
|
12
|
+
[✨ Release Notes](/CHANGELOG.md)
|
|
13
|
+
|
|
14
|
+
## What is CSP and CSP reports?
|
|
15
|
+
|
|
16
|
+
The CSP is a HTTP response header that allows you to control which resources a document is allowed to load. For example setting `Content-Security-Policy: script-src example.com;` will prevent any script tag from loading a source that is not from `example.com`. Any violation will be logged in the console of the browser. Additionally, a reporting endpoint can be set in the CSP header where the browser will send the CSP report to.
|
|
17
|
+
|
|
18
|
+
Once you decide to secure your website with CSP, you most likely want to analyze on production if your CSP headers are configured properly. That can be tricky the more external resources are loaded. Especially dynamically loaded scripts, e.g. depending on your country or your consent, are not always the same for every user. That's where the CSP reports are helpful, because they show the real CSP violations that users experience in their browsers.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- 📋 Registers a POST endpoint for CSP reports
|
|
23
|
+
- 📡 Adds the `Reporting-Endpoints` header to your responses for `report-to` support
|
|
24
|
+
- 🔄 Supports both legacy `report-uri` and `report-to` format reports
|
|
25
|
+
- ✅ Validates and normalizes reports with Zod
|
|
26
|
+
- 💾 Persists reports via unstorage
|
|
27
|
+
- 📝 Full TypeScript support with proper type exports
|
|
28
|
+
|
|
29
|
+
## Quick Setup
|
|
30
|
+
|
|
31
|
+
Install the module to your Nuxt application:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install nuxt-csp-report
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Add it to your `nuxt.config.ts`:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
export default defineNuxtConfig({
|
|
41
|
+
modules: ['nuxt-csp-report'],
|
|
42
|
+
cspReport: {
|
|
43
|
+
// Module options
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Examples
|
|
49
|
+
|
|
50
|
+
* Minimal starter: [](https://stackblitz.com/github/Gonzo17/nuxt-csp-report/tree/main/examples/minimal?file=nuxt.config.ts)
|
|
51
|
+
* Source: [examples/minimal/nuxt.config.ts](examples/minimal/nuxt.config.ts) and [examples/minimal/app.vue](examples/minimal/app.vue).
|
|
52
|
+
* StackBlitz runs inside an iframe; the example CSP allows `frame-ancestors` for `*.stackblitz.io`, `*.webcontainer.io`, and `*.local-credentialless.webcontainer.io`.
|
|
53
|
+
* Playground (used for development): [playground/nuxt.config.ts](playground/nuxt.config.ts) with nuxt-security defaults and extra logging.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
The module is ready to go with the defaults.
|
|
58
|
+
In most use cases simple logs are sufficient. If you want to analyze CSP reports, you can use the `storage` option to persist the reports in a KV store.
|
|
59
|
+
|
|
60
|
+
### nuxt-security
|
|
61
|
+
|
|
62
|
+
The Content Security Policy is set through specific headers. You can handle that yourself with Nuxt/Nitro, but I highly recommend using [nuxt-security](https://github.com/Baroshem/nuxt-security).
|
|
63
|
+
Here is a minimal example of how to use the two moduls in combination:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
export default defineNuxtConfig({
|
|
67
|
+
modules: ['nuxt-security', 'nuxt-csp-report'],
|
|
68
|
+
security: {
|
|
69
|
+
headers: {
|
|
70
|
+
contentSecurityPolicy: {
|
|
71
|
+
'report-uri': '/api/csp-report',
|
|
72
|
+
// your CSP headers
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Advanced: Access reports
|
|
80
|
+
|
|
81
|
+
Depending on your use case you might want to access the CSP reports. You can do that with `useStorage`:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
export default defineNuxtConfig({
|
|
85
|
+
modules: ['nuxt-csp-report'],
|
|
86
|
+
cspReport: {
|
|
87
|
+
storage: {
|
|
88
|
+
driver: {
|
|
89
|
+
name: 'redis',
|
|
90
|
+
options: {
|
|
91
|
+
// Your redis configuration
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
import { type NormalizedCspReport } from 'nuxt-csp-report'
|
|
101
|
+
|
|
102
|
+
const storage = useStorage<NormalizedCspReport>('csp-report-storage')
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Options
|
|
106
|
+
|
|
107
|
+
### endpoint
|
|
108
|
+
* Type: `string`
|
|
109
|
+
* Default: `/api/csp-report`
|
|
110
|
+
* Description: Optional. Path for the CSP report endpoint.
|
|
111
|
+
|
|
112
|
+
### reportingEndpointsHeader
|
|
113
|
+
* Type: `boolean`
|
|
114
|
+
* Default: `false`
|
|
115
|
+
* Description: Optional. Adds the `Reporting-Endpoints` header to your HTML responses, using `'csp-endpoint'` as the key and `endpoint` from the configuration as the value. This header is needed if you want to use `report-to csp-endpoint` in your CSP configuration.
|
|
116
|
+
|
|
117
|
+
### console
|
|
118
|
+
* Type: `'summary' | 'full' | false`
|
|
119
|
+
* Default: `'summary'`
|
|
120
|
+
* Description: Optional. Log reports to console on server. `'full'` will print the `NormalizedCspReport` object.
|
|
121
|
+
|
|
122
|
+
### storage
|
|
123
|
+
* Type: See fields below.
|
|
124
|
+
* Description: Optional. Sets up a storage using `unstorage`, which is part of Nitro and Nuxt.
|
|
125
|
+
|
|
126
|
+
### storage.driver
|
|
127
|
+
* Type: `BuiltinDriverOptions`
|
|
128
|
+
* Description: Defines the driver from `unstorage`. You can use the same notation and drivers as in Nuxt:
|
|
129
|
+
* https://nuxt.com/docs/4.x/directory-structure/server#server-storage
|
|
130
|
+
* https://nitro.build/guide/storage
|
|
131
|
+
* https://unstorage.unjs.io/drivers
|
|
132
|
+
|
|
133
|
+
### storage.keyPrefix
|
|
134
|
+
* Type: `string`
|
|
135
|
+
* Default: `csp-report`
|
|
136
|
+
* Description: Optional. Key prefix for the stored reports.
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
## Contribution
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary>Local development</summary>
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Install dependencies
|
|
146
|
+
pnpm install
|
|
147
|
+
|
|
148
|
+
# Generate type stubs
|
|
149
|
+
pnpm dev:prepare
|
|
150
|
+
|
|
151
|
+
# Develop with the playground
|
|
152
|
+
pnpm dev
|
|
153
|
+
|
|
154
|
+
# Build the playground
|
|
155
|
+
pnpm dev:build
|
|
156
|
+
|
|
157
|
+
# Run ESLint
|
|
158
|
+
pnpm lint
|
|
159
|
+
|
|
160
|
+
# Run Vitest
|
|
161
|
+
pnpm test
|
|
162
|
+
pnpm test:watch
|
|
163
|
+
|
|
164
|
+
# Build the module
|
|
165
|
+
pnpm prepack
|
|
166
|
+
|
|
167
|
+
# Release new version
|
|
168
|
+
pnpm release
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
</details>
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
<!-- Badges -->
|
|
175
|
+
[npm-version-src]: https://img.shields.io/npm/v/nuxt-csp-report/latest.svg?style=flat&colorA=020420&colorB=00DC82
|
|
176
|
+
[npm-version-href]: https://npmjs.com/package/nuxt-csp-report
|
|
177
|
+
|
|
178
|
+
[npm-downloads-src]: https://img.shields.io/npm/dm/nuxt-csp-report.svg?style=flat&colorA=020420&colorB=00DC82
|
|
179
|
+
[npm-downloads-href]: https://npm.chart.dev/nuxt-csp-report
|
|
180
|
+
|
|
181
|
+
[license-src]: https://img.shields.io/npm/l/nuxt-csp-report.svg?style=flat&colorA=020420&colorB=00DC82
|
|
182
|
+
[license-href]: https://npmjs.com/package/nuxt-csp-report
|
|
183
|
+
|
|
184
|
+
[nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js
|
|
185
|
+
[nuxt-href]: https://nuxt.com
|
package/dist/module.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useRuntimeConfig, useStorage, readBody, defineEventHandler } from "#imports";
|
|
1
|
+
import { useRuntimeConfig, useStorage, readBody, defineEventHandler, setResponseStatus } from "#imports";
|
|
2
2
|
import { normalizeCspReport } from "../utils/normalizeCspReport.js";
|
|
3
3
|
import { formatCspLog } from "../utils/formatCspLog.js";
|
|
4
4
|
const runtimeConfig = useRuntimeConfig();
|
|
@@ -27,6 +27,7 @@ export default defineEventHandler(async (event) => {
|
|
|
27
27
|
return { ok: true };
|
|
28
28
|
} catch (err) {
|
|
29
29
|
console.error("[nuxt-csp-report]", err);
|
|
30
|
-
|
|
30
|
+
setResponseStatus(event, 500);
|
|
31
|
+
return { ok: false };
|
|
31
32
|
}
|
|
32
33
|
});
|
|
@@ -41,7 +41,7 @@ declare const reportToEntrySchema: z.ZodObject<{
|
|
|
41
41
|
url: z.ZodString;
|
|
42
42
|
user_agent: z.ZodString;
|
|
43
43
|
}, z.core.$loose>;
|
|
44
|
-
declare const
|
|
44
|
+
declare const _reportToSchema: z.ZodArray<z.ZodObject<{
|
|
45
45
|
age: z.ZodNumber;
|
|
46
46
|
body: z.ZodObject<{
|
|
47
47
|
blockedURL: z.ZodString;
|
|
@@ -64,6 +64,6 @@ declare const reportToSchema: z.ZodArray<z.ZodObject<{
|
|
|
64
64
|
user_agent: z.ZodString;
|
|
65
65
|
}, z.core.$loose>>;
|
|
66
66
|
export type ReportToEntryFormat = z.infer<typeof reportToEntrySchema>;
|
|
67
|
-
export type ReportToFormat = z.infer<typeof
|
|
67
|
+
export type ReportToFormat = z.infer<typeof _reportToSchema>;
|
|
68
68
|
export declare function normalizeCspReport(input: unknown): NormalizedCspReport[];
|
|
69
69
|
export {};
|
|
@@ -32,42 +32,45 @@ const reportToEntrySchema = z.object({
|
|
|
32
32
|
type: z.string(),
|
|
33
33
|
url: z.string(),
|
|
34
34
|
user_agent: z.string()
|
|
35
|
-
}).
|
|
36
|
-
const
|
|
35
|
+
}).loose();
|
|
36
|
+
const _reportToSchema = reportToEntrySchema.array();
|
|
37
37
|
export function normalizeCspReport(input) {
|
|
38
|
-
if (!input)
|
|
39
|
-
|
|
38
|
+
if (!input) return [];
|
|
39
|
+
if (typeof input === "object" && input !== null && "csp-report" in input) {
|
|
40
|
+
const parsed = reportUriSchema.safeParse(input);
|
|
41
|
+
if (!parsed.success) return [];
|
|
42
|
+
const report = parsed.data["csp-report"];
|
|
43
|
+
return [{
|
|
44
|
+
raw: parsed.data,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
documentURL: report["document-uri"],
|
|
47
|
+
blockedURL: report["blocked-uri"],
|
|
48
|
+
directive: report["violated-directive"],
|
|
49
|
+
sourceFile: report["source-file"],
|
|
50
|
+
line: report["line-number"],
|
|
51
|
+
disposition: report["disposition"]
|
|
52
|
+
}];
|
|
40
53
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
if (Array.isArray(input)) {
|
|
55
|
+
const reports = [];
|
|
56
|
+
for (const entry of input) {
|
|
57
|
+
const parsed = reportToEntrySchema.safeParse(entry);
|
|
58
|
+
if (!parsed.success) continue;
|
|
59
|
+
if (parsed.data.type !== "csp-violation") continue;
|
|
60
|
+
const { body } = parsed.data;
|
|
61
|
+
reports.push({
|
|
62
|
+
raw: parsed.data,
|
|
46
63
|
timestamp: Date.now(),
|
|
47
|
-
documentURL:
|
|
48
|
-
blockedURL:
|
|
49
|
-
directive:
|
|
50
|
-
sourceFile:
|
|
51
|
-
line:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
if (Array.isArray(input)) {
|
|
56
|
-
const parsed = reportToSchema.parse(input);
|
|
57
|
-
return parsed.map((item) => {
|
|
58
|
-
return {
|
|
59
|
-
raw: item,
|
|
60
|
-
timestamp: Date.now(),
|
|
61
|
-
documentURL: item.body.documentURL,
|
|
62
|
-
blockedURL: item.body.blockedURL,
|
|
63
|
-
directive: item.body.effectiveDirective,
|
|
64
|
-
sourceFile: item.body.sourceFile,
|
|
65
|
-
line: item.body.lineNumber,
|
|
66
|
-
disposition: item.body.disposition
|
|
67
|
-
};
|
|
64
|
+
documentURL: body.documentURL,
|
|
65
|
+
blockedURL: body.blockedURL,
|
|
66
|
+
directive: body.effectiveDirective,
|
|
67
|
+
sourceFile: body.sourceFile,
|
|
68
|
+
line: body.lineNumber,
|
|
69
|
+
column: body.columnNumber,
|
|
70
|
+
disposition: body.disposition
|
|
68
71
|
});
|
|
69
72
|
}
|
|
70
|
-
|
|
73
|
+
return reports;
|
|
71
74
|
}
|
|
72
75
|
return [];
|
|
73
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-csp-report",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "A Nuxt module for collecting, normalizing, and persisting Content Security Policy reports",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,17 +35,6 @@
|
|
|
35
35
|
"files": [
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
|
-
"scripts": {
|
|
39
|
-
"prepack": "nuxt-module-build build",
|
|
40
|
-
"dev": "npm run dev:prepare && nuxi dev playground",
|
|
41
|
-
"dev:build": "nuxi build playground",
|
|
42
|
-
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
43
|
-
"release": "npm run lint && npm run test && npm run prepack && changelogen --release --prerelease alpha && npm publish --tag alpha && git push --follow-tags",
|
|
44
|
-
"lint": "eslint .",
|
|
45
|
-
"test": "vitest run",
|
|
46
|
-
"test:watch": "vitest watch",
|
|
47
|
-
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
48
|
-
},
|
|
49
38
|
"dependencies": {
|
|
50
39
|
"@nuxt/kit": "^4.2.2",
|
|
51
40
|
"defu": "^6.1.4",
|
|
@@ -66,5 +55,18 @@
|
|
|
66
55
|
"typescript": "~5.9.3",
|
|
67
56
|
"vitest": "^4.0.16",
|
|
68
57
|
"vue-tsc": "^3.2.1"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"nuxt": ">=3.12.0 <5"
|
|
61
|
+
},
|
|
62
|
+
"scripts": {
|
|
63
|
+
"dev": "pnpm dev:prepare && nuxi dev playground",
|
|
64
|
+
"dev:build": "nuxi build playground",
|
|
65
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
66
|
+
"release": "pnpm lint && pnpm test && pnpm prepack && changelogen --release && pnpm publish && git push --follow-tags",
|
|
67
|
+
"lint": "eslint .",
|
|
68
|
+
"test": "vitest run",
|
|
69
|
+
"test:watch": "vitest watch",
|
|
70
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
|
|
69
71
|
}
|
|
70
72
|
}
|