01Slack will not patch this: one link opens a debugging port that runs code in the desktop appA query parameter the client trusts about itself

Slack will not patch this: one link opens a debugging port that runs code in the desktop app

A slack:// link with devEnv=dev1 on it makes the Slack desktop app relaunch with --remote-debugging-port=8315 open. Anything that reaches the port runs JavaScript inside your signed-in client, which is how I had the session in 1.9 seconds, and it is the same channel an Electron app gets escalated to code execution on the host through. Slack's security team says it isn't a security risk.

Slack's desktop app ships a developer shortcut that a web page can flip. Flip it and the app restarts with a Chromium debugging port listening, and whatever reaches that port can run code inside the client you are signed into.
The switch is a query parameter. Put devEnv=dev1 on a slack:// URL, get someone to open it, and the client appends that URL to its own , restarts, reads the value back out of its own arguments, decides it must be a developer build, and hands Chromium --remote-debugging-port=8315.
Nothing asks you anything. The window that comes back looks like the one that left.
I reported it last month. Slack's security team closed it as not a security risk, which is why the button below still works on a current production build.

Slack restarts with a debugging port, from one link.

This one is real and it points at your own machine. Click it and the desktop client closes, reopens, and starts answering on port 8315.

Test itslack://devEnv=dev1

A query parameter the client trusts about itself

Slack registers slack:// as a when it installs. Nothing unusual there. Every desktop app with deep links does the same thing: the operating system routes the URL to the app, and the app decides what it means.
The interesting part is the route it takes to get there. The client does not parse the URL in the process that received it and act on the result. It drops the URL into its own argument list and launches itself again, and that second process reads its arguments to work out what kind of build it is.
The round trip is the bug. A process is normally entitled to trust its arguments, since only whoever started it could have written them. Here the client wrote them itself, out of a string a web page chose.
It is a note the program leaves for itself. After the restart it reads the note back and believes it, because it is meant to be the only one who can write notes. The link is a stranger dictating what the note says.
devEnv is a developer switch. On an internal build it picks which Slack environment to point at and turns on the tooling an engineer needs, the Chromium debugging port included. None of that was stripped out of the build that ships to everyone else.
User interaction required after the click
None
The relaunch, the mode switch and the open port all happen on their own. No prompt, nothing to confirm, and no visible difference in the window that comes back.

The port runs code, and the session is the cheap part

"Debugging port" sounds like something you can watch and not much else. What is actually listening is the , the interface Puppeteer and Playwright drive, and a client that connects to it can evaluate JavaScript in the page, read every cookie the browser holds, and watch or rewrite the traffic.
Evaluating JavaScript in a signed-in Slack renderer is already code execution. It is my code, in a process Slack's API treats as you, and for most of what an attacker wants there is nothing further to reach, because the session material is sitting right there:
  • The d session cookie, which is how the browser proves it is you.
  • The , which the client keeps in memory and attaches to its API calls.
Hold both and you are the account against Slack's own API, from any machine, with nothing running locally at all. That is the cheap version of this and it takes about a second.
How much further it goes depends on the app rather than on the port. The same connection lets you create targets, point them where you like, and call whatever the client's exposes to page JavaScript, and on an Electron app that bridge is the usual route out of the renderer and onto the machine. Code execution on the host is a realistic outcome of an open debugging port, not a guaranteed one, and I stopped at the credentials because they already settle what the port is.
One thing does limit it: the port listens on loopback. The link opens the door, and something already running as you has to walk through it, which can be any process on the machine with no prompt and no privileges of its own.
The cookie and the token are what the app shows Slack instead of your password. Copy them off the machine and they keep working until they expire, whether or not your Slack is still open.

Debugger up in 1.9 seconds

My proof of concept waits for port 8315 to open, connects, reads the session material out of the renderer, and posts a message into my own DM, so the credentials have to work with no client involved.
waiting.
debugger up after 1.9s, target https://app.dev1.slack.com/client/T0BSND4N057
client returned to production after 3.0s
stole  d=<243 bytes> token=xoxc-...<114 bytes>
auth.test              ok=true team=testing user=robert
chat.postMessage       ok=true channel=D0BSND4U1LZ ts=1787672146.020599 error=-
posted as the user into their own DM, no Slack client involved
cleanup                slack relaunched clean, debugger closed
Three seconds from the click to a client back on production. The credentials do not go back with it.
Then the client relaunches clean, the port is gone, and the workspace looks exactly as it did before the link was opened. That last part is what decides how something like this gets used.

Not considered to be a security risk

I reported it. The reply from Slack's security team:
this is not considered to be a security risk
and, on the mechanism:
Furthermore, and most likely, a form of MITM attack would be needed here if the attacker wants
There is no man in the middle anywhere in this. No network position, no certificate, nothing planted on the machine beforehand. Someone opens a link, which is about the cheapest thing there is to arrange on the internet.
I wrote back several times with the trigger spelled out, and each answer came back as the same template. The last one closes with:
With this in mind we are sufficiently pleased with our current configuration and will not be making a change at this time.
Thanks, and good luck with your future bug hunting.
So it is still shipping.

Try it on your own machine

The link hands your own Slack the URL. Your client closes, reopens, and starts answering on port 8315. Nothing here connects to us, and nothing leaves your machine.

Four steps, no interaction after the click

slack://devEnv=dev1
  1. 1Your browserHands the URL to the registered slack:// handler.
  2. 2The running clientAppends it to its own argv and relaunches itself.
  3. 3The new processReads devEnv back out, decides it is a developer build.
  4. 4ChromiumStarts with --remote-debugging-port=8315 and answers CDP.
Test it on your own Slack
Then confirm the port is opencurl http://127.0.0.1:8315/json/version
If curl returns a JSON blob with a webSocketDebuggerUrl in it, that is the control channel, and anything running as you can reach it. Restart Slack normally to close it again.