@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 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
+ }