How Do You Get Muse Out of Its Network Sandbox?

Nothing gets into Muse's sandbox, so I used a home proxy and a relay Muse dials out to, letting it watch LinkedIn and Indeed with my laptop off, plus the 429 variables I never fully separated.

更新日 15 min14 回表示記事の言語
著者John Hsieh

TL;DR

Nothing gets into Muse's sandbox from outside, and everything going out has to pass through the sandbox. So stop trying to get in. Let Muse dial out instead: to change its exit, chain it to a proxy you run on the public internet; to see its screen, have it dial out to a relay you run yourself.

I wanted Muse to watch LinkedIn for me. Ping me when a recruiter messages, look up the company and the recruiter, score the job against my own criteria. And keep doing it with my laptop off.

Muse is Meta's AI assistant, and it comes with its own cloud computer: a VM (a virtual computer rented in the cloud) that can run a browser, run programs and run schedules. That computer's network sits inside a sandbox, with rules about what gets in and what gets out. (I wrote about what Muse can do in What Is Meta Muse Actually Good For?.)

Sounded perfect. In practice, LinkedIn loaded for Muse only some of the time (probably because the sandbox's exit IP is shared by a lot of people, and LinkedIn is hard on IPs like that). When I finally got my login session into it, the very first page came back as HTTP 429 (Too Many Requests: the site thinks you're hammering it).

This is the full day it took me to get from there to something that works.

First, what "get out" means here. I didn't break the sandbox's rules. I figured out what it lets through, then found a way out inside those rules.

Also: LinkedIn and Indeed both have rules about automation tools, so read their terms and decide for yourself. My setup reads my inbox every 15 minutes and scans jobs once a day, only on my own account, and nothing gets sent until I've approved it.

What does the sandbox actually block?

The conclusion first, because not knowing this is exactly why I went the long way round.

What follows is what Muse and I worked out by testing, not anything from official docs.

There are two ways out:

  • The program is set to use a proxy. The connection goes to the sandbox proxy (a proxy is a middleman that forwards your connections, so websites see the proxy's IP). The program sends a CONNECT asking the proxy to open a line to some address, and from then on the sandbox passes the bytes through as they are, without unwrapping them (if what's inside is HTTPS, that part is encrypted). Not every port is allowed (a port is the number that tells apart different services on one machine; the web usually uses 443): 22 timed out, while the handful of ports we actually used, including 443 and one non-standard port, all worked. We never tested the full list. This is the path Muse's browser takes.
  • The program connects directly, no proxy. The sandbox intercepts the connection, unwraps it, and re-wraps it with a certificate it signed itself. A certificate is how a site proves "you really reached me". Once it's swapped, the program has to be told to trust the sandbox's CA (the authority that signed it), or it errors out.

