From 55e30991b192a9f67f539d4d678540f328ee07e2 Mon Sep 17 00:00:00 2001 From: Savio Dias Date: Thu, 28 May 2026 19:49:45 +0530 Subject: [PATCH] fix: replace literal BrowserStack access key with placeholder in SDK / yml tool output --- src/tools/sdk-utils/bstack/commands.ts | 34 +++++++++------------ src/tools/sdk-utils/bstack/configUtils.ts | 4 +-- src/tools/sdk-utils/bstack/sdkHandler.ts | 1 - src/tools/sdk-utils/percy-bstack/handler.ts | 1 - tests/tools/sdk-utils-commands.test.ts | 24 ++++++++------- 5 files changed, 29 insertions(+), 35 deletions(-) diff --git a/src/tools/sdk-utils/bstack/commands.ts b/src/tools/sdk-utils/bstack/commands.ts index d2b0815f..c197d938 100644 --- a/src/tools/sdk-utils/bstack/commands.ts +++ b/src/tools/sdk-utils/bstack/commands.ts @@ -13,19 +13,20 @@ const JAVA_FRAMEWORK_MAP: Record = { cucumber: "cucumber-testng", } as const; +// The literal access key is never echoed into tool output; users substitute +// this placeholder locally. See PMAA-100 security review. +const ACCESS_KEY_PLACEHOLDER = ""; + // Template for Node.js SDK setup instructions -const NODEJS_SDK_INSTRUCTIONS = ( - username: string, - accessKey: string, -): string => `---STEP--- +const NODEJS_SDK_INSTRUCTIONS = (username: string): string => `---STEP--- Install BrowserStack Node SDK using command: \`\`\`bash npm i -D browserstack-node-sdk@latest \`\`\` ---STEP--- -Run the following command to setup browserstack sdk: +Run the following command to setup browserstack sdk (replace the placeholder with your BrowserStack access key): \`\`\`bash -npx setup --username ${username} --key ${accessKey} +npx setup --username ${username} --key "${ACCESS_KEY_PLACEHOLDER}" \`\`\``; // Template for Gradle setup instructions (platform-independent) @@ -44,7 +45,6 @@ const GRADLE_SETUP_INSTRUCTIONS = ` // Generates Maven archetype command for Windows platform function getMavenCommandForWindows( username: string, - accessKey: string, mavenFramework: string, ): string { return ( @@ -56,7 +56,7 @@ function getMavenCommandForWindows( `-DartifactId="${MAVEN_ARCHETYPE_ARTIFACT_ID}" ` + `-Dversion="${MAVEN_ARCHETYPE_VERSION}" ` + `-DBROWSERSTACK_USERNAME="${username}" ` + - `-DBROWSERSTACK_ACCESS_KEY="${accessKey}" ` + + `-DBROWSERSTACK_ACCESS_KEY="${ACCESS_KEY_PLACEHOLDER}" ` + `-DBROWSERSTACK_FRAMEWORK="${mavenFramework}"` ); } @@ -64,30 +64,25 @@ function getMavenCommandForWindows( // Generates Maven archetype command for Unix-like platforms (macOS/Linux) function getMavenCommandForUnix( username: string, - accessKey: string, mavenFramework: string, ): string { return `mvn archetype:generate -B -DarchetypeGroupId=${MAVEN_ARCHETYPE_GROUP_ID} \\ -DarchetypeArtifactId=${MAVEN_ARCHETYPE_ARTIFACT_ID} -DarchetypeVersion=${MAVEN_ARCHETYPE_VERSION} \\ -DgroupId=${MAVEN_ARCHETYPE_GROUP_ID} -DartifactId=${MAVEN_ARCHETYPE_ARTIFACT_ID} -Dversion=${MAVEN_ARCHETYPE_VERSION} \\ -DBROWSERSTACK_USERNAME="${username}" \\ --DBROWSERSTACK_ACCESS_KEY="${accessKey}" \\ +-DBROWSERSTACK_ACCESS_KEY="${ACCESS_KEY_PLACEHOLDER}" \\ -DBROWSERSTACK_FRAMEWORK="${mavenFramework}"`; } // Generates Java SDK setup instructions with Maven/Gradle options -function getJavaSDKInstructions( - framework: string, - username: string, - accessKey: string, -): string { +function getJavaSDKInstructions(framework: string, username: string): string { const mavenFramework = getJavaFrameworkForMaven(framework); const isWindows = process.platform === "win32"; const platformLabel = isWindows ? "Windows" : "macOS/Linux"; const mavenCommand = isWindows - ? getMavenCommandForWindows(username, accessKey, mavenFramework) - : getMavenCommandForUnix(username, accessKey, mavenFramework); + ? getMavenCommandForWindows(username, mavenFramework) + : getMavenCommandForUnix(username, mavenFramework); return `---STEP--- Install BrowserStack Java SDK @@ -105,14 +100,13 @@ export function getSDKPrefixCommand( language: SDKSupportedLanguage, framework: string, username: string, - accessKey: string, ): string { switch (language) { case "nodejs": - return NODEJS_SDK_INSTRUCTIONS(username, accessKey); + return NODEJS_SDK_INSTRUCTIONS(username); case "java": - return getJavaSDKInstructions(framework, username, accessKey); + return getJavaSDKInstructions(framework, username); default: return ""; diff --git a/src/tools/sdk-utils/bstack/configUtils.ts b/src/tools/sdk-utils/bstack/configUtils.ts index a38f13ae..3f78ae0c 100644 --- a/src/tools/sdk-utils/bstack/configUtils.ts +++ b/src/tools/sdk-utils/bstack/configUtils.ts @@ -16,7 +16,7 @@ export function generateBrowserStackYMLInstructions( // Get credentials from config const authString = getBrowserStackAuth(browserStackConfig); - const [username, accessKey] = authString.split(":"); + const [username] = authString.split(":"); // Generate platform configurations using the utility function const platformConfigs = generatePlatformConfigs(config); @@ -32,7 +32,7 @@ export function generateBrowserStackYMLInstructions( # ====================== userName: ${username} -accessKey: ${accessKey} +accessKey: "" # TODO: Replace these sample values with your actual project details projectName: ${projectName} diff --git a/src/tools/sdk-utils/bstack/sdkHandler.ts b/src/tools/sdk-utils/bstack/sdkHandler.ts index 52d48b26..ebecad92 100644 --- a/src/tools/sdk-utils/bstack/sdkHandler.ts +++ b/src/tools/sdk-utils/bstack/sdkHandler.ts @@ -92,7 +92,6 @@ export async function runBstackSDKOnly( input.detectedLanguage as SDKSupportedLanguage, input.detectedTestingFramework as SDKSupportedTestingFramework, username, - accessKey, ); if (sdkSetupCommand) { diff --git a/src/tools/sdk-utils/percy-bstack/handler.ts b/src/tools/sdk-utils/percy-bstack/handler.ts index a3ae6cd9..98d05f9b 100644 --- a/src/tools/sdk-utils/percy-bstack/handler.ts +++ b/src/tools/sdk-utils/percy-bstack/handler.ts @@ -95,7 +95,6 @@ export function runPercyWithBrowserstackSDK( input.detectedLanguage as SDKSupportedLanguage, input.detectedTestingFramework as SDKSupportedTestingFramework, username, - accessKey, ); if (sdkSetupCommand) { diff --git a/tests/tools/sdk-utils-commands.test.ts b/tests/tools/sdk-utils-commands.test.ts index 1cb95729..1969284e 100644 --- a/tests/tools/sdk-utils-commands.test.ts +++ b/tests/tools/sdk-utils-commands.test.ts @@ -12,37 +12,39 @@ describe("getSDKPrefixCommand", () => { beforeEach(() => { // Guard: ensure these env vars never leak into the rendered command. - // The fix forwards `username` / `accessKey` parameters; reading process.env - // would silently return undefined or a stale value in remote (multi-tenant) mode. + // The fix forwards `username` from the parameter; the access key is no + // longer accepted as a parameter and is never echoed. delete process.env.BROWSERSTACK_USERNAME; delete process.env.BROWSERSTACK_ACCESS_KEY; }); - it("nodejs: embeds passed username/accessKey, never reads process.env", () => { - const out = getSDKPrefixCommand("nodejs", "testng", "u-from-config", "k-from-config"); + const PLACEHOLDER = ""; + + it("nodejs: embeds passed username and emits placeholder for accessKey", () => { + const out = getSDKPrefixCommand("nodejs", "testng", "u-from-config"); expect(out).toContain("--username u-from-config"); - expect(out).toContain("--key k-from-config"); + expect(out).toContain(PLACEHOLDER); expect(out).not.toContain("undefined"); expect(out).not.toContain("process.env"); }); - it("java/unix: Maven command uses passed username/accessKey", () => { + it("java/unix: Maven command uses passed username and emits placeholder for accessKey", () => { Object.defineProperty(process, "platform", { value: "darwin" }); - const out = getSDKPrefixCommand("java", "testng", "u-from-config", "k-from-config"); + const out = getSDKPrefixCommand("java", "testng", "u-from-config"); expect(out).toContain('-DBROWSERSTACK_USERNAME="u-from-config"'); - expect(out).toContain('-DBROWSERSTACK_ACCESS_KEY="k-from-config"'); + expect(out).toContain(`-DBROWSERSTACK_ACCESS_KEY="${PLACEHOLDER}"`); expect(out).not.toContain("undefined"); expect(out).not.toContain("process.env"); }); - it("java/windows: Maven command uses passed username/accessKey (regression)", () => { + it("java/windows: Maven command uses passed username and emits placeholder for accessKey (regression)", () => { // Regression for a bug where the Windows branch read process.env.BROWSERSTACK_* // while the Unix branch correctly took params. In remote mode this leaked the // string "undefined" into the Maven command shown to the user. Object.defineProperty(process, "platform", { value: "win32" }); - const out = getSDKPrefixCommand("java", "testng", "u-from-config", "k-from-config"); + const out = getSDKPrefixCommand("java", "testng", "u-from-config"); expect(out).toContain('-DBROWSERSTACK_USERNAME="u-from-config"'); - expect(out).toContain('-DBROWSERSTACK_ACCESS_KEY="k-from-config"'); + expect(out).toContain(`-DBROWSERSTACK_ACCESS_KEY="${PLACEHOLDER}"`); expect(out).not.toContain("undefined"); expect(out).not.toContain("process.env"); });