Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 14 additions & 20 deletions src/tools/sdk-utils/bstack/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,20 @@ const JAVA_FRAMEWORK_MAP: Record<string, string> = {
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 = "<your BrowserStack access key>";

// 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)
Expand All @@ -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 (
Expand All @@ -56,38 +56,33 @@ 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}"`
);
}

// 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
Expand All @@ -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 "";
Expand Down
4 changes: 2 additions & 2 deletions src/tools/sdk-utils/bstack/configUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -32,7 +32,7 @@ export function generateBrowserStackYMLInstructions(
# ======================

userName: ${username}
accessKey: ${accessKey}
accessKey: "<your BrowserStack access key>"

# TODO: Replace these sample values with your actual project details
projectName: ${projectName}
Expand Down
1 change: 0 additions & 1 deletion src/tools/sdk-utils/bstack/sdkHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ export async function runBstackSDKOnly(
input.detectedLanguage as SDKSupportedLanguage,
input.detectedTestingFramework as SDKSupportedTestingFramework,
username,
accessKey,
);

if (sdkSetupCommand) {
Expand Down
1 change: 0 additions & 1 deletion src/tools/sdk-utils/percy-bstack/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ export function runPercyWithBrowserstackSDK(
input.detectedLanguage as SDKSupportedLanguage,
input.detectedTestingFramework as SDKSupportedTestingFramework,
username,
accessKey,
);

if (sdkSetupCommand) {
Expand Down
24 changes: 13 additions & 11 deletions tests/tools/sdk-utils-commands.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = "<your BrowserStack access key>";

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");
});
Expand Down