@optilogic/core 1.2.0 → 1.2.2

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": "@optilogic/core",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Core UI components for Optilogic - A professional React component library",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,22 +1,130 @@
1
+ import * as React from "react";
1
2
  import { cn } from "../../../utils/cn";
2
3
  import type { FileRendererProps } from "../types";
3
4
 
5
+ /**
6
+ * Strict CSP applied at two layers:
7
+ *
8
+ * 1. `csp` attribute on the <iframe> — browser-enforced, cannot be
9
+ * overridden or loosened by anything inside the document.
10
+ * (Supported in Chromium-based browsers.)
11
+ *
12
+ * 2. <meta http-equiv="Content-Security-Policy"> in the <head> of the
13
+ * srcdoc — fallback for browsers that don't support the `csp` attr.
14
+ * Safe from injection: content is placed in <body>, and per the
15
+ * HTML5 spec CSP meta tags are only honoured in <head>. Multiple
16
+ * CSP policies are additive (can only restrict, never loosen).
17
+ */
18
+ const CSP_POLICY = [
19
+ "default-src 'none'",
20
+ "script-src 'unsafe-inline'",
21
+ "style-src 'unsafe-inline'",
22
+ "img-src data: blob:",
23
+ ].join("; ");
24
+
25
+ /**
26
+ * All Permissions-Policy features explicitly denied. Prevents sandboxed
27
+ * content from accessing device APIs even if a future browser grants
28
+ * them by default.
29
+ */
30
+ const PERMISSIONS_POLICY = [
31
+ "camera=()",
32
+ "microphone=()",
33
+ "geolocation=()",
34
+ "payment=()",
35
+ "usb=()",
36
+ "clipboard-read=()",
37
+ "clipboard-write=()",
38
+ "display-capture=()",
39
+ "fullscreen=()",
40
+ "autoplay=()",
41
+ "web-share=()",
42
+ "screen-wake-lock=()",
43
+ "xr-spatial-tracking=()",
44
+ "magnetometer=()",
45
+ "gyroscope=()",
46
+ "accelerometer=()",
47
+ ].join(", ");
48
+
49
+ function buildSandboxedHtml(content: string): string {
50
+ return `<!DOCTYPE html>
51
+ <html>
52
+ <head>
53
+ <meta http-equiv="Content-Security-Policy" content="${CSP_POLICY}">
54
+ <script>
55
+ // Neutralise APIs that the sandbox + CSP can't fully block.
56
+ // This runs in <head> before any user content in <body>.
57
+ // Uses Object.defineProperty to make overrides non-configurable
58
+ // so user scripts cannot restore the original via prototype tricks.
59
+ (function(){
60
+ // postMessage: iframe can message parent even without allow-same-origin.
61
+ // Kill it so content can't probe or spam any future parent listeners.
62
+ // Also kill parent/top refs as an extra layer.
63
+ var noop = function(){};
64
+ try { Object.defineProperty(window, 'postMessage', { value: noop, writable: false, configurable: false }); } catch(e) {}
65
+ try { Object.defineProperty(window, 'parent', { value: window, writable: false, configurable: false }); } catch(e) {}
66
+ try { Object.defineProperty(window, 'top', { value: window, writable: false, configurable: false }); } catch(e) {}
67
+ try { Object.defineProperty(window, 'opener', { value: null, writable: false, configurable: false }); } catch(e) {}
68
+
69
+ // RTCPeerConnection: not governed by CSP; could contact a STUN server
70
+ // over UDP to leak the user's IP. Kill all browser-prefixed variants.
71
+ var rtcNames = ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection'];
72
+ for (var i = 0; i < rtcNames.length; i++) {
73
+ try { Object.defineProperty(window, rtcNames[i], { value: undefined, writable: false, configurable: false }); } catch(e) {}
74
+ }
75
+ })();
76
+ </script>
77
+ </head>
78
+ <body>${content}</body>
79
+ </html>`;
80
+ }
81
+
4
82
  /**
5
83
  * HtmlRenderer
6
84
  *
7
- * Renders HTML content in a fully sandboxed iframe.
85
+ * Renders HTML content in a triple-locked iframe:
86
+ *
87
+ * Layer 1 — `sandbox="allow-scripts"` (no allow-same-origin):
88
+ * Opaque origin. No access to parent DOM, cookies, localStorage,
89
+ * sessionStorage, or IndexedDB. No form submission, popups,
90
+ * top-navigation, or pointer-lock.
91
+ *
92
+ * Layer 2 — Content-Security-Policy (iframe `csp` attr + meta fallback):
93
+ * Blocks ALL network requests (fetch, XHR, WebSocket, sendBeacon,
94
+ * image pings, external scripts/styles/fonts). Only inline scripts,
95
+ * inline styles, and data:/blob: images are allowed.
96
+ *
97
+ * Layer 3 — Permissions-Policy via `allow` attribute:
98
+ * Explicitly denies every device/sensor API (camera, microphone,
99
+ * geolocation, payment, USB, display-capture, etc.).
100
+ *
101
+ * Additional hardening:
102
+ * - referrerpolicy="no-referrer" — no URL leakage to embedded content
8
103
  */
9
104
  export function HtmlRenderer({
10
105
  content,
11
106
  fileName,
12
107
  className,
13
108
  }: FileRendererProps) {
109
+ const srcDoc = React.useMemo(
110
+ () => buildSandboxedHtml(content ?? ""),
111
+ [content],
112
+ );
113
+
114
+ // The `csp` attribute is a valid HTML attribute for iframes (enforces CSP
115
+ // at the browser level, cannot be overridden by document content) but is
116
+ // not yet in React's type definitions. We spread it to avoid a TS error.
117
+ const iframeProps = { csp: CSP_POLICY } as React.IframeHTMLAttributes<HTMLIFrameElement>;
118
+
14
119
  return (
15
120
  <iframe
16
- srcDoc={content ?? ""}
121
+ srcDoc={srcDoc}
17
122
  sandbox="allow-scripts"
18
123
  title={fileName}
124
+ referrerPolicy="no-referrer"
125
+ allow={PERMISSIONS_POLICY}
19
126
  className={cn("h-full w-full border-0", className)}
127
+ {...iframeProps}
20
128
  />
21
129
  );
22
130
  }