@magicdima/vite-plugin-csp 0.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 +92 -0
- package/package.json +17 -0
- package/src/index.ts +121 -0
- package/tsconfig.json +16 -0
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Vite CSP Plugin
|
|
2
|
+
|
|
3
|
+
> WARNING: this package is not stable and it's API will change with every minor version upgrade
|
|
4
|
+
|
|
5
|
+
A Vite plugin for managing Content Security Policy (CSP) headers and meta tags in your application.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
This package is part of the monorepo and should be installed via the workspace configuration.
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Import and configure the plugin in your `vite.config.ts`:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { defineConfig } from 'vite'
|
|
17
|
+
import { cspPlugin } from '@gacha/vite-csp-plugin'
|
|
18
|
+
|
|
19
|
+
export default defineConfig({
|
|
20
|
+
plugins: [
|
|
21
|
+
cspPlugin({
|
|
22
|
+
directives: {
|
|
23
|
+
'default-src': "'self'",
|
|
24
|
+
// accept single string policy
|
|
25
|
+
'script-src': "'self' 'unsafe-inline'",
|
|
26
|
+
'style-src': "'self' 'unsafe-inline'",
|
|
27
|
+
// or array of string that will be concatenated
|
|
28
|
+
'img-src': ["'self'", "data: https:"],
|
|
29
|
+
'connect-src': ["'self'", "https://api.example.com"]
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
]
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Options
|
|
37
|
+
|
|
38
|
+
### `directives`
|
|
39
|
+
|
|
40
|
+
An object containing CSP directives, or a function that returns such an object with access to the resolved Vite config. Each directive value can be a string or an array of strings. The plugin comes with sensible defaults:
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const defaultDirectives = {
|
|
44
|
+
'default-src': "'self'",
|
|
45
|
+
'script-src': ["'self'"],
|
|
46
|
+
'style-src': ["'self'"],
|
|
47
|
+
'img-src': ["'self'", "data:"],
|
|
48
|
+
'font-src': "'self'",
|
|
49
|
+
'connect-src': "'self'",
|
|
50
|
+
'media-src': "'self'",
|
|
51
|
+
'object-src': "'none'",
|
|
52
|
+
'frame-src': "'none'",
|
|
53
|
+
'worker-src': "'self'",
|
|
54
|
+
'manifest-src': "'self'",
|
|
55
|
+
'form-action': "'self'"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You can override any of these defaults by providing your own values. When using arrays, they will be joined with spaces.
|
|
60
|
+
|
|
61
|
+
### `enabled`
|
|
62
|
+
|
|
63
|
+
Whether to enable CSP injection. Defaults to `true`.
|
|
64
|
+
|
|
65
|
+
When disabled, the plugin will not inject CSP meta tag.
|
|
66
|
+
|
|
67
|
+
### `policy`
|
|
68
|
+
|
|
69
|
+
An additional CSP policy string to append to the generated policy. Useful for complex policies that don't fit the directive object structure.
|
|
70
|
+
|
|
71
|
+
## How it works
|
|
72
|
+
|
|
73
|
+
The plugin injects a `<meta http-equiv="Content-Security-Policy">` tag into the HTML head at build time.
|
|
74
|
+
|
|
75
|
+
### Using a function for dynamic configuration with access to Vite config
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
cspPlugin({
|
|
79
|
+
directives: (config) => {
|
|
80
|
+
// Access to resolved Vite config and environment variables
|
|
81
|
+
const isProduction = config.mode === 'production'
|
|
82
|
+
const apiUrl = config.env.VITE_API_URL || 'https://api.example.com'
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
'default-src': "'self'",
|
|
86
|
+
'script-src': isProduction ? "'self'" : ["'self'", "'unsafe-inline'"],
|
|
87
|
+
'connect-src': ['self', apiUrl],
|
|
88
|
+
'img-src': ['self', 'data:', 'https:', 'blob:']
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magicdima/vite-plugin-csp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc"
|
|
8
|
+
},
|
|
9
|
+
"devDependencies": {
|
|
10
|
+
"typescript": "^5.9.3",
|
|
11
|
+
"vite": "^5.2.0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [],
|
|
14
|
+
"author": "",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"packageManager": "pnpm@10.17.1"
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
|
+
|
|
3
|
+
export type CSPDirectiveValue = string | string[];
|
|
4
|
+
|
|
5
|
+
export interface CSPDirectives {
|
|
6
|
+
"default-src"?: CSPDirectiveValue;
|
|
7
|
+
"script-src"?: CSPDirectiveValue;
|
|
8
|
+
"style-src"?: CSPDirectiveValue;
|
|
9
|
+
"img-src"?: CSPDirectiveValue;
|
|
10
|
+
"font-src"?: CSPDirectiveValue;
|
|
11
|
+
"connect-src"?: CSPDirectiveValue;
|
|
12
|
+
"media-src"?: CSPDirectiveValue;
|
|
13
|
+
"object-src"?: CSPDirectiveValue;
|
|
14
|
+
"frame-src"?: CSPDirectiveValue;
|
|
15
|
+
"worker-src"?: CSPDirectiveValue;
|
|
16
|
+
"manifest-src"?: CSPDirectiveValue;
|
|
17
|
+
"form-action"?: CSPDirectiveValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CSPPluginOptions {
|
|
21
|
+
/**
|
|
22
|
+
* CSP directives to apply. Can be an object or a function that receives the Vite config.
|
|
23
|
+
*/
|
|
24
|
+
directives?: CSPDirectives | ((config: ResolvedConfig) => CSPDirectives);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether to include CSP
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
enabled?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Additional CSP policy string to append
|
|
34
|
+
*/
|
|
35
|
+
policy?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const defaultDirectives: CSPDirectives = {
|
|
39
|
+
"default-src": "'self'",
|
|
40
|
+
"script-src": ["'self'"],
|
|
41
|
+
"style-src": ["'self'"],
|
|
42
|
+
"img-src": ["'self'", "data:"],
|
|
43
|
+
"font-src": "'self'",
|
|
44
|
+
"connect-src": "'self'",
|
|
45
|
+
"media-src": "'self'",
|
|
46
|
+
"object-src": "'none'",
|
|
47
|
+
"frame-src": "'none'",
|
|
48
|
+
"worker-src": "'self'",
|
|
49
|
+
"manifest-src": "'self'",
|
|
50
|
+
"form-action": "'self'",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function buildCSPHeader(directives: CSPDirectives): string {
|
|
54
|
+
const policies: string[] = [];
|
|
55
|
+
|
|
56
|
+
for (const [directive, value] of Object.entries(directives)) {
|
|
57
|
+
if (value === true) {
|
|
58
|
+
policies.push(directive);
|
|
59
|
+
} else if (value === false) {
|
|
60
|
+
// Skip false values
|
|
61
|
+
} else if (typeof value === "string") {
|
|
62
|
+
policies.push(`${directive} ${value}`);
|
|
63
|
+
} else if (Array.isArray(value)) {
|
|
64
|
+
policies.push(`${directive} ${value.join(" ")}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return policies.join("; ");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function cspPlugin(options: CSPPluginOptions = {}): Plugin {
|
|
72
|
+
const {
|
|
73
|
+
directives = {},
|
|
74
|
+
enabled: includeCsp = true,
|
|
75
|
+
policy: additionalPolicy,
|
|
76
|
+
} = options;
|
|
77
|
+
|
|
78
|
+
let resolvedDirectives: CSPDirectives;
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
name: "vite-csp-plugin",
|
|
82
|
+
configResolved(resolvedConfig) {
|
|
83
|
+
// Resolve directives (either function or object)
|
|
84
|
+
const userDirectives =
|
|
85
|
+
typeof directives === "function"
|
|
86
|
+
? directives(resolvedConfig)
|
|
87
|
+
: directives;
|
|
88
|
+
|
|
89
|
+
// Merge default directives with user directives
|
|
90
|
+
resolvedDirectives = { ...defaultDirectives, ...userDirectives };
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// configureServer(server) {
|
|
94
|
+
// if (includeDev) {
|
|
95
|
+
// const cspPolicy = buildCSPHeader(resolvedDirectives) +
|
|
96
|
+
// (additionalPolicy ? `; ${additionalPolicy}` : '')
|
|
97
|
+
// server.middlewares.use((_req, res, next) => {
|
|
98
|
+
// res.setHeader('Content-Security-Policy', cspPolicy)
|
|
99
|
+
// next()
|
|
100
|
+
// })
|
|
101
|
+
// }
|
|
102
|
+
// },
|
|
103
|
+
|
|
104
|
+
transformIndexHtml(html) {
|
|
105
|
+
if (!includeCsp) {
|
|
106
|
+
return html;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const cspPolicy =
|
|
110
|
+
buildCSPHeader(resolvedDirectives) +
|
|
111
|
+
(additionalPolicy ? `; ${additionalPolicy}` : "");
|
|
112
|
+
|
|
113
|
+
const cspMetaTag = `<meta http-equiv="Content-Security-Policy" content="${cspPolicy}" />`;
|
|
114
|
+
|
|
115
|
+
// Insert the CSP meta tag at the beginning of the head
|
|
116
|
+
return html.replace(/<head>/, `<head>\n ${cspMetaTag}`);
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default cspPlugin;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"outDir": "dist",
|
|
4
|
+
"noEmit": true,
|
|
5
|
+
"incremental": false,
|
|
6
|
+
"target": "ESNext",
|
|
7
|
+
"module": "NodeNext",
|
|
8
|
+
"moduleResolution": "nodenext",
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"skipDefaultLibCheck": true,
|
|
11
|
+
"strictNullChecks": true, /* viem */
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|