From 00fad397923b9ea8456729445c6b13b21d0665a0 Mon Sep 17 00:00:00 2001 From: ajnart Date: Wed, 5 Aug 2026 14:25:53 +0200 Subject: [PATCH] feat(apps): allow custom URI schemes in application links Remove URL validation from app href field to support custom URI schemes like vscode://, microsoft-edge:https://, and myapp://open. Only javascript: URIs remain blocked for security. Closes #6520 --- apps/docs/docs/management/apps/index.mdx | 10 ++++++---- packages/api/src/router/app.ts | 2 +- packages/validation/src/app.ts | 3 +-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/docs/docs/management/apps/index.mdx b/apps/docs/docs/management/apps/index.mdx index 35c02a4fc..701438d96 100644 --- a/apps/docs/docs/management/apps/index.mdx +++ b/apps/docs/docs/management/apps/index.mdx @@ -7,8 +7,8 @@ tags: - Ping --- -Apps are the building blocks of your Homarr board. -Most of the time they are a simple link to a website, but used on your board they also support a simple ping to check if your service is up. +Apps are the building blocks of your Homarr board. +Most of the time they are a simple link to a website, but used on your board they also support a simple ping to check if your service is up. Apps are used in the bookmark and app widget. ## Create an app @@ -27,7 +27,7 @@ The name must be at least one character long and can be chosen freely. ### Icon URL -This is the URL to the icon that will be displayed on the app widget. +This is the URL to the icon that will be displayed on the app widget. You can paste a direct HTTP(S) URL to an image, search the included icon repositories by name, or upload a local file through the upload button. Direct image URLs are used as entered and are not searched in the icon repositories. ![Icon URL](./img/app-icon-url.png) @@ -40,9 +40,11 @@ An optional description that can be shown as tooltip on the app widget. The URL to the website you want to link to. Clicking on the app widget or bookmark will open this URL. +You can also use custom URI schemes such as `vscode://file/path`, `microsoft-edge:https://example.com`, or `myapp://open` to link to local applications or third-party programs. We trust that you know what you are doing — only `javascript:` URIs are blocked for security reasons. + ### Ping URL -When you want to use another url for the simple status check you can set it here. +When you want to use another url for the simple status check you can set it here. This will allow you to set a different URL for the ping than the URL that is opened when clicking on the app widget. It's useful when you want to ping the service internally for example with a custom ip but open the website externally. diff --git a/packages/api/src/router/app.ts b/packages/api/src/router/app.ts index 3d0285689..3c623b55a 100644 --- a/packages/api/src/router/app.ts +++ b/packages/api/src/router/app.ts @@ -173,7 +173,7 @@ export const appRouter = createTRPCRouter({ mcp: { enabled: true, description: - "Create a new app (bookmark/shortcut to a service). REQUIRED: name (string), iconUrl (icon URL string), href (app URL, http/https or blank). OPTIONAL: description (string or null), pingUrl (URL to check reachability, or empty string)", + "Create a new app (bookmark/shortcut to a service). REQUIRED: name (string), iconUrl (icon URL string), href (app URL or custom URI scheme such as vscode://, or blank). OPTIONAL: description (string or null), pingUrl (URL to check reachability, or empty string)", }, }) .mutation(async ({ ctx, input }) => { diff --git a/packages/validation/src/app.ts b/packages/validation/src/app.ts index 21a4335be..dfc7652a7 100644 --- a/packages/validation/src/app.ts +++ b/packages/validation/src/app.ts @@ -3,8 +3,7 @@ import { z } from "zod/v4"; export const appHrefSchema = z .string() .trim() - .url() - .regex(/^(?!javascript)[a-zA-Z]*:\/\//i) // javascript: is not allowed, i for case insensitive (so Javascript: is also not allowed) + .refine((val) => !/^javascript:/i.test(val), "JavaScript URIs are not permitted") .or(z.literal("")) .transform((value) => (value.length === 0 ? null : value)) .nullable();