Chrome remote debugging expects host header

See original GitHub issue

This is a slightly weird one and I don’t think it is strictly speaking a puppeteer bug but thought I’d raise it here anyway. Since version 1.2, when trying to connect to chrome via the remote debugging interface a host header must be specified. This is not a problem in puppeteer per se, but we use python to get the ws address and so the python requests line now has to include headers={'Host':''} otherwise chrome returns an error: Host header is specified and is not an IP address or localhost. I assume that puppeteer is setting this header in the background somewhere or chrome doesn’t care anymore once the request comes in via the websocket. I also could not find anything documented about this change, but I’m assuming that is has something to do with the .dev domain changes to HTTPS.

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Reactions:8
  • Comments:18 (2 by maintainers)

github_iconTop GitHub Comments

7reactions
itaylorcommented, May 21, 2018

I’ve been working around this issue by doing a dns lookup in the code that sets up puppeteer (running inside Docker for Mac) to call back to my host machine.

const endPoint = await changeUrlHostToUseIp(process.env.CHROME_WS_ENDPOINT);
browser = await puppeteer.connect({
  browserWSEndpoint: endPoint,
  // You may need the following if you use this browser to connect to a machine with self-signed certs
  // ignoreHTTPSErrors: true,
});

const lookupAsync = require('util').promisify(require('dns').lookup);

// Fix for https://github.com/GoogleChrome/puppeteer/issues/2242
async function changeUrlHostToUseIp(urlString) {
  const urlParsed = url.parse(urlString);
  const { address: hostIp } = await lookupAsync(urlParsed.hostname);
  delete urlParsed.host;
  urlParsed.hostname = hostIp;
  return url.format(urlParsed);
}

For reference, my CHROME_WS_ENDPOINT env var looks like: ws://host.docker.internal:9223/devtools/page/FF3312E555CDEADBC3759BF0DE46B72B

6reactions
dvdotsenkocommented, Oct 20, 2019

Since this is on top of results when I google “Host header is specified and is not an IP address or localhost” + “puppeteer” let’s leave crumbs for others:

= WHY

A security bug was filed against Chromium Dev Tools: https://bugs.chromium.org/p/chromium/issues/detail?id=813540

Around v66 of Chromium this patch was introduced: https://chromium-review.googlesource.com/c/chromium/src/+/952522

That patch inspects all HTTP (not WS) requests hitting Dev Tools endpoint and before doing anything else, validates it against:

bool RequestIsSafeToServe(const net::HttpServerRequestInfo& info) {
  // For browser-originating requests, serve only those that are coming from
  // pages loaded off localhost or fixed IPs.
  ...
  return url.HostIsIPAddress() || net::IsLocalHostname(url.host(), nullptr);
}

where

bool IsLocalHostname(const std::string& host) {
  return host == "localhost" || 
    host == "localhost.localdomain" || 
    base::EndsWith(host, ".localhost", base::CompareCase::SENSITIVE);
}

This means that any non-IP-address-looking hostname that is NOT localhost, *.localhost or localhost.localdomain will result in error “Host header is specified and is not an IP address or localhost” error.

= WHAT TO DO ABOUT IT

== Most reliable way to fix it

DNS resolve the hostname and feed that IP address to puppeteer instead of hostname.

(what @itaylor mentioned above, but in simpler, promise-ified DNS)

const puppeteer = require('puppeteer-core')
const dns = require('dns').promises

const port = '9222'
const hostname_dirty = 'host.docker.internal'
// This pipes the hostname through DNS lookup and resolves it to an IP address
const { address: hostname } = await dns.lookup(hostname_dirty)

const browser = await puppeteer.connect({
    browserURL: `http://${hostname}:${port}`
})

== Accidental Fixes / Shortcuts

Standard address for “host machine” on Docker is host.docker.internal, but on Docker for Mac and similar there is another, older alias - docker.for.mac.localhost. host.docker.internal will NOT work, while docker.for.mac.localhost will natively work.

Obviously aliasing headless chrome in your /etc/hosts under some *.localhost hostname will also work.

Read more comments on GitHub >

github_iconTop Results From Across the Web

1232509 - Remove DevTools discovery page served by HTTP ...
Issue 1232509: Remove DevTools discovery page served by HTTP server on remote-debugging-port. Reported by schedule caseq@chromium.org ...
Read more >
"Host header is specified and is not an IP address or localhost ...
It originates from an existing change in the Chrome DevTools Protocol which generates this error when accessing it remotely.
Read more >
Host header is specified and is not an IP address or localhost
So starting from version 66 Chrome stops accepting HTTP connections to the debugging protocol server if the "host header is specified and is...
Read more >
Chapter 7. Debugging CORS requests - CORS in Action
This chapter introduces tools that can be used to debug CORS requests. It starts by introducing features of the Chrome Debugger Tool. Next...
Read more >
Using the Chrome Debugger Tools, part 2: The Network Tab
The request and response headers shown here are the actual HTTP protocol itself. The Remote Address, Request URL, Request Method and Status Code...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found