@rimelight/security 0.0.7 → 0.0.8

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.
@@ -1,3 +1,3 @@
1
- import { t as SecurityOptions } from "../types-CY5GDm3Z.mjs";
2
- import { t as buildCspHeader } from "../csp-DM1NVenC.mjs";
1
+ import { t as SecurityOptions } from "../types-Bg8w94Fn.mjs";
2
+ import { t as buildCspHeader } from "../csp-ESHHTg-n.mjs";
3
3
  export { SecurityOptions, buildCspHeader };
@@ -1,4 +1,4 @@
1
- import { t as SecurityOptions } from "./types-CY5GDm3Z.mjs";
1
+ import { t as SecurityOptions } from "./types-Bg8w94Fn.mjs";
2
2
  //#region src/csp.d.ts
3
3
  /**
4
4
  * Builds the Content-Security-Policy header string dynamically for a given request nonce.
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { t as SecurityOptions } from "./types-CY5GDm3Z.mjs";
2
- import { t as buildCspHeader } from "./csp-DM1NVenC.mjs";
1
+ import { t as SecurityOptions } from "./types-Bg8w94Fn.mjs";
2
+ import { t as buildCspHeader } from "./csp-ESHHTg-n.mjs";
3
3
  //#region src/vite.d.ts
4
- interface RimelightSecurityPlugin {
4
+ export interface RimelightSecurityPlugin {
5
5
  name: string;
6
6
  enforce?: "pre" | "post";
7
7
  resolveId?: (id: string) => string | null | undefined;
@@ -10,19 +10,18 @@ interface RimelightSecurityPlugin {
10
10
  generateBundle?: (options: any, bundle: Record<string, any>) => Promise<void> | void;
11
11
  [key: string]: any;
12
12
  }
13
- interface SecurityPluginOptions extends SecurityOptions {
13
+ export interface SecurityPluginOptions extends SecurityOptions {
14
14
  /**
15
15
  * Additional external script/style URLs to fetch and hash for SRI at build time.
16
16
  */
17
17
  resources?: string[];
18
18
  }
19
19
  /**
20
- * Pure Vite Security & SRI Plugin.
21
- * 1. Accepts framework-agnostic security options (domain, imgSrc, etc.) and exposes virtual:rimelight-security-config.
22
- * 2. Fetches and hashes external 3rd-party scripts/styles at build time.
23
- * 3. Emits SRI hashes for locally bundled JS/CSS chunks (Nuxt-security style).
24
- * 4. Exposes virtual:sri-manifest with a unified URL -> hash mapping.
20
+ * Pure Vite Security & SRI Plugin. 1. Accepts framework-agnostic security options (domain, imgSrc,
21
+ * etc.) and exposes virtual:rimelight-security-config. 2. Fetches and hashes external 3rd-party
22
+ * scripts/styles at build time. 3. Emits SRI hashes for locally bundled JS/CSS chunks
23
+ * (Nuxt-security style). 4. Exposes virtual:sri-manifest with a unified URL -> hash mapping.
25
24
  */
26
- declare function security(options?: SecurityPluginOptions): RimelightSecurityPlugin;
25
+ export declare function security(options?: SecurityPluginOptions): RimelightSecurityPlugin;
27
26
  //#endregion
28
- export { RimelightSecurityPlugin, SecurityOptions, SecurityPluginOptions, buildCspHeader, security, security as sri };
27
+ export { SecurityOptions, buildCspHeader, security as sri };
package/dist/index.mjs CHANGED
@@ -6,11 +6,10 @@ function hashContent(buffer) {
6
6
  return `sha384-${createHash("sha384").update(buffer).digest("base64")}`;
7
7
  }
