From 98aa0368fb3f241a460d682e6698026133e1aa9b Mon Sep 17 00:00:00 2001 From: Eve <162624394+aviu16@users.noreply.github.com> Date: Mon, 16 Feb 2026 07:51:11 -0500 Subject: [PATCH] fix(common): correctly resolve secret environment variables in basic auth header (#5879) Co-authored-by: James George <25279263+jamesgeorge007@users.noreply.github.com> --- .../src/components/http/Codegen.vue | 7 ++-- .../auth/types/__tests__/basic.spec.ts | 20 +++++++++++ .../src/helpers/auth/types/basic.ts | 34 +++++++++++-------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/packages/hoppscotch-common/src/components/http/Codegen.vue b/packages/hoppscotch-common/src/components/http/Codegen.vue index f340aebd..42e31e14 100644 --- a/packages/hoppscotch-common/src/components/http/Codegen.vue +++ b/packages/hoppscotch-common/src/components/http/Codegen.vue @@ -131,7 +131,10 @@ import { getEffectiveRESTRequest, resolvesEnvsInBody, } from "~/helpers/utils/EffectiveURL" -import { AggregateEnvironment, getAggregateEnvs } from "~/newstore/environments" +import { + AggregateEnvironment, + getAggregateEnvsWithCurrentValue, +} from "~/newstore/environments" import { useService } from "dioc/vue" import cloneDeep from "lodash-es/cloneDeep" @@ -237,7 +240,7 @@ const getFinalURL = (input: string): string => { * Combines all environment variables into a single environment object */ const buildFinalEnvironment = (): Environment => { - const aggregateEnvs = getAggregateEnvs() + const aggregateEnvs = getAggregateEnvsWithCurrentValue() const inheritedVariables = currentActiveTabDocument.value.inheritedProperties?.variables || [] diff --git a/packages/hoppscotch-common/src/helpers/auth/types/__tests__/basic.spec.ts b/packages/hoppscotch-common/src/helpers/auth/types/__tests__/basic.spec.ts index b3e7ad1d..0b54786a 100644 --- a/packages/hoppscotch-common/src/helpers/auth/types/__tests__/basic.spec.ts +++ b/packages/hoppscotch-common/src/helpers/auth/types/__tests__/basic.spec.ts @@ -49,5 +49,25 @@ describe("Basic Auth", () => { expect(headers[0].value).toBe(`Basic ${btoa(":")}`) }) + + test("resolves secret environment variables before base64 encoding even when `showKeyIfSecret` is `true`", async () => { + const auth: HoppRESTAuth & { authType: "basic" } = { + authActive: true, + authType: "basic", + username: "<>", + password: "<>", + } + + // `showKeyIfSecret = true` should NOT affect base64 encoding + // Previously, this would encode "testuser:<>" instead of "testuser:testpass" + // See: https://github.com/hoppscotch/hoppscotch/issues/5863 + const headers = await generateBasicAuthHeaders( + auth, + mockEnvVars, + true // showKeyIfSecret + ) + + expect(headers[0].value).toBe(`Basic ${btoa("testuser:testpass")}`) + }) }) }) diff --git a/packages/hoppscotch-common/src/helpers/auth/types/basic.ts b/packages/hoppscotch-common/src/helpers/auth/types/basic.ts index dce9d007..f38a7892 100644 --- a/packages/hoppscotch-common/src/helpers/auth/types/basic.ts +++ b/packages/hoppscotch-common/src/helpers/auth/types/basic.ts @@ -5,29 +5,35 @@ import { HoppRESTHeader, } from "@hoppscotch/data" +/** + * UTF-8 safe base64 encoding. Standard btoa() throws on non-ASCII chars, + * so we encode through TextEncoder first. + */ +function utf8Btoa(str: string): string { + const bytes = new TextEncoder().encode(str) + let binary = "" + for (const byte of bytes) { + binary += String.fromCharCode(byte) + } + return btoa(binary) +} + export async function generateBasicAuthHeaders( auth: HoppRESTAuth & { authType: "basic" }, envVars: Environment["variables"], - showKeyIfSecret = false + // `showKeyIfSecret` is intentionally not forwarded to `parseTemplateString()` here. + // The base64 encoding must always use actual values, otherwise the + // Authorization header is unusable (see #5863). + _showKeyIfSecret = false ): Promise { - const username = parseTemplateString( - auth.username, - envVars, - false, - showKeyIfSecret - ) - const password = parseTemplateString( - auth.password, - envVars, - false, - showKeyIfSecret - ) + const username = parseTemplateString(auth.username, envVars, false, false) + const password = parseTemplateString(auth.password, envVars, false, false) return [ { active: true, key: "Authorization", - value: `Basic ${btoa(`${username}:${password}`)}`, + value: `Basic ${utf8Btoa(`${username}:${password}`)}`, description: "", }, ]