From d33ed2014971e14a68873854d32b6f31bfa9af72 Mon Sep 17 00:00:00 2001 From: ajnart <49837342+ajnart@users.noreply.github.com> Date: Fri, 10 Jul 2026 17:53:00 +0200 Subject: [PATCH] fix(releases): bind token to saved provider:identifier match The token must only be used for repositories that are registered in the widget's saved options, matched by provider:identifier (not by fragile cuid2 id). This prevents tokens from being sent to arbitrary providerUrl values submitted by the caller. The saved providerUrl is used in preference to the request's providerUrl when a token is being applied. --- packages/api/src/router/widgets/releases.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/api/src/router/widgets/releases.ts b/packages/api/src/router/widgets/releases.ts index 6e105aaf9..fa22a2210 100644 --- a/packages/api/src/router/widgets/releases.ts +++ b/packages/api/src/router/widgets/releases.ts @@ -1,3 +1,4 @@ +import SuperJSON from "superjson"; import { escapeForRegEx } from "@tiptap/react"; import { z } from "zod/v4"; @@ -44,6 +45,7 @@ export const releasesRouter = createTRPCRouter({ ) .query(async ({ ctx, input }) => { const tokensByProvider = new Map(); + const savedRepos = new Map(); if (input.itemId) { try { @@ -51,6 +53,16 @@ export const releasesRouter = createTRPCRouter({ if (item && item.kind === "releases") { await throwIfActionForbiddenAsync(ctx, eq(boards.id, item.boardId), "view"); + const options = SuperJSON.parse>(item.options); + const repos = options.repositories as Array<{ provider?: string; identifier: string; providerUrl?: string }> | undefined; + if (repos) { + for (const repo of repos) { + if (repo.provider && repo.identifier) { + savedRepos.set(`${repo.provider}:${repo.identifier}`, { providerUrl: repo.providerUrl }); + } + } + } + const secrets = await ctx.db.query.widgetSecrets.findMany({ where: eq(widgetSecrets.itemId, input.itemId), }); @@ -70,6 +82,7 @@ export const releasesRouter = createTRPCRouter({ return await Promise.all( input.repositories.map(async (repository) => { const repositoryId = repository.id ?? repository.identifier; + const savedRepo = savedRepos.get(`${repository.provider}:${repository.identifier}`); try { const response = await releasesRequestHandler .handler({ @@ -77,8 +90,8 @@ export const releasesRouter = createTRPCRouter({ provider: repository.provider, identifier: repository.identifier, versionRegex: formatVersionFilterRegex(repository.versionFilter), - providerUrl: repository.providerUrl, - token: tokensByProvider.get(repository.provider), + providerUrl: savedRepo?.providerUrl ?? repository.providerUrl, + token: savedRepo ? tokensByProvider.get(repository.provider) : undefined, }) .getDataAsync();