@rimelight/security 0.0.19 → 0.0.21

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/src/vite.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { createHash } from "node:crypto"
2
+
3
+ import type { SecurityOptions } from "./types"
4
+
5
+ export interface RimelightSecurityPlugin {
6
+ name: string
7
+ enforce?: "pre" | "post"
8
+ resolveId?: (id: string) => string | null | undefined
9
+ load?: (id: string) => string | null | undefined
10
+ buildStart?: () => Promise<void> | void
11
+ generateBundle?: (options: any, bundle: Record<string, any>) => Promise<void> | void
12
+ [key: string]: any
13
+ }
14
+
15
+ export interface SecurityPluginOptions extends SecurityOptions {
16
+ /**
17
+ * Additional external script/style URLs to fetch and hash for SRI at build time.
18
+ */
19
+ resources?: string[]
20
+ }
21
+
22
+ const DEFAULT_RESOURCES = ["https://betterlytics.io/analytics.js"]
23
+
24
+ function hashContent(buffer: Buffer | ArrayBuffer | Uint8Array | string): string {
25
+ const hash = createHash("sha384")
26
+ .update(buffer as any)
27
+ .digest("base64")
28
+ return `sha384-${hash}`
29
+ }
30
+
31
+ /**
32
+ * Pure Vite Security & SRI Plugin. 1. Accepts framework-agnostic security options (domain, imgSrc,
33
+ * etc.) and exposes virtual:rimelight-security-config. 2. Fetches and hashes external 3rd-party
34
+ * scripts/styles at build time. 3. Emits SRI hashes for locally bundled JS/CSS chunks
35
+ * (Nuxt-security style). 4. Exposes virtual:sri-manifest with a unified URL -> hash mapping.
36
+ */
37
+ export function security(options: SecurityPluginOptions = {}): RimelightSecurityPlugin {
38
+ const sriManifest: Record<string, string> = {}
39
+ const externalUrls = Array.from(new Set([...DEFAULT_RESOURCES, ...(options.resources || [])]))
40
+
41
+ const sriVirtualId = "virtual:sri-manifest"
42
+ const resolvedSriVirtualId = "\0" + sriVirtualId
43
+
44
+ const configVirtualId = "virtual:rimelight-security-config"
45
+ const resolvedConfigVirtualId = "\0" + configVirtualId
46
+
47
+ return {
48
+ name: "vite-plugin-rimelight-security",
49
+ enforce: "post",
50
+
51
+ async buildStart() {
52
+ // 1. Fetch & Hash External Resources (3rd-party SRI)
53
+ if (process.env["NODE_ENV"] === "development") return
54
+
55
+ await Promise.all(
56
+ externalUrls.map(async (url) => {
57
+ if (!url.startsWith("https://")) return
58
+ try {
59
+ const parsed = new URL(url)
60
+ if (parsed.hostname.includes("*")) return
61
+ const pathname = parsed.pathname
62
+ if (pathname === "/" || pathname === "" || pathname.endsWith("/")) return
63
+
64
+ const response = await fetch(url)
65
+ if (!response.ok) return
66
+ const buffer = await response.arrayBuffer()
67
+ sriManifest[url] = hashContent(buffer)
68
+ } catch {
69
+ // Silently ignore unreachable external resources during build
70
+ }
71
+ })
72
+ )
73
+ },
74
+
75
+ generateBundle(_outputOptions, bundle) {
76
+ // 2. Hash Emitted Bundle Chunks & Assets (1st-party SRI)
77
+ for (const [fileName, chunk] of Object.entries(bundle)) {
78
+ if (!fileName.endsWith(".js") && !fileName.endsWith(".css")) continue
79
+
80
+ let content: string | Uint8Array | undefined
81
+ if (chunk.type === "chunk") {
82
+ content = chunk.code
83
+ } else if (chunk.type === "asset") {
84
+ content = chunk.source
85
+ }
86
+
87
+ if (content) {
88
+ const hash = hashContent(content)
89
+ // Store both absolute leading slash path and relative file name
90
+ sriManifest[`/${fileName.replace(/^\/+/, "")}`] = hash
91
+ sriManifest[fileName] = hash
92
+ }
93
+ }
94
+ },
95
+
96
+ resolveId(id: string) {
97
+ if (id === sriVirtualId) return resolvedSriVirtualId
98
+ if (id === configVirtualId) return resolvedConfigVirtualId
99
+ return null
100
+ },
101
+
102
+ load(id: string) {
103
+ if (id === resolvedSriVirtualId) {
104
+ return `export const manifest = ${JSON.stringify(sriManifest)};`
105
+ }
106
+ if (id === resolvedConfigVirtualId) {
107
+ return `export const config = ${JSON.stringify(options)};`
108
+ }
109
+ return null
110
+ }
111
+ }
112
+ }
113
+
114
+ export { security as sri }