Everything else is blocked:

  • Private IPs (10.x, 192.168.x: addresses that only mean something inside a local network and can't be found from outside) are blocked both ways, going out and coming in.
  • Connecting into the VM from outside: there's no route at all.

One more thing, not about the network but it kept biting me: the VM restarts every now and then. Every running process dies. Only the home directory survives.

What Muse's sandbox lets through · Open in Miro

In one sentence: only "Muse dials out, through the sandbox, to a public address" gets through.

Attempt 1: a VPN back home

I'd actually had Muse read LinkedIn straight from its browser before this. Back then it was willing to show me its browser so I could log in, and the login itself worked: it could read things. But a few runs in a row and it fell apart, either the session dropped or a verification check popped up. That was never going to hold up for checking messages every 15 minutes and jobs every day. So this time I wanted to use the LinkedIn MCP instead (MCP is the interface that lets an AI call outside tools; the LinkedIn MCP is the tool Muse uses to read my profile and inbox).

The catch: when the MCP logs in, it opens a browser window of its own, and Muse never once showed me that one. To log in to LinkedIn in that window, I had to find my own way to see its screen.

Why not just let Muse type the password? LinkedIn logins keep throwing CAPTCHAs and two-step checks that need a human in front of the screen. And I didn't want to hand my password over.

My home router already runs a VPN. The obvious move: connect Muse to it, and Muse is basically sitting in my living room. Right?

WireGuard was out from the start. It runs over UDP, and the sandbox proxy only forwards TCP (UDP and TCP are two ways of moving data; the web uses TCP). I added an OpenVPN server over TCP on the router, had it go out through the sandbox proxy, and this time it connected.

And then? Nothing useful.

  • Muse started noVNC in the VM (it turns a computer's screen into a web page you can view and click in a browser) and gave me an address inside the VPN. It wouldn't load. The router could ping Muse, but an actual connection to noVNC went in and nothing ever came back. The sandbox proxy can't see what runs inside the tunnel, so my guess is that the VM's own network-level rules did the blocking: ping packets allowed, incoming connections not. I never dug into the exact mechanism.
  • The exit IP hadn't changed either. A VPN changes routing, which decides which network interface a packet leaves through. But Muse's browser is configured to hand its connections to the sandbox proxy. That's a next hop the application picks itself, and the routing table has no say in it. So browsing still went out through the sandbox.

I also tried a Cloudflare cloudflared quick tunnel. It doesn't use HTTP proxy settings, and three different configurations all failed with the same error: tls: first record does not look like a TLS handshake. In other words, it was waiting for an encrypted handshake with Cloudflare and got an HTTP response instead.

With the screen out of reach for now, I settled for second best: export the LinkedIn cookie (the small file a site uses to remember you're logged in) from Chrome on my laptop and move it into Muse's browser.

First page: 429. 😱

Attempt 2: a proxy at home

The first suspect was obviously the IP. The sandbox's exit IP is shared, so LinkedIn being suspicious of it made sense. Fine, send Muse out through my home instead.

Changing routes with a VPN didn't work, but a proxy is different. It's a next hop the application chooses, and Muse's browser was already using the sandbox proxy. So chain one more proxy, mine, behind it.

I installed tinyproxy (a very small proxy program) on the router, on a public port, with three limits:

  1. It needs a username and password.
  2. A domain allowlist: LinkedIn and Indeed (linkedin.com, licdn.com and so on), plus all of Google, which I added later for Indeed's Google sign-in. Everything outside the list, and my home network, is blocked. That caps the damage: even if the password leaks, all it gets you is my home IP pointed at those sites.
  3. CONNECT is only allowed to destination port 443, so all it can open is HTTPS tunnels.

The chaining works like this. Muse runs a tiny local forwarder in the VM, and the browser's proxy points at it. The forwarder asks the sandbox proxy to CONNECT to my home proxy, then, through that tunnel, asks my home proxy to CONNECT to LinkedIn. The TLS between the browser and LinkedIn (the padlock in the address bar: encryption plus proof of the site's identity) sits innermost and is never unwrapped.

Muse's browser → local forwarder in the VM → sandbox proxy → proxy.example.com (my home, password) → www.linkedin.com

One caveat up front. My home proxy uses plain basic auth, and the password itself isn't encrypted. Along the whole stretch from the VM to my home, even the sandbox can read it. So it's a dedicated password I don't use anywhere else, and the allowlist is what actually limits the damage. Adding all of Google loosened that limit a bit, in exchange for Indeed's Google sign-in working. I'm fine with that trade.

The exit was finally my home IP. I also had Muse open a site that inspects connection fingerprints: the certificate came from a normal public CA, and the connection looked like real Chrome. The sandbox really wasn't unwrapping this route, which matches "proxy set, not unwrapped" from above.

LinkedIn still said 429. 😭

I was completely stuck.

Changing the IP alone didn't fix the 429

This was the part that ate most of the day. We checked everything we could:

  • Is the IP blocked outright? Through the same proxy and the same exit, a request to /feed/ with no cookie got a normal redirect to the login page, not a 429. All that proves is the IP isn't blocked wholesale. My own Chrome loaded the feed fine too, so the account wasn't restricted.
  • Request volume: my home proxy's log only shows which domain each CONNECT went to (it can't see inside HTTPS). Muse opened exactly two tunnels to www.linkedin.com and never even reached static.licdn.com (where images and scripts live). It was blocked on the very first HTML. Hardly a flood.
  • Browser identity: the cookie came from Chrome on Windows, and Muse was running Chromium on Linux. We made the version and platform it reported match what it really was, and confirmed the connection looked like real Chrome. All lined up. Still 429.

At that point the cookie itself looked most suspicious: it was created by a login on my laptop and suddenly showed up in a browser on a different machine and a different OS. I don't know how LinkedIn actually decides, but I stopped moving cookies and went back to the original plan: log in inside Muse's own browser.

Let Muse dial out

To log in inside Muse's browser, I have to see its screen. Muse won't hand me the MCP's login window, and Attempt 1 already showed there's no way in from outside.

So flip it. I set up a relay on a cloud server of my own (a server I control, sitting on the public internet, that patches Muse and me together), and had Muse dial out from inside to reach it:

  • Muse runs a browser, a virtual display and noVNC in the VM.
  • Muse connects out to my relay with chisel and reverse-forwards noVNC through it. chisel is a small tool that wraps a connection inside an ordinary web connection (WebSocket), and it accepts an HTTP proxy, so it goes out through the sandbox proxy. The relay has two entrances, 443 and one non-standard port, and both worked.
  • I open the relay's page on my phone (one password on the page, another on noVNC itself), see Muse's browser, and log in myself, CAPTCHAs and all.

Why not SSH reverse forwarding? Port 22 timed out through the sandbox proxy. SSH on 443 might have worked, but I never tried it: my server already runs a web server on 443, and since chisel speaks HTTP, it can sit right behind that web server without opening another port.

How it all got wired up · Open in Miro

GitHub doesn't need my home IP, so it skips my home exit and goes straight out through the sandbox.

The login went through the LinkedIn MCP's own --login, and this time the window it opened finally showed up on my phone through the relay. After that, reading my profile and my inbox just worked. No 429.

That whole afternoon of matching versions and checking fingerprints... none of it ended up mattering.

To be honest about it, though, this doesn't prove the 429 had nothing to do with the IP. The run that worked changed three things at once: the exit was my home, the login happened in Muse's browser, and it went through the MCP's own --login. I had tried "sandbox IP + logging in myself in Muse's browser" before: the login worked, it could read things, and at least at first there was no 429. But that was Muse reading straight from the browser, and on repeated runs the session dropped or a verification check popped up. What I never tried is "sandbox IP + the MCP's --login": Muse never gave me the MCP's login screen, and by the time I could see it through the relay, the exit was already my home. So the variables still never got separated. All I can say is: changing the IP alone didn't fix the 429, and logging in myself, in the environment that uses the session, with the tool that uses it, did.

So why keep the home exit? Because "home exit + --login inside Muse" is the only combination I've seen run reliably, and I'd already watched the sandbox-IP setup drop sessions and hit verification checks on repeated runs. I didn't feel like gambling on that just to drop one proxy.

Things that bit me

Along the way, and after it was working, there was still a pile of small traps:

  1. Logged in inside Muse's browser, and the MCP still said there was no session. Right after the relay came up, I logged in from a Chromium that Muse had started separately, and the MCP ignored it. Turns out the MCP doesn't check the browser on startup. It checks a record file (source-state.json) that --login normally writes after a successful login. The one we had was created by the cookie import tool back when Muse converted the moved cookie into the MCP's format, so when we cleaned up cookies later, it got deleted along with them. "No session" was technically correct. Running --login and logging in once more through the relay fixed it. (This one was about the record file, not a 429.)
  2. The VM restarts and takes everything with it. Halfway through --login, the VM restarted: browser, noVNC and tunnel all gone, and the relay page just stopped loading (502). The annoying part: the proxy password had only lived inside a running process, so after the restart Muse didn't know it anymore. Now the proxy and relay credentials sit in a file only Muse can read, and before each scheduled run it checks the virtual display, the proxy and Chrome, and restarts whatever is missing.
  3. My Indeed account signs in with Google, and Google hops to a country domain. First accounts.google.com was blocked by my home proxy's allowlist. Once that was allowed, accounts.google.com.sg got blocked. In the end I let the home proxy allow all of Google permanently (that's the trade mentioned above).
  4. Indeed sits behind Cloudflare's human check. Login worked, then loading the "Jobs for you" page asked for verification again. I clicked "Verify you are human" once through the relay. If a scheduled run hits the check again, it stops and tells me instead of retrying.
  5. Indeed's official MCP was a dead end. It uses OAuth (the "let this app use your account" approval flow). After I approve, Indeed redirects a one-time authorization code to localhost, which means "the machine the browser is running on", and Muse would then trade that code for the real pass (a token). But the browser I approve in is on my own device, not on the VM, so the code lands on my own device, Muse never gets it, and there's no token. In theory I could approve inside Muse's browser through the relay, or paste the full redirect URL back to Muse by hand. But the client it registers on the fly (DCR) only gets the jobs.search scope, and the official API has no "Jobs for you" recommendations anyway. All that work wouldn't get me what I wanted, so I dropped it.
  6. The GitHub connector asked for approval on every single file read. My job scoring rules live in a private repo, and I change them a lot. Muse had copied the rules into the schedules as fixed text, and the three schedules had copied different versions. Now it has an access key that can only read that one repo, and every run fetches the latest rules first. I push, and the next run uses them. No approval cards.
