modelcontextprotocol / modelcontextprotocol/servers

puppeteer: update old MCP SDK and add browser-automation guardrails by default

Open
#4,118 10 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
90.5k
Forks
11.7k
Avg merge
2d 2h
Merged PRs (30d)
5

Description

Summary

While auditing and hardening local MCP server usage, I reviewed @modelcontextprotocol/server-puppeteer@2025.5.12 and built a local hardened wrapper for our environment. I am opening this issue to share findings and possible upstream improvements.

Browser automation exposed as an MCP tool is high impact: it can navigate to arbitrary targets, execute page JavaScript, capture screenshots, submit forms, and interact with local/private network targets if not constrained. The current server appears designed as a reference/demo server; the suggestions below are intended to make it safer for default agent use.

Package/runtime reviewed

  • Package: @modelcontextprotocol/server-puppeteer@2025.5.12
  • npm latest observed: 2025.5.12
  • License observed: MIT
  • Author observed: Anthropic, PBC
  • Issue tracker declared by package: https://github.com/modelcontextprotocol/servers/issues
  • Maintainers observed from npm metadata: jspahrsummers, thedsp, ashwin-ant
  • Local dependency resolution:
@modelcontextprotocol/server-puppeteer@2025.5.12
+-- @modelcontextprotocol/sdk@1.0.1
`-- puppeteer@23.11.1

Audit finding: old MCP SDK bundled with published package

Using a temporary lockfile for @modelcontextprotocol/server-puppeteer@2025.5.12, npm audit --omit=dev --audit-level=moderate reports:

@modelcontextprotocol/sdk  <=1.25.1
Severity: high
Anthropic's MCP TypeScript SDK has a ReDoS vulnerability - https://github.com/advisories/GHSA-8r9q-7v3j-jr4g
Model Context Protocol (MCP) TypeScript SDK does not enable DNS rebinding protection by default - https://github.com/advisories/GHSA-w48q-cv73-mx4w
No fix available
node_modules/@modelcontextprotocol/sdk
  @modelcontextprotocol/server-puppeteer  *
  Depends on vulnerable versions of @modelcontextprotocol/sdk

2 high severity vulnerabilities

The package currently depends on @modelcontextprotocol/sdk@1.0.1, so consumers inherit these advisories.

Behavioral findings

From local inspection of dist/index.js and README behavior:

  1. Arbitrary URL schemes are accepted by page.goto(args.url).
    There is no explicit http/https restriction, embedded-credential rejection, host allowlist/denylist, or private-network blocking.

  2. Redirect/subresource requests are not guarded.
    A public page can load or redirect to local/private targets unless request interception or equivalent policy is applied.

  3. Caller-supplied launchOptions are powerful.
    The server has a dangerous-args list, but callers can still influence browser launch behavior. Safer defaults should be stricter for autonomous agent environments.

  4. NPX default opens a visible non-headless browser.
    This may be surprising for unattended MCP sessions.

  5. Screenshots and console logs are stored in memory without obvious caps.
    A long-running server can accumulate unbounded screenshots/log entries.

  6. puppeteer_fill echoes filled values.
    If an agent fills a password/token field, echoing the value back increases the risk of credential retention in chat/session logs.

  7. puppeteer_evaluate lacks explicit script/result caps and redaction.
    Evaluation is useful, but bounded input, bounded output, timeout, and redaction make it safer.

Local hardening behavior that worked well

For our local wrapper, we preserved the upstream 7 tool names exactly:

puppeteer_navigate
puppeteer_screenshot
puppeteer_click
puppeteer_fill
puppeteer_select
puppeteer_hover
puppeteer_evaluate

We added:

  • @modelcontextprotocol/sdk@1.29.0 locally instead of the package's bundled SDK 1.0.1
  • default headless=true
  • isolated profile under a user data directory, with refusal to use node_modules
  • top-level http/https only by default
  • optional explicit LCV_PUPPETEER_ALLOW_FILE_URLS=1 override
  • embedded-credential rejection
  • DNS/private/reserved/loopback/link-local address blocking
  • request interception so subresources also pass through the URL/IP guard
  • optional allowlist/denylist host controls
  • restricted launch options by default, with an explicit unsafe escape hatch
  • screenshot dimensions and screenshot count caps
  • selector/value/script/result caps
  • console log ring buffer
  • secret redaction
  • no echo of filled values

Validation from local wrapper

7-tool parity:

514:server.registerTool("puppeteer_navigate", {
536:server.registerTool("puppeteer_screenshot", {
572:server.registerTool("puppeteer_click", {
584:server.registerTool("puppeteer_fill", {
601:server.registerTool("puppeteer_select", {
614:server.registerTool("puppeteer_hover", {
626:server.registerTool("puppeteer_evaluate", {

Smoke output:

puppeteer tools: puppeteer_click,puppeteer_evaluate,puppeteer_fill,puppeteer_hover,puppeteer_navigate,puppeteer_screenshot,puppeteer_select
puppeteer file block: Blocked unsupported URL scheme: file:
puppeteer loopback block: Blocked private or reserved IP address: 127.0.0.1
puppeteer example.com final=https://example.com/ title=Example Domain
puppeteer evaluate: Execution result:
puppeteer screenshot: Screenshot 'smoke' taken at 640x480
lcv-hardened-puppeteer 1.0.0 running on stdio; headless=true; block_private=true

Local dependency audit after using newer SDK and overriding ip-address:

$ npm ls ip-address express-rate-limit @modelcontextprotocol/sdk
lcv-workspace@ C:\Users\leona\lcv-workspace
`-- @modelcontextprotocol/sdk@1.29.0
  `-- express-rate-limit@8.4.1
    `-- ip-address@10.2.0 overridden

$ npm audit --audit-level=moderate
found 0 vulnerabilities

Suggested upstream changes

  • Update @modelcontextprotocol/sdk dependency to a non-vulnerable current release.
  • Add default URL policy: http/https only, reject embedded credentials, optional allowlist/denylist.
  • Add DNS/private/reserved address blocking, including redirects and subresource requests.
  • Add request interception to apply policy to all browser network requests.
  • Default to headless mode for stdio agent usage, or document the non-headless default more prominently.
  • Restrict launchOptions to a safe subset by default. Keep an explicit ALLOW_DANGEROUS/unsafe override for users who need it.
  • Add caps for screenshots, console logs, selectors, fill values, evaluate scripts, and evaluate result size.
  • Do not echo filled values back to the model; return selector and character count instead.
  • Redact common secret patterns in console/evaluate output.
  • Add smoke tests for: scheme block, loopback/private IP block, tool list parity, screenshot cap, and launchOption rejection.

Compatibility note

These changes can preserve the current 7 tool names and schemas. The main compatibility risk is users who intentionally depend on local/private-network navigation or custom launch args. Those use cases can be preserved behind explicit environment flags.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading dist/index.js and the README behavior for the seven Puppeteer tools, then review the dependency resolution showing the MCP SDK version. Compare the proposed URL, launch-option, output, and credential safeguards with the local wrapper findings. Done should include an agreed scope and smoke tests for scheme blocking, private-IP blocking, tool parity, screenshot limits, and launch-option rejection.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, typescript
Domain
backend-api-design, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.