potato face
CryingPotato

Integrating Bolt with Convex

#ai #javascript

Bolt.new is the hottest new AI kid around the block, and when my roommates and I had a new idea for an app (Cracked or Cooked - swipe on YC startups to get useless public opinion on the success of a startup), I reached for Bolt to start building the UI. It was.. surprisingly good? I've also been dying to use Convex to see how good the DX is (spoiler - I cannot imagine defining my schema outside of Typescript anymore). I told the Bolt interface to use Convex to setup a backend and my friendly neighborhood Claude wrote some pretty believable looking Convex code. When I tried to run npx convex dev though, our old pal CORS decided to immediately foil our AI overlords.

The more I thought about it, and the more experimentation I did, the more I was convinced that this was a match made in heaven. The UIs that Bolt created were really good for the tiny prototype apps I wanted to build, but manually downloading the code and fixing up all the bugs in the Convex pieces was not a tenable way to work quickly - I'd rather just use something like Aider. How could we get the best of both worlds?

Dismantling 10 Years of Browser Security

My first idea was a simple CORS proxy. For anyone unfamiliar with CORS, I'd recommend reading "Understanding the Web Security Model", an excellent deep dive into why and how we ended up with CORS. A CORS proxy receives a request, sends the exact request upstream, and returns it with all the CORS headers set to be maximally permissive. This is a terrible idea for all the reasons the blog post outlines (e.g. Stackblitz can now steal my Convex credentials), but I just wanted to make it work.

I couldn't use an off-the-shelf proxy hosted on Cloudflare - anyone who discovered the URL to that would be able to abuse it. In hindsight this wasn't a real problem, but this is a toy app that I'm allowed to over-engineer (maybe this is why I never finish anything). No, this proxy would be cool - you would use an authenticated endpoint to get back a magic URL that would be a valid CORS proxy for a certain amount of time.

> curl -v -X GET -H "Authorization: secretsecret" https://cors.domain.com
randomstring.cors.domain.com # valid for 60 minutes (refreshes when used)

You can now use the above URL as a proxy by passing it in the path:

randomstring.cors.domain.com/google.com # redirects to google.com

The authenticated CORS proxy is here if you're interested in checking it out and hosting it yourself (no security promises), but let's get back to using it for our original use case.

An aside: the Bolt.new terminal is not a real terminal, so you can't do:

> A=$(ls -l)
> echo $A // empty
> export A='a'
> echo $A // still empty
> A='a'
> echo $A
a

Instead we have to get the URL the old-fashioned way:

> CORS_URL=...
> CORS_PASSWORD=...
> curl -X GET -H "Authorization: $CORS_PASSWORD" $CORS_URL/url
> // Manually save the output to CORS_TMP_URL

Now you have to trick your browser into accepting the fake SSL certificate from your hosted proxy (again, a terrible idea, but probably fine if you host your own proxy). Open up proxy_url/google.com and accept the scary warnings in your browser of choice. Now let's try running the Convex command again (using some hidden flags and environment variables I found by snooping into the CLI):

CONVEX_PROVISION_HOST=https://$CORS_TMP_URL/provision.convex.dev npx convex dev --override-auth-url https://$CORS_TMP_URL/auth.convex.dev --url https://$CORS_TMP_URL/provision.convex.dev

It makes it far enough to trick Convex into going further! We can now paste an auth token and... it fails again. Now we see a request like:

proxy_url/api/deployment/...

Looking into how this URL is constructed in the CLI, we see a call to new URL('/api/deployment', 'base_url'). The problem is that our proxy information is encoded in the path parameters of the base_url, which is stripped out by the new URL command. So instead of having a proxy URL like https://proxy.com/auth.convex.dev, we need it encoded in the subdomain: https://auth.convex.dev.proxy.com.

I updated the proxy to do this too. One annoyance about the subdomain approach is that we have to go to each individual URL (e.g. auth.convex.dev.proxy.com) and accept the wildcard SSL certificate. After clicking through a bunch of warnings, let's try again:

CONVEX_PROVISION_HOST=https://provision.convex.dev.$CORS_TMP_URL npx convex dev --override-auth-url https://auth.convex.dev.$CORS_TMP_URL --url https://provision.convex.dev.$CORS_TMP_URL

It seemed to work! But now.. we see requests to appid.convex.cloud. This doesn't seem easily configurable in the Convex CLI, so I was forced to turn to a solution that wasn't fully web based. I set up mitmproxy locally with a CORS plugin. You can launch this by running:

sudo mitmdump -s cors_proxy.py -p 8080

How do you get your browser to actually use the proxy? I think you can use extensions like FoxyProxy, but I'm wary of extensions that have so many security implications (yes, I do have some sense of what's secure). Instead, I used the MacOS native proxy script feature (thanks ChatGPT). You set this up in settings and use a proxy script:

function FindProxyForURL(url, host) {
if (shExpMatch(host, "cool-ibex-420.convex.cloud")) {
return "PROXY localhost:8080"; // mitmproxy's default port
}
return "DIRECT";
}

There's a MacOS quirk (feature?) where you can't use a file URL, so make sure you point it to a remote URL. I just used the GitHub hosted URL for mine. For the 5th time, after two days of experimentation, let's try again:

CONVEX_PROVISION_HOST=https://provision.convex.dev.$CORS_TMP_URL npx convex dev --override-auth-url https://auth.convex.dev.$CORS_TMP_URL --url https://provision.convex.dev.$CORS_TMP_URL

And now it does work!

How Did We Get Here?

If you were paying attention to the original problem we were trying to solve, the use of a proxy makes our initial hosted proxy approach completely useless. We might as well use mitmproxy for all the convex URLs. I hated this solution though, and really felt that there is a better way.

The idea I came up with is a short lived file sync server with a similar interface to wormhole. The UX for this is:

This gives me a cool way to keep some things local while leveraging Bolt for my menial UI tasks that I am too lazy to work on.