csprefabricate 0.3.1 → 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/CHANGELOG.md ADDED
@@ -0,0 +1,41 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0](https://github.com/JamesToohey/csprefabricate/compare/v0.4.0...v1.0.0) (2025-06-02)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * updates for 1.0 release ([#29](https://github.com/JamesToohey/csprefabricate/issues/29))
9
+
10
+ ### Documentation
11
+
12
+ * updates for 1.0 release ([#29](https://github.com/JamesToohey/csprefabricate/issues/29)) ([1629ea1](https://github.com/JamesToohey/csprefabricate/commit/1629ea1e415142a54f6bd538832dbca4cdf1f179))
13
+
14
+ ## [0.4.0](https://github.com/JamesToohey/csprefabricate/compare/v0.3.1...v0.4.0) (2025-05-30)
15
+
16
+
17
+ ### Features
18
+
19
+ * add baseline CSPs ([#25](https://github.com/JamesToohey/csprefabricate/issues/25)) ([6dd8588](https://github.com/JamesToohey/csprefabricate/commit/6dd858853d143de66979e74ba981844c2b1e28d3))
20
+
21
+ ## [0.3.1](https://github.com/JamesToohey/csprefabricate/compare/v0.3.0...v0.3.1) (2025-05-27)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * fix publish command ([#23](https://github.com/JamesToohey/csprefabricate/issues/23)) ([7b537f3](https://github.com/JamesToohey/csprefabricate/commit/7b537f337e20c188a89ddb3dd3f2ab5ddefade04))
27
+
28
+ ## [0.3.0](https://github.com/JamesToohey/csprefabricate/compare/v0.2.3...v0.3.0) (2025-05-26)
29
+
30
+
31
+ ### Features
32
+
33
+ * Add warnings for common misconfigurations ([#21](https://github.com/JamesToohey/csprefabricate/issues/21)) ([d47858a](https://github.com/JamesToohey/csprefabricate/commit/d47858a04b777edec738b0f8ead23845795597a5))
34
+
35
+ ## [0.2.3](https://github.com/JamesToohey/csprefabricate/compare/0.2.2...v0.2.3) (2025-05-25)
36
+
37
+
38
+ ### Bug Fixes
39
+
40
+ * give release please access to github actions ([#17](https://github.com/JamesToohey/csprefabricate/issues/17)) ([456d933](https://github.com/JamesToohey/csprefabricate/commit/456d933dcbb746943f1f0a921e96a5b54c6055e5))
41
+ * update contributing ([4588b6a](https://github.com/JamesToohey/csprefabricate/commit/4588b6a09731a08121f3a26612147518fbf50b05))
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # csprefabricate (Work in progress)
1
+ # csprefabricate
2
2
 
3
- **Generate a valid CSP with JavaScript. Built with TypeScript.**
3
+ **Generate a valid CSP with TypeScript.**
4
4
 
5
- Content Security Policies (CSPs) are cumbersome strings that are frusting to work with:
5
+ Content Security Policies (CSPs) are cumbersome strings that are frustrating to work with:
6
6
 
7
7
  - Fickle syntax
8
- - Duplicattion when multiple TLDs are required
8
+ - Duplication when multiple TLDs are required
9
9
  - Easy to allow insecure configuration
10
10
 
11
11
  This project aims to make creating useful and secure CSPs a more pleasant experience.
@@ -82,15 +82,19 @@ const cspString = create(csp);
82
82
  import {create, Directive, ContentSecurityPolicy} from "csprefabricate";
83
83
 
84
84
  const csp: ContentSecurityPolicy = {
85
- [Directive.DEFAULT_SRC]: ["self"],
86
- [Directive.SCRIPT_SRC]: ["self", "*.googletagmanager.com"],
85
+ [Directive.DEFAULT_SRC]: ["'self'"],
86
+ [Directive.SCRIPT_SRC]: ["'self'", "*.googletagmanager.com"],
87
+ [Directive.STYLE_SRC]: ["'self'"],
87
88
  [Directive.IMG_SRC]: [
88
- "self",
89
- "*.google-analytics.com",
89
+ "'self'",
90
+ "https://*.google-analytics.com",
90
91
  "https://*.googletagmanager.com",
91
92
  ],
93
+ [Directive.OBJECT_SRC]: ["'none'"],
94
+ [Directive.BASE_URI]: ["'self'"],
95
+ [Directive.FORM_ACTION]: ["'self'"],
92
96
  [Directive.CONNECT_SRC]: [
93
- "self",
97
+ "'self'",
94
98
  "https://*.google-analytics.com",
95
99
  "https://*.analytics.google.com",
96
100
  "https://*.googletagmanager.com",
@@ -98,7 +102,7 @@ const csp: ContentSecurityPolicy = {
98
102
  };
99
103
 
100
104
  const cspString = create(csp);
101
- // "default-src 'self'; script-src 'self' *.googletagmanager.com; img-src 'self' *.google-analytics.com https://*.googletagmanager.com; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com;"
105
+ // "default-src 'self'; script-src 'self' *.googletagmanager.com; style-src 'self'; img-src 'self' https://*.google-analytics.com https://*.googletagmanager.com; object-src 'none'; base-uri 'self'; form-action 'self'; connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com;"
102
106
  ```
103
107
 
104
108
  ### Example 3: Using TLD Expansion for Multiple Domains
@@ -114,6 +118,22 @@ const cspString = create(csp);
114
118
  // "img-src 'self' *.example.com *.example.co.uk *.example.net;"
115
119
  ```
116
120
 
117
- ## Future
121
+ ## Baseline Recommended CSPs
122
+
123
+ You can quickly generate a recommended Content Security Policy for common use cases using built-in baselines.
118
124
 
119
- - Generate baseline recommended CSPs (for example, Google Analytics)
125
+ Available Baselines:
126
+
127
+ - BASELINE_STRICT_CSP
128
+ - GOOGLE_ANALYTICS_CSP
129
+ - GOOGLE_ANALYTICS_WITH_SIGNALS_CSP
130
+
131
+ ### Google Analytics Baseline CSP
132
+
133
+ Allow Google Analytics and Tag Manager:
134
+
135
+ ```typescript
136
+ import {create, Baseline} from "csprefabricate";
137
+
138
+ const cspString = create(Baseline.GOOGLE_ANALYTICS_CSP);
139
+ ```
@@ -0,0 +1,8 @@
1
+ import { ContentSecurityPolicy } from "./types";
2
+ export declare const BASELINE_STRICT_CSP: ContentSecurityPolicy;
3
+ /**
4
+ * Google Analytics Content Security Policy based on the official guidelines.
5
+ * https://developers.google.com/tag-platform/security/guides/csp#google_analytics_4_google_analytics
6
+ */
7
+ export declare const GOOGLE_ANALYTICS_CSP: ContentSecurityPolicy;
8
+ export declare const GOOGLE_ANALYTICS_WITH_SIGNALS_CSP: ContentSecurityPolicy;
@@ -0,0 +1,245 @@
1
+ import { Directive } from "./types";
2
+ // List of supported domains for Google Signals from https://www.google.com/supported_domains
3
+ const googleSupportedTLDs = [
4
+ ".com",
5
+ ".ad",
6
+ ".ae",
7
+ ".com.af",
8
+ ".com.ag",
9
+ ".al",
10
+ ".am",
11
+ ".co.ao",
12
+ ".com.ar",
13
+ ".as",
14
+ ".at",
15
+ ".com.au",
16
+ ".az",
17
+ ".ba",
18
+ ".com.bd",
19
+ ".be",
20
+ ".bf",
21
+ ".bg",
22
+ ".com.bh",
23
+ ".bi",
24
+ ".bj",
25
+ ".com.bn",
26
+ ".com.bo",
27
+ ".com.br",
28
+ ".bs",
29
+ ".bt",
30
+ ".co.bw",
31
+ ".by",
32
+ ".com.bz",
33
+ ".ca",
34
+ ".cd",
35
+ ".cf",
36
+ ".cg",
37
+ ".ch",
38
+ ".ci",
39
+ ".co.ck",
40
+ ".cl",
41
+ ".cm",
42
+ ".cn",
43
+ ".com.co",
44
+ ".co.cr",
45
+ ".com.cu",
46
+ ".cv",
47
+ ".com.cy",
48
+ ".cz",
49
+ ".de",
50
+ ".dj",
51
+ ".dk",
52
+ ".dm",
53
+ ".com.do",
54
+ ".dz",
55
+ ".com.ec",
56
+ ".ee",
57
+ ".com.eg",
58
+ ".es",
59
+ ".com.et",
60
+ ".fi",
61
+ ".com.fj",
62
+ ".fm",
63
+ ".fr",
64
+ ".ga",
65
+ ".ge",
66
+ ".gg",
67
+ ".com.gh",
68
+ ".com.gi",
69
+ ".gl",
70
+ ".gm",
71
+ ".gr",
72
+ ".com.gt",
73
+ ".gy",
74
+ ".com.hk",
75
+ ".hn",
76
+ ".hr",
77
+ ".ht",
78
+ ".hu",
79
+ ".co.id",
80
+ ".ie",
81
+ ".co.il",
82
+ ".im",
83
+ ".co.in",
84
+ ".iq",
85
+ ".is",
86
+ ".it",
87
+ ".je",
88
+ ".com.jm",
89
+ ".jo",
90
+ ".co.jp",
91
+ ".co.ke",
92
+ ".com.kh",
93
+ ".ki",
94
+ ".kg",
95
+ ".co.kr",
96
+ ".com.kw",
97
+ ".kz",
98
+ ".la",
99
+ ".com.lb",
100
+ ".li",
101
+ ".lk",
102
+ ".co.ls",
103
+ ".lt",
104
+ ".lu",
105
+ ".lv",
106
+ ".com.ly",
107
+ ".co.ma",
108
+ ".md",
109
+ ".me",
110
+ ".mg",
111
+ ".mk",
112
+ ".ml",
113
+ ".com.mm",
114
+ ".mn",
115
+ ".com.mt",
116
+ ".mu",
117
+ ".mv",
118
+ ".mw",
119
+ ".com.mx",
120
+ ".com.my",
121
+ ".co.mz",
122
+ ".com.na",
123
+ ".com.ng",
124
+ ".com.ni",
125
+ ".ne",
126
+ ".nl",
127
+ ".no",
128
+ ".com.np",
129
+ ".nr",
130
+ ".nu",
131
+ ".co.nz",
132
+ ".com.om",
133
+ ".com.pa",
134
+ ".com.pe",
135
+ ".com.pg",
136
+ ".com.ph",
137
+ ".com.pk",
138
+ ".pl",
139
+ ".pn",
140
+ ".com.pr",
141
+ ".ps",
142
+ ".pt",
143
+ ".com.py",
144
+ ".com.qa",
145
+ ".ro",
146
+ ".ru",
147
+ ".rw",
148
+ ".com.sa",
149
+ ".com.sb",
150
+ ".sc",
151
+ ".se",
152
+ ".com.sg",
153
+ ".sh",
154
+ ".si",
155
+ ".sk",
156
+ ".com.sl",
157
+ ".sn",
158
+ ".so",
159
+ ".sm",
160
+ ".sr",
161
+ ".st",
162
+ ".com.sv",
163
+ ".td",
164
+ ".tg",
165
+ ".co.th",
166
+ ".com.tj",
167
+ ".tl",
168
+ ".tm",
169
+ ".tn",
170
+ ".to",
171
+ ".com.tr",
172
+ ".tt",
173
+ ".com.tw",
174
+ ".co.tz",
175
+ ".com.ua",
176
+ ".co.ug",
177
+ ".co.uk",
178
+ ".com.uy",
179
+ ".co.uz",
180
+ ".com.vc",
181
+ ".co.ve",
182
+ ".co.vi",
183
+ ".com.vn",
184
+ ".vu",
185
+ ".ws",
186
+ ".rs",
187
+ ".co.za",
188
+ ".co.zm",
189
+ ".co.zw",
190
+ ".cat",
191
+ ];
192
+ export const BASELINE_STRICT_CSP = {
193
+ [Directive.DEFAULT_SRC]: ["'self'"],
194
+ [Directive.SCRIPT_SRC]: ["'self'"],
195
+ [Directive.STYLE_SRC]: ["'self'"],
196
+ [Directive.IMG_SRC]: ["'self'"],
197
+ [Directive.OBJECT_SRC]: ["'none'"],
198
+ [Directive.BASE_URI]: ["'self'"],
199
+ [Directive.FORM_ACTION]: ["'self'"],
200
+ };
201
+ /**
202
+ * Google Analytics Content Security Policy based on the official guidelines.
203
+ * https://developers.google.com/tag-platform/security/guides/csp#google_analytics_4_google_analytics
204
+ */
205
+ export const GOOGLE_ANALYTICS_CSP = {
206
+ ...BASELINE_STRICT_CSP,
207
+ [Directive.DEFAULT_SRC]: ["'self'"],
208
+ [Directive.SCRIPT_SRC]: ["'self'", "*.googletagmanager.com"],
209
+ [Directive.IMG_SRC]: [
210
+ "'self'",
211
+ "https://*.google-analytics.com",
212
+ "https://*.googletagmanager.com",
213
+ ],
214
+ [Directive.CONNECT_SRC]: [
215
+ "'self'",
216
+ "https://*.google-analytics.com",
217
+ "https://*.analytics.google.com",
218
+ "https://*.googletagmanager.com",
219
+ ],
220
+ };
221
+ export const GOOGLE_ANALYTICS_WITH_SIGNALS_CSP = {
222
+ ...BASELINE_STRICT_CSP,
223
+ ...GOOGLE_ANALYTICS_CSP,
224
+ [Directive.IMG_SRC]: [
225
+ "'self'",
226
+ "https://*.google-analytics.com",
227
+ "https://*.googletagmanager.com",
228
+ "https://*.g.doubleclick.net",
229
+ "https://*.google.com",
230
+ { "https://*.google.": googleSupportedTLDs },
231
+ ],
232
+ [Directive.CONNECT_SRC]: [
233
+ "'self'",
234
+ "https://*.google-analytics.com",
235
+ "https://*.googletagmanager.com",
236
+ "https://*.g.doubleclick.net",
237
+ "https://pagead2.googlesyndication.com",
238
+ { "https://*.google": googleSupportedTLDs },
239
+ ],
240
+ [Directive.FRAME_SRC]: [
241
+ "'self'",
242
+ "https://td.doubleclick.net",
243
+ "https://www.googletagmanager.com",
244
+ ],
245
+ };
package/dist/helpers.js CHANGED
@@ -45,7 +45,12 @@ export function warnOnCspIssues(csp, overrides = {}) {
45
45
  const options = { ...DEFAULT_WARNINGS, ...overrides };
46
46
  // 1. Overly permissive: * in script-src, style-src, etc.
47
47
  if (options.overlyPermissive) {
48
- [Directive.SCRIPT_SRC, Directive.STYLE_SRC, Directive.IMG_SRC, Directive.CONNECT_SRC].forEach(directive => {
48
+ [
49
+ Directive.SCRIPT_SRC,
50
+ Directive.STYLE_SRC,
51
+ Directive.IMG_SRC,
52
+ Directive.CONNECT_SRC,
53
+ ].forEach((directive) => {
49
54
  const rules = csp[directive];
50
55
  if (Array.isArray(rules) && rules.includes("*")) {
51
56
  console.warn(`[CSPrefabricate] Overly permissive: '*' found in ${directive}`);
@@ -54,7 +59,11 @@ export function warnOnCspIssues(csp, overrides = {}) {
54
59
  }
55
60
  // 2. Missing important directives
56
61
  if (options.missingDirectives) {
57
- [Directive.OBJECT_SRC, Directive.BASE_URI, Directive.FORM_ACTION].forEach(directive => {
62
+ [
63
+ Directive.OBJECT_SRC,
64
+ Directive.BASE_URI,
65
+ Directive.FORM_ACTION,
66
+ ].forEach((directive) => {
58
67
  if (!(directive in csp)) {
59
68
  console.warn(`[CSPrefabricate] Missing recommended directive: ${directive}`);
60
69
  }
@@ -62,7 +71,7 @@ export function warnOnCspIssues(csp, overrides = {}) {
62
71
  }
63
72
  // 3. Unsafe inline
64
73
  if (options.unsafeInline) {
65
- [Directive.SCRIPT_SRC, Directive.STYLE_SRC].forEach(directive => {
74
+ [Directive.SCRIPT_SRC, Directive.STYLE_SRC].forEach((directive) => {
66
75
  const rules = csp[directive];
67
76
  if (Array.isArray(rules) && rules.includes("'unsafe-inline'")) {
68
77
  console.warn(`[CSPrefabricate] 'unsafe-inline' found in ${directive}`);
@@ -73,7 +82,8 @@ export function warnOnCspIssues(csp, overrides = {}) {
73
82
  if (options.missingNonceOrHash) {
74
83
  const rules = csp[Directive.SCRIPT_SRC];
75
84
  if (Array.isArray(rules) && rules.includes("'unsafe-inline'")) {
76
- const hasNonceOrHash = rules.some((r) => typeof r === "string" && (r.startsWith("'nonce-") || r.startsWith("'sha")));
85
+ const hasNonceOrHash = rules.some((r) => typeof r === "string" &&
86
+ (r.startsWith("'nonce-") || r.startsWith("'sha")));
77
87
  if (!hasNonceOrHash) {
78
88
  console.warn(`[CSPrefabricate] 'unsafe-inline' in script-src without nonce or hash`);
79
89
  }
@@ -81,7 +91,7 @@ export function warnOnCspIssues(csp, overrides = {}) {
81
91
  }
82
92
  // 5. Permitting data: in img-src or media-src
83
93
  if (options.dataUri) {
84
- [Directive.IMG_SRC, Directive.MEDIA_SRC].forEach(directive => {
94
+ [Directive.IMG_SRC, Directive.MEDIA_SRC].forEach((directive) => {
85
95
  const rules = csp[directive];
86
96
  if (Array.isArray(rules) && rules.includes("data:")) {
87
97
  console.warn(`[CSPrefabricate] 'data:' allowed in ${directive}`);
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
- import { ContentSecurityPolicy, Directive } from "./types";
1
+ import { Directive } from "./types";
2
2
  import { create } from "./utils";
3
- export { create, Directive, ContentSecurityPolicy };
3
+ import * as Baseline from "./baseline";
4
+ export { Baseline };
5
+ export { create, Directive };
6
+ export type { ContentSecurityPolicy } from "./types";
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
1
  import { Directive } from "./types";
2
2
  import { create } from "./utils";
3
+ import * as Baseline from "./baseline";
4
+ export { Baseline };
3
5
  export { create, Directive };
package/dist/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { formatRule, isValidDirective, warnOnCspIssues } from "./helpers";
1
+ import { formatRule, isValidDirective, warnOnCspIssues, } from "./helpers";
2
2
  export const processRules = (rules) => {
3
3
  // Flatten and deduplicate rules
4
4
  const seen = new Set();
@@ -36,7 +36,8 @@ export const create = (obj, warningOptions) => {
36
36
  .map(([directive, rules]) => {
37
37
  if (Array.isArray(rules)) {
38
38
  // Filter out non-string/object values at runtime
39
- const filtered = rules.filter((r) => typeof r === "string" || (typeof r === "object" && r !== null));
39
+ const filtered = rules.filter((r) => typeof r === "string" ||
40
+ (typeof r === "object" && r !== null));
40
41
  const processed = processRules(filtered);
41
42
  return processed ? `${directive} ${processed}` : `${directive}`;
42
43
  }
package/package.json CHANGED
@@ -1,8 +1,55 @@
1
1
  {
2
2
  "name": "csprefabricate",
3
- "version": "0.3.1",
3
+ "version": "1.0.0",
4
+ "description": "Generate valid and secure Content Security Policies (CSP) with TypeScript.",
5
+ "keywords": [
6
+ "csp",
7
+ "content-security-policy",
8
+ "security",
9
+ "web-security",
10
+ "xss-protection",
11
+ "typescript"
12
+ ],
13
+ "homepage": "https://github.com/jamestoohey/csprefabricate#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/jamestoohey/csprefabricate/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/jamestoohey/csprefabricate.git"
20
+ },
21
+ "license": "MIT",
22
+ "author": {
23
+ "name": "James Toohey",
24
+ "url": "https://github.com/jamestoohey"
25
+ },
4
26
  "packageManager": "yarn@4.5.3",
5
27
  "type": "module",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ }
33
+ },
34
+ "main": "dist/index.js",
35
+ "types": "dist/index.d.ts",
36
+ "files": [
37
+ "dist/",
38
+ "README.md",
39
+ "LICENSE",
40
+ "CHANGELOG.md"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsc --project tsconfig.build.json",
44
+ "functional-test": "yarn build && tsx --test src/test/functional/functional.test.js",
45
+ "lint": "eslint .",
46
+ "pack": "npm pack",
47
+ "prepack": "yarn typecheck && yarn test && yarn build",
48
+ "prepublish": "yarn version check",
49
+ "test": "tsx --test src/test/**/*test.ts",
50
+ "test:watch": "tsx --test --watch src/test/**/*test.ts",
51
+ "typecheck": "tsc --noEmit"
52
+ },
6
53
  "devDependencies": {
7
54
  "@tsconfig/node-lts": "^22.0.1",
8
55
  "@types/node": "^22.13.5",
@@ -14,18 +61,7 @@
14
61
  "tsx": "^4.19.3",
15
62
  "typescript": "^5.7.3"
16
63
  },
17
- "main": "dist/index.js",
18
- "files": [
19
- "dist/"
20
- ],
21
- "scripts": {
22
- "build": "tsc --project tsconfig.build.json",
23
- "pack": "npm pack",
24
- "prepack": "yarn typecheck && yarn test && yarn build",
25
- "prettier": "prettier . --write",
26
- "prepublish": "yarn version check",
27
- "test": "tsx --test src/test/**/*test.ts",
28
- "typecheck": "tsc --noEmit",
29
- "lint": "eslint ."
64
+ "engines": {
65
+ "node": ">=18"
30
66
  }
31
67
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,123 +0,0 @@
1
- import { describe, it, beforeEach, after } from "node:test";
2
- import assert from "node:assert";
3
- import { formatRule, isValidDirective, warnOnCspIssues } from "../helpers";
4
- import { Directive } from "../types";
5
- void describe("Helpers tests", () => {
6
- void describe("isValidDirective", () => {
7
- void it("Returns true if directive is valid", () => {
8
- assert.strictEqual(isValidDirective(Directive.BASE_URI), true);
9
- assert.strictEqual(isValidDirective("default-src"), true);
10
- });
11
- void it("Returns false if directive is invalid", () => {
12
- assert.strictEqual(isValidDirective("some-src"), false);
13
- });
14
- });
15
- void describe("formatRule", () => {
16
- void it("Formats special rules with single quotes", () => {
17
- assert.strictEqual(formatRule("self"), `'self'`);
18
- });
19
- void it("Returns non-special rules", () => {
20
- assert.strictEqual(formatRule("google.com"), `google.com`);
21
- });
22
- });
23
- void describe("warnOnCspIssues", () => {
24
- let warnings = [];
25
- const originalWarn = console.warn;
26
- void beforeEach(() => {
27
- warnings = [];
28
- console.warn = (msg) => { warnings.push(msg); };
29
- });
30
- void after(() => {
31
- console.warn = originalWarn;
32
- });
33
- void it("Warns on overly permissive *", () => {
34
- const csp = {
35
- [Directive.SCRIPT_SRC]: ["*"]
36
- };
37
- warnOnCspIssues(csp);
38
- assert(warnings.some(w => w.includes("Overly permissive")));
39
- });
40
- void it("Warns on missing important directives", () => {
41
- const csp = {
42
- [Directive.DEFAULT_SRC]: ["'self'"]
43
- };
44
- warnOnCspIssues(csp);
45
- assert(warnings.some(w => w.includes("Missing recommended directive: object-src")));
46
- assert(warnings.some(w => w.includes("Missing recommended directive: base-uri")));
47
- assert(warnings.some(w => w.includes("Missing recommended directive: form-action")));
48
- });
49
- void it("Warns on 'unsafe-inline' in script-src", () => {
50
- const csp = {
51
- [Directive.SCRIPT_SRC]: ["'unsafe-inline'"]
52
- };
53
- warnOnCspIssues(csp);
54
- assert(warnings.some(w => w.includes("'unsafe-inline' found in script-src")));
55
- });
56
- void it("Warns on 'unsafe-inline' in script-src without nonce or hash", () => {
57
- const csp = {
58
- [Directive.SCRIPT_SRC]: ["'unsafe-inline'"]
59
- };
60
- warnOnCspIssues(csp);
61
- assert(warnings.some(w => w.includes("'unsafe-inline' in script-src without nonce or hash")));
62
- });
63
- void it("Does not warn if nonce is present with 'unsafe-inline'", () => {
64
- const csp = {
65
- [Directive.SCRIPT_SRC]: ["'unsafe-inline'", "'nonce-abc'"]
66
- };
67
- warnOnCspIssues(csp);
68
- assert(!warnings.some(w => w.includes("without nonce or hash")));
69
- });
70
- void it("Does not warn if hash is present with 'unsafe-inline'", () => {
71
- const csp = {
72
- [Directive.SCRIPT_SRC]: ["'unsafe-inline'", "'sha256-xyz'"]
73
- };
74
- warnOnCspIssues(csp);
75
- assert(!warnings.some(w => w.includes("without nonce or hash")));
76
- });
77
- void it("Warns on data: in img-src", () => {
78
- const csp = {
79
- [Directive.IMG_SRC]: ["data:"]
80
- };
81
- warnOnCspIssues(csp);
82
- assert(warnings.some(w => w.includes("'data:' allowed in img-src")));
83
- });
84
- void it("Respects warning options to opt out of specific warnings", () => {
85
- const csp = {
86
- [Directive.SCRIPT_SRC]: ["*"]
87
- };
88
- const opts = { overlyPermissive: false };
89
- warnOnCspIssues(csp, opts);
90
- assert(!warnings.some(w => w.includes("Overly permissive")));
91
- });
92
- void it("Does not warn if all warnings are disabled", () => {
93
- const csp = {
94
- [Directive.SCRIPT_SRC]: ["*"],
95
- [Directive.IMG_SRC]: ["data:"]
96
- };
97
- const opts = {
98
- overlyPermissive: false,
99
- missingDirectives: false,
100
- unsafeInline: false,
101
- missingNonceOrHash: false,
102
- dataUri: false
103
- };
104
- warnOnCspIssues(csp, opts);
105
- assert.strictEqual(warnings.length, 0);
106
- });
107
- void it("Warns only for enabled warnings", () => {
108
- const csp = {
109
- [Directive.SCRIPT_SRC]: ["*", "'unsafe-inline'"],
110
- [Directive.IMG_SRC]: ["data:"]
111
- };
112
- const opts = {
113
- overlyPermissive: false,
114
- missingDirectives: false,
115
- unsafeInline: true
116
- };
117
- warnOnCspIssues(csp, opts);
118
- assert(warnings.some(w => w.includes("'unsafe-inline' found in script-src")));
119
- assert(!warnings.some(w => w.includes("Overly permissive")));
120
- assert(!warnings.some(w => w.includes("Missing recommended directive")));
121
- });
122
- });
123
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,108 +0,0 @@
1
- import { describe, it, afterEach, mock, beforeEach } from "node:test";
2
- import assert from "node:assert";
3
- import { create, processRules } from "../utils";
4
- import { Directive } from "../types";
5
- void describe("Utils tests", () => {
6
- let mockWarn;
7
- const originalWarn = console.warn;
8
- void beforeEach(() => {
9
- mockWarn = mock.method(console, "warn", () => { });
10
- });
11
- void afterEach(() => {
12
- console.warn = originalWarn;
13
- });
14
- void describe("processRules", () => {
15
- void it("Processes rules provided as an array of strings (simple)", () => {
16
- const rules = ["self", "*.google.com", "*.google.com.au"];
17
- assert.strictEqual(processRules(rules), `'self' *.google.com *.google.com.au`);
18
- });
19
- void it("Processes rules provided a complex list of tlds", () => {
20
- const rules = ["self", { "*.google": [".com", ".com.au"] }];
21
- assert.strictEqual(processRules(rules), `'self' *.google.com *.google.com.au`);
22
- });
23
- });
24
- void describe("create", () => {
25
- void it("Formats a CSP string with all rules", () => {
26
- const csp = {
27
- [Directive.DEFAULT_SRC]: ["self"],
28
- [Directive.SCRIPT_SRC]: ["self", "js.example.com"],
29
- [Directive.STYLE_SRC]: ["self", "css.example.com"],
30
- [Directive.IMG_SRC]: [
31
- "self",
32
- { "*.google": [".com", ".com.au"] },
33
- ],
34
- [Directive.CONNECT_SRC]: ["self"],
35
- [Directive.FONT_SRC]: ["self", "font.example.com"],
36
- [Directive.OBJECT_SRC]: ["none"],
37
- [Directive.MEDIA_SRC]: ["self", "media.example.com"],
38
- [Directive.FRAME_SRC]: ["self"],
39
- [Directive.SANDBOX]: ["allow-scripts"],
40
- [Directive.REPORT_URI]: ["/my-report-uri"],
41
- [Directive.CHILD_SRC]: ["self"],
42
- [Directive.FORM_ACTION]: ["self"],
43
- [Directive.FRAME_ANCESTORS]: ["none"],
44
- [Directive.PLUGIN_TYPES]: ["application/pdf"],
45
- [Directive.BASE_URI]: ["self"],
46
- [Directive.REPORT_TO]: ["myGroupName"],
47
- [Directive.WORKER_SRC]: ["none"],
48
- [Directive.MANIFEST_SRC]: ["none"],
49
- [Directive.PREFETCH_SRC]: ["none"],
50
- [Directive.NAVIGATE_TO]: ["example.com"],
51
- [Directive.REQUIRE_TRUSTED_TYPES_FOR]: ["script"],
52
- [Directive.TRUSTED_TYPES]: ["none"],
53
- [Directive.UPGRADE_INSECURE_REQUESTS]: null,
54
- [Directive.BLOCK_ALL_MIXED_CONTENT]: null,
55
- };
56
- const cspString = create(csp);
57
- assert.strictEqual(cspString, "default-src 'self'; script-src 'self' js.example.com; style-src 'self' css.example.com; img-src 'self' *.google.com *.google.com.au; connect-src 'self'; font-src 'self' font.example.com; object-src 'none'; media-src 'self' media.example.com; frame-src 'self'; sandbox allow-scripts; report-uri /my-report-uri; child-src 'self'; form-action 'self'; frame-ancestors 'none'; plugin-types application/pdf; base-uri 'self'; report-to myGroupName; worker-src 'none'; manifest-src 'none'; prefetch-src 'none'; navigate-to example.com; require-trusted-types-for script; trusted-types 'none'; upgrade-insecure-requests; block-all-mixed-content;");
58
- });
59
- void it("Ignores invalid directives", () => {
60
- const csp = {
61
- [Directive.DEFAULT_SRC]: ["self"],
62
- // @ts-expect-error deliberate testing of invalid directive
63
- ["invalid-directive"]: ["self"],
64
- [Directive.IMG_SRC]: ["my.domain.com"]
65
- };
66
- const cspString = create(csp);
67
- assert.strictEqual(cspString, "default-src 'self'; img-src my.domain.com;");
68
- });
69
- void it("Calls warning helper when invoked", () => {
70
- const csp = {
71
- [Directive.DEFAULT_SRC]: ["self"],
72
- };
73
- create(csp);
74
- assert.equal(mockWarn.mock.calls.length, 3);
75
- const args = mockWarn.mock.calls.map((call) => call.arguments);
76
- assert.equal(args[0][0], "[CSPrefabricate] Missing recommended directive: object-src");
77
- assert.equal(args[1][0], "[CSPrefabricate] Missing recommended directive: base-uri");
78
- assert.equal(args[2][0], "[CSPrefabricate] Missing recommended directive: form-action");
79
- });
80
- });
81
- void describe("Edge cases", () => {
82
- void it("Handles empty rules array", () => {
83
- const csp = {
84
- [Directive.DEFAULT_SRC]: [],
85
- };
86
- const cspString = create(csp);
87
- assert.strictEqual(cspString, "default-src;");
88
- });
89
- void it("Handles completely empty policy object", () => {
90
- const csp = {};
91
- const cspString = create(csp);
92
- assert.strictEqual(cspString, "");
93
- });
94
- void it("Handles duplicate rules in an array", () => {
95
- const csp = {
96
- [Directive.DEFAULT_SRC]: ["self", "self", "example.com", "example.com"],
97
- };
98
- const cspString = create(csp);
99
- assert.strictEqual(cspString, "default-src 'self' example.com;");
100
- });
101
- void it("Ignores non-string, non-object values in rules array at runtime", () => {
102
- const csp = {
103
- [Directive.DEFAULT_SRC]: ["self", 123, false, null, undefined],
104
- };
105
- assert.doesNotThrow(() => create(csp));
106
- });
107
- });
108
- });