8
8
  /**
9
- * Pure Vite Security & SRI Plugin.
10
- * 1. Accepts framework-agnostic security options (domain, imgSrc, etc.) and exposes virtual:rimelight-security-config.
11
- * 2. Fetches and hashes external 3rd-party scripts/styles at build time.
12
- * 3. Emits SRI hashes for locally bundled JS/CSS chunks (Nuxt-security style).
13
- * 4. Exposes virtual:sri-manifest with a unified URL -> hash mapping.
9
+ * Pure Vite Security & SRI Plugin. 1. Accepts framework-agnostic security options (domain, imgSrc,
10
+ * etc.) and exposes virtual:rimelight-security-config. 2. Fetches and hashes external 3rd-party
11
+ * scripts/styles at build time. 3. Emits SRI hashes for locally bundled JS/CSS chunks
12
+ * (Nuxt-security style). 4. Exposes virtual:sri-manifest with a unified URL -> hash mapping.
14
13
  */
15
14
  function security(options = {}) {
16
15
  const sriManifest = {};
@@ -23,7 +22,7 @@ function security(options = {}) {
23
22
  name: "vite-plugin-rimelight-security",
24
23
  enforce: "post",
25
24
  async buildStart() {
26
- if (process.env.NODE_ENV === "development") return;
25
+ if (process.env["NODE_ENV"] === "development") return;
27
26
  await Promise.all(externalUrls.map(async (url) => {
28
27
  if (!url.startsWith("https://")) return;
29
28
  try {
@@ -1,10 +1,10 @@
1
- import { t as SecurityOptions } from "../types-CY5GDm3Z.mjs";
1
+ import { t as SecurityOptions } from "../types-Bg8w94Fn.mjs";
2
2
  //#region src/middleware/security.d.ts
3
3
  /**
4
- * Universal Security Middleware for Hono and Web Standards (Fetch API).
5
- * Injects security headers, CSP with per-request nonce, and SRI attributes.
4
+ * Universal Security Middleware for Hono and Web Standards (Fetch API). Injects security headers,
5
+ * CSP with per-request nonce, and SRI attributes.
6
6
  */
7
- declare function security(options?: SecurityOptions): (c: any, next: any) => Promise<Response | void>;
7
+ export declare function security(options?: SecurityOptions): (c: any, next: any) => Promise<Response | void>;
8
8
  //#endregion
9
9
  //#region src/middleware/dev-only.d.ts
10
10
  /**
@@ -17,6 +17,5 @@ declare function security(options?: SecurityOptions): (c: any, next: any) => Pro
17
17
  * import { devOnly } from "@rimelight/security/middleware"
18
18
  * app.use(devOnly)
19
19
  */
20
- declare const devOnly: (c: any, next: any) => Promise<Response>;
21
- //#endregion
22
- export { devOnly, security };
20
+ export declare const devOnly: (c: any, next: any) => Promise<Response>;
21
+ //#endregion
@@ -10,8 +10,15 @@ function isConfigRecord(obj) {
10
10
  }
11
11
  const manifest$1 = isManifestRecord(manifest) ? manifest : {};
12
12
  const defaultPluginConfig = isConfigRecord(config) ? config : {};
13
- function applySecurityHeaders(response, options, origin, nonce) {
13
+ function applySecurityHeaders(response, options, origin, nonce, reportOnlyOptions) {
14
14
  const cspHeader = buildCspHeader(options, nonce);
15
+ const reportOnlyHeader = reportOnlyOptions ? buildCspHeader({
16
+ ...reportOnlyOptions,
17
+ directives: {
18
+ ...reportOnlyOptions.directives,
19
+ "upgrade-insecure-requests": false
20
+ }
21
+ }, nonce) : void 0;
15
22
  const headers = new Headers(response.headers);
16
23
  headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
17
24
  headers.set("X-Content-Type-Options", "nosniff");
@@ -21,6 +28,7 @@ function applySecurityHeaders(response, options, origin, nonce) {
21
28
  headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), payment=(), usb=()");
22
29
  headers.set("No-Vary-Search", "except=(\"q\" \"search\" \"locale\"), params=?1");
23
30
  if (cspHeader) headers.set("Content-Security-Policy", cspHeader);
31
+ if (reportOnlyHeader) headers.set("Content-Security-Policy-Report-Only", reportOnlyHeader);
24
32
  if (origin) headers.set("Link", [
25
33
  `<${origin}/sitemap-index.xml>; rel="sitemap"`,
26
34
  `<${origin}/llms.txt>; rel="llms"`,
@@ -35,8 +43,8 @@ function applySecurityHeaders(response, options, origin, nonce) {
35
43
  });
36
44
  }
37
45
  /**
38
- * Universal Security Middleware for Hono and Web Standards (Fetch API).
39
- * Injects security headers, CSP with per-request nonce, and SRI attributes.
46
+ * Universal Security Middleware for Hono and Web Standards (Fetch API). Injects security headers,
47
+ * CSP with per-request nonce, and SRI attributes.
40
48
  */
41
49
  function security(options = {}) {
42
50
  const mergedOptions = {
@@ -49,17 +57,17 @@ function security(options = {}) {
49
57
  scriptResources: [...defaultPluginConfig.scriptResources || [], ...options.scriptResources || []],
50
58
  styleResources: [...defaultPluginConfig.styleResources || [], ...options.styleResources || []],
51
59
  directives: {
52
- ...defaultPluginConfig.directives || {},
53
- ...options.directives || {}
60
+ ...defaultPluginConfig.directives,
61
+ ...options.directives
54
62
  },
55
63
  headers: {
56
- ...defaultPluginConfig.headers || {},
57
- ...options.headers || {}
64
+ ...defaultPluginConfig.headers,
65
+ ...options.headers
58
66
  }
59
67
  };
60
68
  return async (c, next) => {
61
- const isProd = import.meta.env?.PROD ?? process.env.NODE_ENV === "production";
62
- const isDev = import.meta.env?.DEV ?? process.env.NODE_ENV === "development";
69
+ const isProd = import.meta.env?.PROD ?? process.env["NODE_ENV"] === "production";
70
+ const isDev = import.meta.env?.DEV ?? process.env["NODE_ENV"] === "development";
63
71
  const req = c.req?.raw || c.request || c.req;
64
72
  const proto = req?.headers?.get("x-forwarded-proto");
65
73
  if (isProd && proto === "http" && req?.url) {
@@ -76,13 +84,55 @@ function security(options = {}) {
76
84
  return c.res;
77
85
  }
78
86
  if (isDev) {
79
- c.res = applySecurityHeaders(response, {
87
+ const devDirectives = {
88
+ ...mergedOptions.directives,
89
+ "default-src": false,
90
+ "script-src": [
91
+ "'self'",
92
+ "'unsafe-inline'",
93
+ "'unsafe-eval'",
94
+ "https:",
95
+ "http:",
96
+ "ws:",
97
+ "wss:"
98
+ ],
99
+ "style-src": [
100
+ "'self'",
101
+ "'unsafe-inline'",
102
+ "https:",
103
+ "http:"
104
+ ],
105
+ "connect-src": [
106
+ "'self'",
107
+ "ws:",
108
+ "wss:",
109
+ "http:",
110
+ "https:"
111
+ ],
112
+ "img-src": [
113
+ "'self'",
114
+ "data:",
115
+ "blob:",
116
+ "https:",
117
+ "http:"
118
+ ]
119
+ };
120
+ const reportOnlySimulation = {
80
121
  ...mergedOptions,
81
122
  directives: {
82
123
  ...mergedOptions.directives,
83
- "default-src": false
124
+ "style-src": ["'self'", "'unsafe-inline'"],
125
+ "script-src": [
126
+ "'self'",
127
+ "'unsafe-inline'",
128
+ "'unsafe-eval'"
129
+ ]
84
130
  }
85
- }, origin);
131
+ };
132
+ c.res = applySecurityHeaders(response, {
133
+ ...mergedOptions,
134
+ directives: devDirectives
135
+ }, origin, void 0, reportOnlySimulation);
86
136
  return c.res;
87
137
  }
88
138
  const nonce = crypto.randomUUID().replace(/-/g, "");
@@ -1,8 +1,8 @@
1
1
  //#region src/types.d.ts
2
2
  interface SecurityOptions {
3
3
  /**
4
- * The base domain name for this project (e.g. "idantity.me") used to autoconfigure
5
- * standard allowed domains and img-src CDN targets.
4
+ * The base domain name for this project (e.g. "idantity.me") used to autoconfigure standard
5
+ * allowed domains and img-src CDN targets.
6
6
  */
7
7
  domain?: string;
8
8
  /**
@@ -34,8 +34,8 @@ interface SecurityOptions {
34
34
  */
35
35
  trustedTypes?: boolean;
36
36
  /**
37
- * Granular CSP directive overrides. Values are arrays of directive tokens, e.g.:
38
- * { "frame-ancestors": ["'self'"] }
37
+ * Granular CSP directive overrides. Values are arrays of directive tokens, e.g.: {
38
+ * "frame-ancestors": ["'self'"] }
39
39
  */
40
40
  directives?: Record<string, string[] | string | boolean>;
41
41
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rimelight/security",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "private": false,
5
5
  "description": "Rimelight Entertainment's Security Package",
6
6
  "homepage": "https://rimelight.com/docs",
@@ -40,8 +40,8 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@astrojs/check": "0.9.10",
43
- "@rimelight/config": "0.0.4",
44
- "astro": "7.3.1",
43
+ "@rimelight/config": "0.0.5",
44
+ "astro": "7.3.2",
45
45
  "typescript": "6.0.3"
46
46
  },
47
47
  "peerDependencies": {
@@ -121,3 +121,46 @@
121
121
  });
122
122
  })();
123
123
  </script>
124
+
125
+ <!-- Development CSP Violation Reporter -->
126
+ {import.meta.env.DEV && (
127
+ <script>
128
+ (function() {
129
+ // Ignore known internal dev-only tooling noise
130
+ function isDevInternal(e: SecurityPolicyViolationEvent): boolean {
131
+ const file = e.sourceFile || "";
132
+ const sample = e.sample || "";
133
+ const blockedUri = e.blockedURI || "";
134
+
135
+ if (file.includes("dev-toolbar") || file.includes("toolbar-") || file.includes("astro_runtime_client")) return true;
136
+ if (file.includes("vite/client") || file.includes("@vite/client") || file.includes("/@fs/") || file.includes("/@id/")) return true;
137
+ if (file.includes("node_modules/.vite") || file.includes("client:")) return true;
138
+ if (blockedUri.startsWith("ws://") || blockedUri.startsWith("wss://")) return true;
139
+ if (sample.includes("--astro-dev-toolbar") || sample.includes("astro-dev-toolbar")) return true;
140
+ return false;
141
+ }
142
+
143
+ const reported = new Set<string>();
144
+
145
+ window.addEventListener("securitypolicyviolation", function(e) {
146
+ if (e.disposition !== "report") return; // Only notify on report-only (prod violation simulation)
147
+ if (isDevInternal(e)) return;
148
+
149
+ const key = `${e.effectiveDirective}:${e.blockedURI || e.sample || ""}`;
150
+ if (reported.has(key)) return;
151
+ reported.add(key);
152
+
153
+ console.warn(
154
+ `%c[Rimelight Security]%c Allowed in DEV, but will fail in PRODUCTION:\n` +
155
+ `Directive: ${e.effectiveDirective}\n` +
156
+ (e.blockedURI ? `Blocked URI: ${e.blockedURI}\n` : "") +
157
+ (e.sourceFile ? `Source: ${e.sourceFile}:${e.lineNumber || 0}\n` : "") +
158
+ (e.sample ? `Sample: ${e.sample.slice(0, 100)}\n` : "") +
159
+ `Fix: Add to imgSrc, scriptResources, styleResources or use nonces in astro.config.ts`,
160
+ "background: #e11d48; color: white; padding: 2px 6px; border-radius: 4px; font-weight: bold;",
161
+ "color: inherit; font-weight: normal;"
162
+ );
163
+ });
164
+ })();
165
+ </script>
166
+ )}