Skip to content

Commit 8cdcffc

Browse files
committed
implement a trusted mode for iframe html
- since there are use cases for this.
1 parent 51e388d commit 8cdcffc

File tree

2 files changed

+71
-5
lines changed

2 files changed

+71
-5
lines changed

src/packages/frontend/frame-editors/html-editor/iframe-html.tsx

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,14 @@ import { use_font_size_scaling } from "../frame-tree/hooks";
2828
import { EditorState } from "../frame-tree/types";
2929
import { useEffect, useRef, useState } from "react";
3030
import { webapp_client } from "@cocalc/frontend/webapp-client";
31-
import { Spin } from "antd";
31+
import { Switch, Spin, Tooltip } from "antd";
32+
import {
33+
set_local_storage,
34+
get_local_storage,
35+
delete_local_storage,
36+
} from "@cocalc/frontend/misc/local-storage";
37+
import { appBasePath } from "@cocalc/frontend/customize/app-base-path";
38+
import { join } from "path";
3239

3340
interface Props {
3441
id: string;
@@ -47,6 +54,20 @@ interface Props {
4754
value?: string;
4855
}
4956

57+
function getKey({ project_id, path }) {
58+
return `${project_id}:${path}:trust`;
59+
}
60+
function isTrusted(props): boolean {
61+
return !!get_local_storage(getKey(props));
62+
}
63+
function setTrusted(props, trust: boolean) {
64+
if (trust) {
65+
set_local_storage(getKey(props), "true");
66+
} else {
67+
delete_local_storage(getKey(props));
68+
}
69+
}
70+
5071
function should_memoize(prev, next) {
5172
return !is_different(prev, next, [
5273
"reload",
@@ -83,6 +104,11 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
83104
// is only needed for rmd mode where an aux file loaded from server.
84105
const [init, setInit] = useState<boolean>(mode == "rmd");
85106
const [srcDoc, setSrcDoc] = useState<string | null>(null);
107+
const [trust, setTrust0] = useState<boolean>(isTrusted(props));
108+
const setTrust = (trust) => {
109+
setTrusted(props, trust);
110+
setTrust0(trust);
111+
};
86112

87113
useEffect(() => {
88114
if (mode != "rmd") {
@@ -115,9 +141,31 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
115141
// done -- we tried
116142
setInit(false);
117143
}
118-
setSrcDoc(buf.toString("utf8"));
144+
let src = buf.toString("utf8");
145+
if (trust) {
146+
// extra security and pointed in the right direction.
147+
// We can't just use <iframe src= since the backend 'raw files' server
148+
// just always forces a download of that, for security reasons.
149+
const extraHead = `<base href="${join(appBasePath, project_id)}/files/">
150+
<meta http-equiv="Content-Security-Policy" content="default-src * data: blob: 'unsafe-inline' 'unsafe-eval';
151+
script-src * data: blob: 'unsafe-inline' 'unsafe-eval';
152+
img-src * data: blob:;
153+
style-src * data: blob: 'unsafe-inline';
154+
font-src * data: blob: about:;
155+
connect-src * data: blob:;
156+
frame-src * data: blob:;
157+
object-src * data: blob:;
158+
media-src * data: blob:;">`;
159+
const newSrc = src.replace(/<head>/i, `<head>${extraHead}`);
160+
if (src != newSrc) {
161+
src = newSrc;
162+
} else {
163+
src = `<head>\n${extraHead}\n</head>\n\n${src}`;
164+
}
165+
}
166+
setSrcDoc(src);
119167
})();
120-
}, [reload, mode, path, derived_file_types]);
168+
}, [reload, mode, path, derived_file_types, trust]);
121169

122170
const rootEl = useRef(null);
123171
const iframe = useRef(null);
@@ -211,15 +259,18 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
211259
if (mode == "rmd" && srcDoc == null) {
212260
return render_no_html();
213261
}
262+
214263
return (
215264
<iframe
216265
ref={iframe}
217266
srcDoc={mode != "rmd" ? value : (srcDoc ?? "")}
218-
sandbox="allow-forms allow-scripts allow-presentation"
219267
width={"100%"}
220268
height={"100%"}
221269
style={{ border: 0, ...style }}
222270
onLoad={iframe_loaded}
271+
sandbox={
272+
!trust ? "allow-forms allow-scripts allow-presentation" : undefined
273+
}
223274
/>
224275
);
225276
}
@@ -266,6 +317,21 @@ export const IFrameHTML: React.FC<Props> = React.memo((props: Props) => {
266317
// the cocalc-editor-div is needed for a safari hack only
267318
return (
268319
<div style={STYLE} className={"cocalc-editor-div smc-vfill"} ref={rootEl}>
320+
<div>
321+
<Tooltip
322+
title={
323+
"Arbitrary HTML is potentially dangerous. If you trust this content, switch this to trusted."
324+
}
325+
>
326+
<Switch
327+
style={{ float: "right", marginTop: "5px", marginRight: "5px" }}
328+
checked={trust}
329+
onChange={(checked) => setTrust(checked)}
330+
unCheckedChildren={"Untrusted"}
331+
checkedChildren={"Trusted"}
332+
/>
333+
</Tooltip>
334+
</div>
269335
{render_iframe()}
270336
</div>
271337
);

src/packages/util/smc-version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/* autogenerated by the update_version script */
2-
exports.version=1746892796;
2+
exports.version=1746984059;

0 commit comments

Comments
 (0)