Skip to content

Make the tool proxy safe to point every visitor at - #45

Merged
devbadya merged 3 commits into
mainfrom
cursor/proxy-for-visitors-a60c
Aug 30, 2026
Merged

devbadya merged 3 commits into
mainfrom
cursor/proxy-for-visitors-a60c

Conversation

@devbadya

@devbadya devbadya commented Aug 30, 2026 •

Copy link
Copy Markdown
Owner

The proxy exists but every visitor would have to paste its URL into the Tools panel to get anything from it. This makes it something the build can carry — which is only defensible once a proxy outage stops being an outage for the app.

The fallback comes first

searchWeb and readPage try the proxy and fall back to calling the provider from the page when it fails. An outage, a spent Railway budget or an allowlist that has not caught up now costs a slower search rather than the answer. Browser-direct is the path this build already took, so the worst case is the old behaviour.

The build can carry an origin

deploy.yml passes the repository variable AGENT_API_BASE to the build as VITE_AGENT_API_BASE. Set it and every visitor's DuckDuckGo search and page reads go through that proxy with no configuration. Leave it unset and the site stays browser-direct, which is what a fork needs.

An empty value is now explicitly not a proxy. A workflow forwarding a variable nobody set hands the build '', and the old code read that as same-origin /api — pointing every hosted visitor at an endpoint a static host does not serve.

A public proxy needs a ceiling

The origin allowlist says who may call, not how often, and an allowed page is exactly what a scraper would forge. pnpm proxy allows 30 requests a minute per caller and answers 429 beyond that. The caller is the left-most x-forwarded-for address, since Railway and every other edge terminate the connection themselves and the socket address would put every visitor in one bucket. PROXY_RATE_LIMIT changes it, 0 switches it off, health checks are exempt.

Tests

pnpm check passes: 1195 tests, up from 1187. New coverage for both fallbacks, the empty-value case, and the sliding window including per-caller buckets and eviction of callers that fell silent.

The fallback, proven against the real deployed proxy. The dev server's origin (http://127.0.0.1:5173) is not on that proxy's allowlist, so it is genuinely refused. Both /api/search and /api/fetch fail with CORS/403 in the Network panel, and the search still returns three real results while read_page still returns Example Domain:

Console: the proxy is refused and FALLBACK_OK still reports three results and the page title

Network: 403 to the Railway proxy, then 200 through the reader

proxy_refuses_search_still_works.mp4

The rate limit, live against pnpm proxy with PROXY_RATE_LIMIT=3:

health 1..5 -> HTTP 200        (exempt)
foreign origin -> {"error":"Origin not allowed"}
search 1 -> 200, 2 -> 200, 3 -> 200
search 4 -> 429 {"error":"Too many requests"}
search 5 -> 429
another address -> its own quota

To show artifacts inline, enable in settings.

Open in Web Open in Cursor 

cursoragent and others added 2 commits August 30, 2026 17:16
The Hobby plan does not create a URL by itself. This image runs only
pnpm proxy, listens on 0.0.0.0, and answers GET /api/health so a
generated Railway domain can be pasted into Tools → Tool proxy URL.

Co-authored-by: Sebastian <devbadya@users.noreply.github.com>
A hosted build can now carry a proxy origin, so search and page reads
work without anyone opening the Tools panel. Three things had to be
true first.

Both tools fall back to calling the provider from the page when the
proxy fails, so an outage, a spent budget or an allowlist that has not
caught up costs a slower search rather than the answer.

An empty VITE_AGENT_API_BASE is no longer read as same-origin. A
workflow forwarding a repository variable nobody set hands the build an
empty string, which would have aimed every visitor at an /api the static
host does not serve.

The standalone proxy allows 30 requests a minute per caller and answers
429 beyond that, counted from the forwarded address. The origin
allowlist says who may call, not how often, and an allowed page is what
a scraper would forge.

Co-authored-by: Sebastian <devbadya@users.noreply.github.com>
@devbadya
devbadya marked this pull request as ready for review August 30, 2026 19:22
Keep the Railway host paragraph from #44 and the AGENT_API_BASE
plus fallback copy from this branch.

Co-authored-by: Sebastian <devbadya@users.noreply.github.com>
@devbadya
devbadya merged commit 26a1107 into main Aug 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants