diff --git a/apps/docs/docs/widgets/iframe/index.mdx b/apps/docs/docs/widgets/iframe/index.mdx
index 5761d0296..5f410a7f3 100644
--- a/apps/docs/docs/widgets/iframe/index.mdx
+++ b/apps/docs/docs/widgets/iframe/index.mdx
@@ -66,8 +66,6 @@ add_header Content-Security-Policy "frame-ancestors 'self' https://trusted-domai
- **Wikipedia homepage:** https://en.wikipedia.org/wiki/Main_Page
- **Any YouTube video or stream:** https://www.youtube.com/embed/dQw4w9WgXcQ?si=MWUbbG66UNTla_Ni
-- **Same-origin page:** `/path/to/page` resolves from the current Homarr origin. Root-relative URLs are useful when Homarr and
- the embedded page share a reverse-proxy host.
### Configuration
diff --git a/packages/widgets/src/iframe/component.spec.ts b/packages/widgets/src/iframe/component.spec.ts
index a5f3b5835..8c7dd134d 100644
--- a/packages/widgets/src/iframe/component.spec.ts
+++ b/packages/widgets/src/iframe/component.spec.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest";
-import { getAllowedPermissions, getFrameTitle, getSandboxFlags, isSupportedProtocol } from "./component";
+import { getAllowedPermissions, getFrameTitle, getSandboxFlags } from "./component";
const permissions = {
allowFullScreen: true,
@@ -18,12 +18,6 @@ describe("iframe policy", () => {
expect(getSandboxFlags(permissions)).toContain("allow-modals");
});
- it("accepts only HTTP protocols", () => {
- expect(isSupportedProtocol("https://example.com")).toBe(true);
- expect(isSupportedProtocol("javascript:alert(1)")).toBe(false);
- expect(isSupportedProtocol("not a url")).toBe(false);
- });
-
it("creates a display title without credentials, paths, or query parameters", () => {
expect(getFrameTitle("https://user:secret@example.com/private?token=sensitive")).toBe("example.com");
});
diff --git a/packages/widgets/src/iframe/component.tsx b/packages/widgets/src/iframe/component.tsx
index eef89b172..3c311b88e 100644
--- a/packages/widgets/src/iframe/component.tsx
+++ b/packages/widgets/src/iframe/component.tsx
@@ -1,26 +1,21 @@
"use client";
import { Box, Stack, Text, Title } from "@mantine/core";
-import { IconBrowserOff, IconProtocol } from "@tabler/icons-react";
+import { IconBrowserOff } from "@tabler/icons-react";
import { objectEntries } from "@homarr/common";
import { useI18n } from "@homarr/translation/client";
import type { WidgetComponentProps } from "../definition";
import classes from "./component.module.css";
-import { getSafeIframeUrl } from "./url";
export default function IFrameWidget({ options, isEditMode }: WidgetComponentProps<"iframe">) {
const t = useI18n("widget.iframe");
const { embedUrl, allowScrolling, ...permissions } = options;
- const safeEmbedUrl = getSafeIframeUrl(embedUrl);
const allowedPermissions = getAllowedPermissions(permissions);
const sandboxFlags = getSandboxFlags(permissions);
if (embedUrl.trim() === "") return ;
- if (!safeEmbedUrl) {
- return ;
- }
return (
@@ -29,8 +24,8 @@ export default function IFrameWidget({ options, isEditMode }: WidgetComponentPro
loading="lazy"
style={isEditMode ? { userSelect: "none", pointerEvents: "none" } : undefined}
className={classes.iframe}
- src={safeEmbedUrl}
- title={getFrameTitle(safeEmbedUrl)}
+ src={embedUrl}
+ title={getFrameTitle(embedUrl)}
allow={allowedPermissions}
scrolling={allowScrolling ? "yes" : "no"}
sandbox={sandboxFlags.join(" ")}
@@ -42,12 +37,6 @@ export default function IFrameWidget({ options, isEditMode }: WidgetComponentPro
);
}
-const supportedProtocols = ["http", "https"];
-
-export const isSupportedProtocol = (url: string) => {
- return getSafeIframeUrl(url) !== undefined;
-};
-
const NoUrl = () => {
const t = useI18n("widget.iframe");
@@ -59,21 +48,6 @@ const NoUrl = () => {
);
};
-const UnsupportedProtocol = () => {
- const t = useI18n("widget.iframe");
-
- return (
-
-
-
- {t("error.unsupportedProtocol", {
- supportedProtocols: supportedProtocols.map((protocol) => protocol).join(", "),
- })}
-
-
- );
-};
-
export const getAllowedPermissions = (
permissions: Omit["options"], "embedUrl" | "allowScrolling">,
) => {
diff --git a/packages/widgets/src/iframe/index.ts b/packages/widgets/src/iframe/index.ts
index 512f3278b..810a0b03b 100644
--- a/packages/widgets/src/iframe/index.ts
+++ b/packages/widgets/src/iframe/index.ts
@@ -2,7 +2,6 @@ import { IconBrowser, IconExternalLink } from "@tabler/icons-react";
import { createWidgetDefinition } from "../definition";
import { optionsBuilder } from "../options";
-import { getSafeIframeUrl } from "./url";
const createOptions = () =>
optionsBuilder.from((factory) => ({
@@ -23,17 +22,17 @@ export const { definition, componentLoader } = createWidgetDefinition("iframe",
supportsAdvancedFocus: false,
icon: IconBrowser,
contextActions: ({ options }) => {
- const embedUrl = getSafeIframeUrl(options.embedUrl);
- return embedUrl
- ? [
- {
- key: "open-iframe",
- label: "widget.common.openInNewTab.label",
- icon: IconExternalLink,
- onClick: () => window.open(embedUrl, "_blank", "noopener,noreferrer"),
- },
- ]
- : [];
+ const embedUrl = options.embedUrl;
+ if (typeof embedUrl !== "string" || embedUrl.trim() === "") return [];
+
+ return [
+ {
+ key: "open-iframe",
+ label: "widget.common.openInNewTab.label",
+ icon: IconExternalLink,
+ onClick: () => window.open(embedUrl, "_blank", "noopener,noreferrer"),
+ },
+ ];
},
createOptions,
}).withDynamicImport(() => import("./component"));
diff --git a/packages/widgets/src/iframe/url.ts b/packages/widgets/src/iframe/url.ts
deleted file mode 100644
index a76e5da42..000000000
--- a/packages/widgets/src/iframe/url.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { getSafeApplicationUrl } from "../common/application-url";
-
-const relativeUrlBase = "https://homarr.invalid";
-
-export const getSafeIframeUrl = (value: unknown): string | undefined => {
- const absoluteUrl = getSafeApplicationUrl(value);
- if (absoluteUrl) return absoluteUrl;
- if (typeof value !== "string") return undefined;
-
- const path = value.trim();
- if (!path.startsWith("/")) return undefined;
-
- const resolvedUrl = getSafeApplicationUrl(path, { baseUrl: relativeUrlBase });
- if (!resolvedUrl) return undefined;
-
- const parsedUrl = new URL(resolvedUrl);
- if (parsedUrl.origin !== relativeUrlBase) return undefined;
- return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
-};