Internals of how the Glitch debugger connects

Disclosure: this post doesn’t walk you through the steps for doing something in particular.

Buttons, environment variables, and proxies

Clicking the “Debugger” button in the editor adds the GLITCH_DEBUGGER variable to your .env file.
When the this environment variable is set, the (Node.js) project runs a wrapper /opt/debugger/source/debugger.js around the project’s normal invocation (as determined by the package.json). This script runs the project’s original invocation (i) with debugging enabled and (ii) with a different value for the PORT environment variable (1988 instead of 3000).

APP_PORT = 1988;
...
childEnv.PORT = APP_PORT;
...
child = child_process.spawn('bash', ['-c', process.argv[2]], {
  cwd: '/app',
  env: childEnv
});

It sets up a proxy listening on the usual port 3000 that forwards normal requests to the app’s port (1988) and forwards debugger requests to the node debug port (9200).

proxyToDebugger = new HttpProxy.createProxyServer({
  target: {
    host: 'localhost',
    port: DEBUGGER_PORT
  }
});

proxyToApp = new HttpProxy.createProxyServer({
  target: {
    host: 'localhost',
    port: APP_PORT
  }
});
...
proxyServer = http.createServer(function(req, res) {
  // This check MUST match the one in the proxy, or non-members may be
  // able to get to the debugger
  if (
    req.headers.origin === 'chrome-devtools://devtools' ||
    req.headers.origin === 'devtools://devtools'
  ) {
    return waitForPort('localhost', DEBUGGER_PORT).then(function() {
      updateRequestForDebugger(req);
      return proxyToDebugger.web(req, res);
    });
  } else {
    return waitForPort('localhost', APP_PORT).then(function() {
      return proxyToApp.web(req, res);
    });
  }
});
...
  proxyServer.listen(3000, () => { ... });

The “Debugger” button also opens a page that sets up a one-time debugging route in the Glitch API server associated with a token. The page receives this token and displays it as part of a URL.

What happens now

Thus for normal requests to your app,

  • A normal request to yourdomain.glitch.me goes to the Glitch proxy;
  • The Glitch proxy forwards normal requests to port 3000 in your project container (point “P” for later reference);
  • Your project container is running something (let’s call it the debugger proxy) that listens on port 3000;
  • The debugger proxy forwards normal requests to port 1988 (point “D” for later reference);
  • The debugger wrapper runs your app such that the PORT environment variable is set to 1988;
  • Your app listens on the port specified in its PORT environment variable (point “E” for later reference); and
  • Your app receives the request on port 1988.

Normal requests work just like always.

Whereas for debugger requests (we think, due to some of this not being open source),

  • A debugger request to api.glitch.com/project/debugger/one-time-debugging-token goes to the Glitch API server;
  • The Gltich API server authenticates the request based on the token in the request path;
  • The Glitch API server forwards the debugger request to port 3000 in your project container;
  • Your project container is running the debugger proxy on port 3000;
  • The debugger proxy forwards debugger requests to port 9200 (this is also point “D”);
  • The Node.js inspector server receives the request on port 9200.

Debugger requests from someone who knows the debugging token make their way to the Node.js inspector server.

If you don’t heed process.env.PORT

The above works if you follow the recommendation that your app listen on whatever port the PORT environment variable says. If your app instead unconditionally listens on port 3000, it will get to point “E” and find that the port is already in use (by the debugger proxy). Your app will probably exit anbormally before you can connect from your devtools.

Security

Points “P” and “D” are critical to the project’s security. Two components both distinguish normal requests and debugger requests and take different actions.

  • The Glitch proxy forwards normal requests to the project and rejects debugger requests.
  • The debugger proxy forwards normal requests to the app and forwards debugger requests to the inspector.

As the code comment in the debugger wrapper notes, both components must be in agreement about which requests are normal requests and which requests are debugger requests. If there were a request that point “P” treated as a normal request and point “D” treated as a debugger request, then an attacker could send that request to yourdomain.glitch.me and have the debugger proxy forward it to the Node.js inspector server, bypassing the authentication that’s meant to take place in the Glitch API server.

Side story: an unauthenticated access vulnerability

When I was reading up on how the debugger wrapper works, I saw the note about the logic there having to agree with the Glitch proxy, but I didn’t know what the Glitch proxy was intended to do. I put one of my projects into debug mode and tried sending a debugger request to its subdomain, without authentication. The request went through to the Node.js inspector server. It seemed that either the criteria between points “P” and “D” had diverged, or the Glitch proxy did not subsequently reject the debugger request. I reported this issue to Glitch, and they have since fixed it by making the Glitch proxy reject debugger requests.

Timeline

2020/01/19 I discover this issue. I notify Glitch support by email.

2020/01/20 I realize that I did not get an automated ticket creation response. I resend the report on HappyFox. A Glitch representative confirms that they’ve received the report.

2020/01/26 Glitch deploys the fix. The fix does not need to change project containers, so it takes effect immediatly. I verify that the fix works.

4 Likes