vite-plugin-csp-dev 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/LICENCE +21 -0
- package/README.md +41 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +138 -0
- package/package.json +41 -0
package/LICENCE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-present gatisr
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# vite-plugin-csp-dev
|
|
2
|
+
|
|
3
|
+
A Vite plugin to add Content Security Policy (CSP) headers to your application in serve mode.
|
|
4
|
+
|
|
5
|
+
In order to set CSP headers in production builds, you must configure your web server accordingly, as this plugin only affects the development server.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
In your `vite.config.mjs`:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { secureHeaders } from 'vite-plugin-csp-dev';
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
plugins: [
|
|
16
|
+
secureHeaders({
|
|
17
|
+
reportOnly: false, // default: false - Report CSP violations instead of blocking them.
|
|
18
|
+
processI18n: true, // default: true - Process i18n.
|
|
19
|
+
defaultSrc: "'self'", // default: "'self'" - Value for default-src directive in CSP.
|
|
20
|
+
noncePlaceholder: 'NONCE_PLACEHOLDER', // default: 'NONCE_PLACEHOLDER' - Placeholder for nonce in HTML.
|
|
21
|
+
xssProtection: '1; mode=block', // default: '1; mode=block' - Value for X-XSS-Protection header.
|
|
22
|
+
frameOptions: 'DENY', // default: 'DENY' - Value for X-Frame-Options header.
|
|
23
|
+
contentTypeOptions: 'nosniff', // default: 'nosniff' - Value for X-Content-Type-Options header.
|
|
24
|
+
referrerPolicy: 'strict-origin-when-cross-origin', // default: 'strict-origin-when-cross-origin' - Value for Referrer-Policy header.
|
|
25
|
+
permissionsPolicy: 'camera=(), microphone=(), geolocation=()', // default: 'camera=(), microphone=(), geolocation()' - Value for Permissions-Policy header.
|
|
26
|
+
cacheControl: 'no-store, max-age=0', // default: 'no-store, max-age=0' - Value for Cache-Control header.
|
|
27
|
+
scriptSrc: (nonce) => `'self' 'nonce-${nonce}'`, // default: `'self' 'nonce-${nonce}'` - Function to generate script-src directive in CSP
|
|
28
|
+
styleSrc: (nonce) => `'self' 'nonce-${nonce}'`, // default: `'self' 'nonce-${nonce}'` - Function to generate style-src directive in CSP
|
|
29
|
+
workerSrc: "'self'", // default: "'self'" - Value for worker-src directive in CSP.
|
|
30
|
+
connectSrc: "'self'", // default: "'self'" - Value for connect-src directive in CSP.
|
|
31
|
+
imgSrc: "'self' data:", // default: "'self' data:" - Value for img-src directive in CSP.
|
|
32
|
+
fontSrc: "'self'", // default: "'self'" - Value for font-src directive in CSP.
|
|
33
|
+
objectSrc: "'none'", // default: "'none'" - Value for object-src directive in CSP.
|
|
34
|
+
frameSrc: "'self'", // default: "'self'" - Value for frame-src directive in CSP.
|
|
35
|
+
baseUri: "'self'", // default: "'self'" - Value for base-uri directive in CSP.
|
|
36
|
+
formAction: "'self'", // default: "'self'" - Value for form-action directive in CSP.
|
|
37
|
+
frameAncestors: "'none'", // default: "'none'" - Value for frame-ancestors directive in CSP.
|
|
38
|
+
}),
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Plugin as Plugin_2 } from 'vite';
|
|
2
|
+
|
|
3
|
+
declare type Options = {
|
|
4
|
+
/**
|
|
5
|
+
* - Whether to use Content-Security-Policy-Report-Only header instead of Content-Security-Policy header. For development purposes.
|
|
6
|
+
*/
|
|
7
|
+
reportOnly?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* - Whether to process i18n
|
|
10
|
+
*/
|
|
11
|
+
processI18n?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* - Value for default-src directive in CSP
|
|
14
|
+
*/
|
|
15
|
+
defaultSrc?: string;
|
|
16
|
+
/**
|
|
17
|
+
* - The placeholder for nonce in the HTML
|
|
18
|
+
*/
|
|
19
|
+
noncePlaceholder?: string;
|
|
20
|
+
/**
|
|
21
|
+
* - Value for X-XSS-Protection header
|
|
22
|
+
*/
|
|
23
|
+
xssProtection?: string;
|
|
24
|
+
/**
|
|
25
|
+
* - Value for X-Frame-Options header
|
|
26
|
+
*/
|
|
27
|
+
frameOptions?: string;
|
|
28
|
+
/**
|
|
29
|
+
* - Value for X-Content-Type-Options header
|
|
30
|
+
*/
|
|
31
|
+
contentTypeOptions?: string;
|
|
32
|
+
/**
|
|
33
|
+
* - Value for Referrer-Policy header
|
|
34
|
+
*/
|
|
35
|
+
referrerPolicy?: string;
|
|
36
|
+
/**
|
|
37
|
+
* - Value for Permissions-Policy header
|
|
38
|
+
*/
|
|
39
|
+
permissionsPolicy?: string;
|
|
40
|
+
/**
|
|
41
|
+
* - Value for Cache-Control header
|
|
42
|
+
*/
|
|
43
|
+
cacheControl?: string;
|
|
44
|
+
/**
|
|
45
|
+
* - Function to generate script-src directive in CSP, nonce is the generated nonce value
|
|
46
|
+
*/
|
|
47
|
+
scriptSrc?: (nonce: string) => string;
|
|
48
|
+
/**
|
|
49
|
+
* - Function to generate style-src directive in CSP, nonce is the generated nonce value
|
|
50
|
+
*/
|
|
51
|
+
styleSrc?: (nonce: string) => string;
|
|
52
|
+
/**
|
|
53
|
+
* - Value for img-src directive in CSP
|
|
54
|
+
*/
|
|
55
|
+
imgSrc?: string;
|
|
56
|
+
/**
|
|
57
|
+
* - Value for font-src directive in CSP
|
|
58
|
+
*/
|
|
59
|
+
fontSrc?: string;
|
|
60
|
+
/**
|
|
61
|
+
* - Value for object-src directive in CSP
|
|
62
|
+
*/
|
|
63
|
+
objectSrc?: string;
|
|
64
|
+
/**
|
|
65
|
+
* - Value for frame-src directive in CSP
|
|
66
|
+
*/
|
|
67
|
+
frameSrc?: string;
|
|
68
|
+
/**
|
|
69
|
+
* - Value for base-uri directive in CSP
|
|
70
|
+
*/
|
|
71
|
+
baseUri?: string;
|
|
72
|
+
/**
|
|
73
|
+
* - Value for form-action directive in CSP
|
|
74
|
+
*/
|
|
75
|
+
formAction?: string;
|
|
76
|
+
/**
|
|
77
|
+
* - Value for frame-ancestors directive in CSP
|
|
78
|
+
*/
|
|
79
|
+
frameAncestors?: string;
|
|
80
|
+
/**
|
|
81
|
+
* - Value for worker-src directive in CSP
|
|
82
|
+
*/
|
|
83
|
+
workerSrc?: string;
|
|
84
|
+
/**
|
|
85
|
+
* - Value for connect-src directive in CSP
|
|
86
|
+
*/
|
|
87
|
+
connectSrc?: string;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Creates a Vite plugin for handling Content Security Policy with nonce and i18n processing
|
|
92
|
+
* @param {Options} [options] - Configuration options for the plugin
|
|
93
|
+
* @returns {import('vite').Plugin} The Vite plugin instance
|
|
94
|
+
*/
|
|
95
|
+
export declare function secureHeaders(options?: Options): Plugin_2;
|
|
96
|
+
|
|
97
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { createHash as B } from "node:crypto";
|
|
2
|
+
const L = () => B("sha256").update(Date.now().toString()).digest("base64");
|
|
3
|
+
function I(w = {}) {
|
|
4
|
+
const {
|
|
5
|
+
reportOnly: N = !1,
|
|
6
|
+
processI18n: v = !0,
|
|
7
|
+
defaultSrc: S = "'self'",
|
|
8
|
+
noncePlaceholder: C = "NONCE_PLACEHOLDER",
|
|
9
|
+
xssProtection: p = "1; mode=block",
|
|
10
|
+
frameOptions: m = "DENY",
|
|
11
|
+
contentTypeOptions: u = "nosniff",
|
|
12
|
+
referrerPolicy: f = "strict-origin-when-cross-origin",
|
|
13
|
+
permissionsPolicy: y = "camera=(), microphone=(), geolocation=()",
|
|
14
|
+
cacheControl: $ = "no-store, max-age=0",
|
|
15
|
+
scriptSrc: g,
|
|
16
|
+
styleSrc: h,
|
|
17
|
+
imgSrc: A = "'self' data:",
|
|
18
|
+
fontSrc: H = "'self'",
|
|
19
|
+
objectSrc: P = "'none'",
|
|
20
|
+
frameSrc: E = "'self'",
|
|
21
|
+
baseUri: O = "'self'",
|
|
22
|
+
formAction: b = "'self'",
|
|
23
|
+
frameAncestors: k = "'none'",
|
|
24
|
+
workerSrc: j = "'self'",
|
|
25
|
+
connectSrc: x = "'self'"
|
|
26
|
+
} = w;
|
|
27
|
+
let i;
|
|
28
|
+
return {
|
|
29
|
+
name: "vite-plugin-csp-dev",
|
|
30
|
+
enforce: "post",
|
|
31
|
+
apply: "serve",
|
|
32
|
+
config(n, s) {
|
|
33
|
+
if (n.resolve = n.resolve || {}, n.resolve.alias = n.resolve.alias || {}, v) {
|
|
34
|
+
n.plugins = n.plugins || [];
|
|
35
|
+
const e = s.command === "serve";
|
|
36
|
+
let o = "vue-i18n/dist/vue-i18n.esm-browser.prod.js";
|
|
37
|
+
e && (o = "vue-i18n/dist/vue-i18n.esm-browser.js"), n.resolve.alias["vue-i18n"] = o;
|
|
38
|
+
}
|
|
39
|
+
return n;
|
|
40
|
+
},
|
|
41
|
+
configResolved(n) {
|
|
42
|
+
i = n.command === "serve" ? L() : C, n.html = n.html || {}, n.html.cspNonce = i;
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* @param {import('vite').ViteDevServer} server - The Vite development server
|
|
46
|
+
*/
|
|
47
|
+
configureServer(n) {
|
|
48
|
+
n.middlewares.use((s, e, o) => {
|
|
49
|
+
const t = i, c = g ? g(t) : `'self' 'nonce-${t}'`, r = h ? h(t) : `'self' 'nonce-${t}'`, l = [
|
|
50
|
+
`default-src ${S}`,
|
|
51
|
+
`script-src ${c}`,
|
|
52
|
+
`style-src ${r}`,
|
|
53
|
+
`img-src ${A}`,
|
|
54
|
+
`font-src ${H}`,
|
|
55
|
+
`object-src ${P}`,
|
|
56
|
+
`base-uri ${O}`,
|
|
57
|
+
`frame-src ${E}`,
|
|
58
|
+
`form-action ${b}`,
|
|
59
|
+
`frame-ancestors ${k}`,
|
|
60
|
+
`worker-src ${j}`,
|
|
61
|
+
`connect-src ${x}`,
|
|
62
|
+
"upgrade-insecure-requests"
|
|
63
|
+
].join("; "), a = N ? "Content-Security-Policy-Report-Only" : "Content-Security-Policy";
|
|
64
|
+
e.setHeader(a, l), p && e.setHeader("X-XSS-Protection", p), m && e.setHeader("X-Frame-Options", m), u && e.setHeader("X-Content-Type-Options", u), f && e.setHeader("Referrer-Policy", f), y && e.setHeader("Permissions-Policy", y), $ && e.setHeader("Cache-Control", $), o();
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} html - The original HTML
|
|
69
|
+
* @param {import('vite').IndexHtmlTransformContext} _ctx - The transform context
|
|
70
|
+
* @returns {string} The transformed HTML with nonces added
|
|
71
|
+
*/
|
|
72
|
+
// eslint-disable-next-line no-unused-vars
|
|
73
|
+
transformIndexHtml(n, s) {
|
|
74
|
+
const e = i, o = `
|
|
75
|
+
<script nonce="${e}">
|
|
76
|
+
(function() {
|
|
77
|
+
const originalCreateElement = document.createElement;
|
|
78
|
+
document.createElement = function() {
|
|
79
|
+
const element = originalCreateElement.apply(document, arguments);
|
|
80
|
+
if (arguments[0].toLowerCase() === 'style' || arguments[0].toLowerCase() === 'script') {
|
|
81
|
+
element.nonce = "${e}";
|
|
82
|
+
}
|
|
83
|
+
return element;
|
|
84
|
+
};
|
|
85
|
+
// Override insertBefore to add nonce to dynamically inserted style tags
|
|
86
|
+
const originalInsertBefore = Element.prototype.insertBefore;
|
|
87
|
+
Element.prototype.insertBefore = function(newNode, referenceNode) {
|
|
88
|
+
if (newNode.tagName && newNode.tagName.toLowerCase() === 'style' && !newNode.nonce) {
|
|
89
|
+
newNode.nonce = "${e}";
|
|
90
|
+
}
|
|
91
|
+
return originalInsertBefore.call(this, newNode, referenceNode);
|
|
92
|
+
};
|
|
93
|
+
// Override appendChild to add nonce to dynamically inserted style tags
|
|
94
|
+
const originalAppendChild = Element.prototype.appendChild;
|
|
95
|
+
Element.prototype.appendChild = function(newNode) {
|
|
96
|
+
if (newNode.tagName && newNode.tagName.toLowerCase() === 'style' && !newNode.nonce) {
|
|
97
|
+
newNode.nonce = "${e}";
|
|
98
|
+
}
|
|
99
|
+
return originalAppendChild.call(this, newNode);
|
|
100
|
+
};
|
|
101
|
+
})();
|
|
102
|
+
<\/script>
|
|
103
|
+
`, t = /<link\s+([^>]*)>/g;
|
|
104
|
+
return n.replaceAll("<script", `<script nonce="${e}"`).replaceAll("<style", `<style nonce="${e}"`).replace(t, (c, r) => {
|
|
105
|
+
let l = r.replace(/\s*\/\s*$/, "");
|
|
106
|
+
return l.includes("nonce=") || (l += ` nonce="${e}"`), `<link ${l.trim()}>`;
|
|
107
|
+
}).replaceAll('style="', `style="nonce="${e}" `).replace("</head>", `${o}</head>`);
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
//@param {import('rollup').OutputOptions} buildOptions - The build options
|
|
111
|
+
//@param {import('rollup').OutputBundle} bundle - The output bundle
|
|
112
|
+
//@returns {import('rollup').OutputBundle} The modified bundle with nonces added to HTML assets
|
|
113
|
+
*/
|
|
114
|
+
// @ts-ignore
|
|
115
|
+
generateBundle(n, s) {
|
|
116
|
+
const e = i, o = /<link\s+([^>]*?)(\/?\s*>)/g;
|
|
117
|
+
return Object.keys(s).reduce((t, c) => {
|
|
118
|
+
const r = s[c];
|
|
119
|
+
return r.type === "asset" && r.fileName.endsWith(".html") ? {
|
|
120
|
+
...t,
|
|
121
|
+
[c]: {
|
|
122
|
+
...r,
|
|
123
|
+
source: r.source.replaceAll("<script", `<script nonce="${e}"`).replaceAll("<style", `<style nonce="${e}"`).replace(o, (l, a) => {
|
|
124
|
+
let d = a.replace(/\s*\/\s*$/, "");
|
|
125
|
+
return d.includes("nonce=") || (d += ` nonce="${e}"`), `<link ${d.trim()}>`;
|
|
126
|
+
}).replaceAll('style="', `style="nonce="${e}" `)
|
|
127
|
+
}
|
|
128
|
+
} : {
|
|
129
|
+
...t,
|
|
130
|
+
[c]: r
|
|
131
|
+
};
|
|
132
|
+
}, {});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export {
|
|
137
|
+
I as secureHeaders
|
|
138
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite-plugin-csp-dev",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A Vite plugin to add Content Security Policy (CSP) headers to your application in serve mode.",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "vite build",
|
|
20
|
+
"lint": "oxlint .",
|
|
21
|
+
"format": "prettier --write .",
|
|
22
|
+
"prepublishOnly": "pnpm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"vite",
|
|
26
|
+
"plugin"
|
|
27
|
+
],
|
|
28
|
+
"author": "gatisr",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^25.0.3",
|
|
32
|
+
"@zzdats/prettier-config": "^1.0.0",
|
|
33
|
+
"oxlint": "^1.36.0",
|
|
34
|
+
"prettier": "^3.7.4",
|
|
35
|
+
"vite": "^7.3.0",
|
|
36
|
+
"vite-plugin-dts": "^4.5.4"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|