@optilogic/core 1.2.0 → 1.2.1
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/dist/index.cjs +64 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +64 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/file-view/components/HtmlRenderer.tsx +108 -2
package/package.json
CHANGED
|
@@ -1,22 +1,128 @@
|
|
|
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
|
+
"display-capture=()",
|
|
37
|
+
"fullscreen=()",
|
|
38
|
+
"autoplay=()",
|
|
39
|
+
"web-share=()",
|
|
40
|
+
"screen-wake-lock=()",
|
|
41
|
+
"xr-spatial-tracking=()",
|
|
42
|
+
"magnetometer=()",
|
|
43
|
+
"gyroscope=()",
|
|
44
|
+
"accelerometer=()",
|
|
45
|
+
].join(", ");
|
|
46
|
+
|
|
47
|
+
function buildSandboxedHtml(content: string): string {
|
|
48
|
+
return `<!DOCTYPE html>
|
|
49
|
+
<html>
|
|
50
|
+
<head>
|
|
51
|
+
<meta http-equiv="Content-Security-Policy" content="${CSP_POLICY}">
|
|
52
|
+
<script>
|
|
53
|
+
// Neutralise APIs that the sandbox + CSP can't fully block.
|
|
54
|
+
// This runs in <head> before any user content in <body>.
|
|
55
|
+
// Uses Object.defineProperty to make overrides non-configurable
|
|
56
|
+
// so user scripts cannot restore the original via prototype tricks.
|
|
57
|
+
(function(){
|
|
58
|
+
// postMessage: iframe can message parent even without allow-same-origin.
|
|
59
|
+
// Kill it so content can't probe or spam any future parent listeners.
|
|
60
|
+
// Also kill parent/top refs as an extra layer.
|
|
61
|
+
var noop = function(){};
|
|
62
|
+
try { Object.defineProperty(window, 'postMessage', { value: noop, writable: false, configurable: false }); } catch(e) {}
|
|
63
|
+
try { Object.defineProperty(window, 'parent', { value: window, writable: false, configurable: false }); } catch(e) {}
|
|
64
|
+
try { Object.defineProperty(window, 'top', { value: window, writable: false, configurable: false }); } catch(e) {}
|
|
65
|
+
try { Object.defineProperty(window, 'opener', { value: null, writable: false, configurable: false }); } catch(e) {}
|
|
66
|
+
|
|
67
|
+
// RTCPeerConnection: not governed by CSP; could contact a STUN server
|
|
68
|
+
// over UDP to leak the user's IP. Kill all browser-prefixed variants.
|
|
69
|
+
var rtcNames = ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection'];
|
|
70
|
+
for (var i = 0; i < rtcNames.length; i++) {
|
|
71
|
+
try { Object.defineProperty(window, rtcNames[i], { value: undefined, writable: false, configurable: false }); } catch(e) {}
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
74
|
+
</script>
|
|
75
|
+
</head>
|
|
76
|
+
<body>${content}</body>
|
|
77
|
+
</html>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
4
80
|
/**
|
|
5
81
|
* HtmlRenderer
|
|
6
82
|
*
|
|
7
|
-
* Renders HTML content in a
|
|
83
|
+
* Renders HTML content in a triple-locked iframe:
|
|
84
|
+
*
|
|
85
|
+
* Layer 1 — `sandbox="allow-scripts"` (no allow-same-origin):
|
|
86
|
+
* Opaque origin. No access to parent DOM, cookies, localStorage,
|
|
87
|
+
* sessionStorage, or IndexedDB. No form submission, popups,
|
|
88
|
+
* top-navigation, or pointer-lock.
|
|
89
|
+
*
|
|
90
|
+
* Layer 2 — Content-Security-Policy (iframe `csp` attr + meta fallback):
|
|
91
|
+
* Blocks ALL network requests (fetch, XHR, WebSocket, sendBeacon,
|
|
92
|
+
* image pings, external scripts/styles/fonts). Only inline scripts,
|
|
93
|
+
* inline styles, and data:/blob: images are allowed.
|
|
94
|
+
*
|
|
95
|
+
* Layer 3 — Permissions-Policy via `allow` attribute:
|
|
96
|
+
* Explicitly denies every device/sensor API (camera, microphone,
|
|
97
|
+
* geolocation, payment, USB, display-capture, etc.).
|
|
98
|
+
*
|
|
99
|
+
* Additional hardening:
|
|
100
|
+
* - referrerpolicy="no-referrer" — no URL leakage to embedded content
|
|
8
101
|
*/
|
|
9
102
|
export function HtmlRenderer({
|
|
10
103
|
content,
|
|
11
104
|
fileName,
|
|
12
105
|
className,
|
|
13
106
|
}: FileRendererProps) {
|
|
107
|
+
const srcDoc = React.useMemo(
|
|
108
|
+
() => buildSandboxedHtml(content ?? ""),
|
|
109
|
+
[content],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// The `csp` attribute is a valid HTML attribute for iframes (enforces CSP
|
|
113
|
+
// at the browser level, cannot be overridden by document content) but is
|
|
114
|
+
// not yet in React's type definitions. We spread it to avoid a TS error.
|
|
115
|
+
const iframeProps = { csp: CSP_POLICY } as React.IframeHTMLAttributes<HTMLIFrameElement>;
|
|
116
|
+
|
|
14
117
|
return (
|
|
15
118
|
<iframe
|
|
16
|
-
srcDoc={
|
|
119
|
+
srcDoc={srcDoc}
|
|
17
120
|
sandbox="allow-scripts"
|
|
18
121
|
title={fileName}
|
|
122
|
+
referrerPolicy="no-referrer"
|
|
123
|
+
allow={PERMISSIONS_POLICY}
|
|
19
124
|
className={cn("h-full w-full border-0", className)}
|
|
125
|
+
{...iframeProps}
|
|
20
126
|
/>
|
|
21
127
|
);
|
|
22
128
|
}
|