Muse's report, in Chinese, on why Indeed's official MCP is stuck: with a localhost redirect, the browser that approves isn't on the sandbox VM, so the authorization code never reaches Muse Point 5 above, in Muse's own words to me (in Chinese): with a localhost redirect, the browser I approve in isn't on the VM, so the code never gets back to Muse. "On my side" means Muse itself; the PKCE verifier is a one-time secret that has to match when the code is traded for a token.

What it does now

Jobs are scored against the criteria I set up in LazyCV, where 🟢 green is the top grade.

Every 15 minutes, Muse reads my LinkedIn inbox and connection invitations. If a message has a job in it, it looks up the company and the recruiter, scores the job, and attaches a suggested reply. If there isn't enough to go on, it asks me what's missing. Nothing gets sent until I say "send", and it never replies or accepts invitations on its own.

Once a day it also scans LinkedIn jobs and reads the jobs Indeed recommends from my profile, skips the ones it has seen, scores the rest, and only tells me about the green ones. Before every scoring run it pulls the latest criteria from GitHub. All of this keeps running with my laptop off.

A mental model for the sandbox network

Looking back, the whole day kept running into the same wall.

A CONNECT through the sandbox proxy to a public address, on a port that's allowed, gets through, and the contents are left alone. A direct connection with no proxy also gets through, but gets unwrapped and re-signed, so the program has to trust the sandbox's CA. Private IPs, ports like 22 that aren't allowed, and anything connecting into the VM from outside never arrive. I didn't find a way out for UDP either.

So every time I wanted to "reach Muse from outside", what ended up working was the reverse: put a service on the public internet and have Muse dial out to it. That's what the home proxy is, and that's what the relay is. The VM also restarts whenever it likes. Saving credentials to a file, and checking services before each scheduled run, are two things I only added after a restart burned me once.

If you want to hook your own services up to Muse, start by having it test for you: run an HTTPS service on the public internet, ask Muse to connect with and without the proxy, try a few ports, and within a few minutes you'll know which paths work and which get unwrapped.

コメント(0)

まだコメントはありません。最初のコメントを書いてみませんか?