Skip to content

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

@lcv-leo

Description

@lcv-leo

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions