@cosmicdrift/kumiko-headless 0.146.2 → 0.146.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-headless",
3
- "version": "0.146.2",
3
+ "version": "0.146.4",
4
4
  "description": "Headless UI logic for Kumiko — Dispatcher contract, Form-Controller, View-Model, Nav-Resolver. Plattform- und React-frei; jeder Renderer (renderer, renderer-web, renderer-native, …) komponiert darauf.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -36,7 +36,7 @@
36
36
  }
37
37
  },
38
38
  "dependencies": {
39
- "@cosmicdrift/kumiko-framework": "0.146.2",
39
+ "@cosmicdrift/kumiko-framework": "0.146.4",
40
40
  "zod": "^4.4.3"
41
41
  },
42
42
  "publishConfig": {
@@ -0,0 +1,20 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { createHash } from "node:crypto";
3
+ import { APEX_LIGHTBOX_SCRIPT, APEX_LIGHTBOX_SCRIPT_CSP_HASH } from "../lightbox";
4
+
5
+ function scriptBody(html: string): string {
6
+ const match = html.match(/^<script>(?<body>[\s\S]*)<\/script>$/);
7
+ const body = match?.groups?.["body"];
8
+ if (body === undefined) {
9
+ throw new Error("APEX_LIGHTBOX_SCRIPT isn't a single <script>...</script> string");
10
+ }
11
+ return body;
12
+ }
13
+
14
+ describe("APEX_LIGHTBOX_SCRIPT_CSP_HASH", () => {
15
+ test("matches the actual script content byte-for-byte", () => {
16
+ const body = scriptBody(APEX_LIGHTBOX_SCRIPT);
17
+ const hash = `sha256-${createHash("sha256").update(body).digest("base64")}`;
18
+ expect(hash).toBe(APEX_LIGHTBOX_SCRIPT_CSP_HASH);
19
+ });
20
+ });
package/src/apex/index.ts CHANGED
@@ -9,7 +9,11 @@ import { APEX_STRUCTURAL_CSS } from "./css";
9
9
  import { APEX_LIGHTBOX_HTML, APEX_LIGHTBOX_SCRIPT } from "./lightbox";
10
10
 
11
11
  export { APEX_NAV_MENU_CSS, APEX_STRUCTURAL_CSS } from "./css";
12
- export { APEX_LIGHTBOX_HTML, APEX_LIGHTBOX_SCRIPT } from "./lightbox";
12
+ export {
13
+ APEX_LIGHTBOX_HTML,
14
+ APEX_LIGHTBOX_SCRIPT,
15
+ APEX_LIGHTBOX_SCRIPT_CSP_HASH,
16
+ } from "./lightbox";
13
17
 
14
18
  export type ApexTheme = "light" | "dark";
15
19
 
@@ -6,8 +6,10 @@ export const APEX_LIGHTBOX_HTML = `<dialog id="apex-lightbox" class="apex-lightb
6
6
  <img class="apex-lightbox__img" alt="" />
7
7
  </dialog>`;
8
8
 
9
- /** ponytail: one delegated listener; no-op when no .shot-frame on the page. */
10
- export const APEX_LIGHTBOX_SCRIPT = `<script>
9
+ // Split from APEX_LIGHTBOX_SCRIPT so APEX_LIGHTBOX_SCRIPT_CSP_HASH hashes
10
+ // exactly the bytes the browser executes — CSP's script-src hash-source
11
+ // covers the content between <script> and </script>, nothing more/less.
12
+ const APEX_LIGHTBOX_SCRIPT_BODY = `
11
13
  (function () {
12
14
  var dlg = document.getElementById("apex-lightbox");
13
15
  if (!dlg) return;
@@ -38,4 +40,17 @@ export const APEX_LIGHTBOX_SCRIPT = `<script>
38
40
  close();
39
41
  });
40
42
  })();
41
- </script>`;
43
+ `;
44
+
45
+ /** ponytail: one delegated listener; no-op when no .shot-frame on the page. */
46
+ export const APEX_LIGHTBOX_SCRIPT = `<script>${APEX_LIGHTBOX_SCRIPT_BODY}</script>`;
47
+
48
+ // Apps that enforce `script-src 'self'` without `'unsafe-inline'`/nonce (the
49
+ // apex renderer has no per-request nonce hook — its output is often
50
+ // pre-rendered to static HTML at build time, where a nonce can't work
51
+ // anyway) need a hash-source instead. Add this to your CSP's script-src,
52
+ // e.g. `script-src 'self' 'sha256-...'` (value = this constant).
53
+ // lightbox.test.ts asserts this matches APEX_LIGHTBOX_SCRIPT_BODY byte-for-
54
+ // byte, so an edit to the script that doesn't update the hash fails CI
55
+ // instead of silently breaking every strict-CSP consumer's lightbox.
56
+ export const APEX_LIGHTBOX_SCRIPT_CSP_HASH = "sha256-f+hHLpDuQsjmtFZCjdM13D9NaMTCyOKaawAhfLf/X9o=";