Skip to content

fix(percy): replace runPercyScan token interpolation with placeholder (PMAA-103)#288

Open
manoj-k04 wants to merge 1 commit intobrowserstack:mainfrom
manoj-k04:PMAA-103-fix-percy-token-leak
Open

fix(percy): replace runPercyScan token interpolation with placeholder (PMAA-103)#288
manoj-k04 wants to merge 1 commit intobrowserstack:mainfrom
manoj-k04:PMAA-103-fix-percy-token-leak

Conversation

@manoj-k04
Copy link
Copy Markdown
Collaborator

@manoj-k04 manoj-k04 commented Apr 30, 2026

Summary

  • Removes the plaintext Percy-token leak in runPercyScan tool output (HackerOne #3576387, CVSS 6.5) by replacing token interpolation with placeholder-only instructions.
  • Re-enables the runPercyScan registration that PMAA-100 (fix(percy): disable runPercyScan #285) had to comment out as a temporary mitigation.
  • Adds regression tests so the leak cannot silently come back.

Refs: HackerOne #3576387, PMAA-100 / #285, PMAA-103.

Background

The token fetched from api.browserstack.com/api/app_percy/get_project_token was being interpolated verbatim into MCP tool output as export PERCY_TOKEN="<actual_token>". Because MCP responses are consumed by AI assistants (Claude Desktop, Cursor, etc.), any path that could trigger the tool — prompt injection, malicious MCP client, compromised assistant session — would exfiltrate a valid Percy credential across a trust boundary.

PR #285 mitigated the issue by disabling the runPercyScan registration entirely. This PR delivers the proper fix so the tool can be turned back on.

Changes

Output sanitization (the actual fix)

  • src/tools/run-percy-scan.tsgeneratePercyTokenInstructions returns placeholder text only (<your Percy project token>) and points users at the Percy dashboard. The token is still fetched so fetchPercyToken's project-validation side effect is preserved, but is intentionally not echoed.
  • src/tools/sdk-utils/percy-web/handler.ts — same treatment in runPercyWeb. Parameter kept for upstream compatibility, with a SECURITY: comment.
  • src/tools/sdk-utils/percy-automate/handler.ts — same treatment in runPercyAutomateOnly; replaces the Here is percy token if required {${percyToken}} line.

Re-enable the tool (revert PMAA-100 disable)

  • src/tools/percy-sdk.ts — restore runPercyScan import, RunPercyScanParamsShape import, and the tool registration block.
  • tests/tools/percySdk.test.ts — restore matching import, mock, name assertion, and runPercyScan - SUCCESS test.

Regression coverage

  • tests/tools/runPercyScan.test.ts — assertions inverted: output must not contain the fetched token, and must contain the placeholder.
  • tests/tools/percyTokenLeak.test.ts (new) — pins the no-leak contract on runPercyWeb and runPercyAutomateOnly directly.

Test plan

  • npx tsc — clean.
  • npx vitest run129/129 pass (4 percySdk tests restored from PMAA-100 disable; 2 new tests in percyTokenLeak.test.ts).
  • End-to-end PoC over the real MCP transport (with a fake Percy token injected at the BrowserStack API boundary):
    • Before fix (main + re-enable, no sanitization): outputContainsSecret: true — the fake token appeared 3× in PERCY_TOKEN lines (export, setx, set).
    • After fix (this branch): outputContainsSecret: false — all PERCY_TOKEN lines show the <your Percy project token> placeholder.
  • Reviewer to confirm the Percy-Web setup flow (expandPercyVisualTesting) still produces usable instructions despite the token being absent — users now retrieve their token from the Percy dashboard, which matches the public Percy onboarding flow.

🤖 Generated with Claude Code

The Percy token fetched from api.browserstack.com was being interpolated
verbatim into runPercyScan tool output (and the percy-web /
percy-automate SDK handlers), exposing a server-side credential across a
trust boundary into MCP client / AI-assistant context (HackerOne
#3576387, CVSS 6.5).

PMAA-100 (PR browserstack#285) mitigated this by disabling the runPercyScan tool
registration. This change removes the underlying leak so the tool can be
safely re-enabled.

Output sanitization
- run-percy-scan.ts: generatePercyTokenInstructions returns
  placeholder-only text and points users to the Percy dashboard. The
  token is still fetched (preserves the existing project-validation
  side effect of fetchPercyToken) but is intentionally not echoed.
- percy-web/handler.ts and percy-automate/handler.ts: same treatment in
  runPercyWeb and runPercyAutomateOnly. Parameter signatures are kept
  for upstream compatibility; SECURITY comments document why the token
  must not be interpolated.

Tool re-enabled
- percy-sdk.ts: restore the runPercyScan import, schema import, and
  tool registration block that PMAA-100 commented out.
- percySdk.test.ts: restore the matching import, mock, name assertion,
  and "runPercyScan - SUCCESS" test.

Regression coverage
- runPercyScan.test.ts: assertions inverted — output must NOT contain
  the fetched token; the placeholder must be present.
- percyTokenLeak.test.ts (new): pin the no-leak contract on
  runPercyWeb and runPercyAutomateOnly directly so any future regression
  fails CI.

Refs: HackerOne #3576387, PMAA-100, PMAA-103

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@manoj-k04 manoj-k04 force-pushed the PMAA-103-fix-percy-token-leak branch from 330e92c to 72f0a9f Compare April 30, 2026 13:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant