The Origin Allow-List of api.onepassapps.com: Two Allowed Origins, One Fixed Preflight and Per-Route Rate Limits
I work on OnePassApps, so this is our own product, and everything below is a measurement of our own build, taken today from outside the browser with no session and no cookies. On 4 November 2026 between about 22:17 and
I work on OnePassApps, so this is our own product, and everything below is a measurement of our own build, taken today from outside the browser with no session and no cookies.
On 4 November 2026 between about 22:17 and 22:27 UTC I read the cross-origin contract of the API host behind app.onepassapps.com: which origins it answers, what its preflight says, and how its rate limits differ from route to route. Earlier posts here covered this host's root, health and 404 envelope, the feature flags, the auth config and the response headers of the app host. None of them read the origin rules, so that is what this post is about.
Two origins get an answer
Every route on api.onepassapps.com answers a cross-origin GET with access-control-allow-origin set to the request origin - but only for two origins:
https://app.onepassapps.comhttps://api.onepassapps.com
I sent thirteen origins in turn and counted the ones that came back with the header. Those two did. The other eleven came back with no access-control-allow-origin header at all: https://onepassapps.com, https://www.onepassapps.com, https://staging.onepassapps.com, https://clerk.onepassapps.com, https://example.com, https://evil.example, null, https://app.onepassapps.com:443, https://APP.ONEPASSAPPS.COM, https://app.onepassapps.com.evil.example and https://evil.example/https://app.onepassapps.com.
The matching is exact. The same host spelled in upper case is not the same origin, and a port suffix is not the same origin either. The suffix trick - an attacker domain that starts with our hostname - gets nothing, because the answer is an absent header rather than a wildcard.
The preflight is a fixed answer
A browser that wants to send anything other than a simple GET asks first, with an OPTIONS request. The API host answers that preflight with 204 and the same headers every time, whatever was asked for:
-
access-control-allow-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS- the full list, whether the request asked for GET, POST, PATCH or DELETE. -
access-control-allow-headers: Content-Type,Authorization,X-Request-Id,Idempotency-Key,X-ToolPass-Feature-Subject,X-Support-Access-Token,X-ToolPass-Analytics-Session,X-ToolPass-Impersonation- also fixed. I asked for a header calledx-custom-oneand it did not appear in the answer, so a browser would refuse a request carrying it. -
access-control-max-age: 600- the preflight is cached for ten minutes. -
access-control-expose-headers: X-Request-Id,X-RateLimit-Limit,X-RateLimit-Remaining,X-Concurrency-Limit,X-Concurrency-Remaining,Retry-After,X-ToolPass-Credits-Changed,X-ToolPass-Impersonating- the eight headers a page is allowed to read off the response. -
vary: Origin, Access-Control-Request-Headers.
Two details worth keeping. The preflight is answered even on a path that does not exist - OPTIONS /api/nope-xyz answers 204 with the whole policy, so the policy is decided before the route is. And a preflight from an origin that is not allowed still answers 204 with the same method list and header list, but without access-control-allow-origin, so the browser blocks the real request.
No response on this host, preflight or otherwise, carried access-control-allow-credentials.
Vary changes by route
The Vary header is not uniform across the API. On /api, /api/health, /api/runtime and /api/auth/config it is Origin. On /api/plans and /api/config/feature-flags it is Accept-Encoding, Origin. On /api/voting it is Authorization, Cookie, Accept-Encoding, Origin - the only route whose answer depends on who you are.
The rate limits differ by route
The two limit headers are not one number for the host:
| Route | x-ratelimit-limit |
x-concurrency-limit |
|---|---|---|
/api |
120 | 64 |
/api/health |
60 | 8 |
/api/plans |
120 | 64 |
/api/runtime |
120 | 64 |
/api/config/feature-flags |
120 | 64 |
/api/voting |
60 | 64 |
/api/auth/config |
120 | 64 |
The counters are live rather than decoration: five requests in a row answered x-ratelimit-remaining 98, 97, 96, 95, 94, and every response carried its own x-request-id.
The app host is the opposite
For contrast, the HTML on app.onepassapps.com answers access-control-allow-origin: *, so any page on any origin can read our app shell with fetch. The API host is scoped to two origins; the pages are open to everyone. Public app-shell markup on one side, the data on the other.
A correction to an earlier post here
A post I published earlier today said the API host's responses carried no Access-Control-Allow-Origin header, with or without an Origin header sent, and no Vary: Origin. Re-measured today with an explicit Origin header, that is wrong: the host answers access-control-allow-origin for the two origins listed above, and every route carries Vary: Origin. The build is the same in both readings - /api/health reports commitSha b68e02fb091594f92992f32872cfde846e17c4bb and buildTimestamp 2026-11-01T20:23:14Z - so the difference is in what the earlier probe recorded, not in the deployed version. I have corrected that post in place.
Limits
One build, one snapshot (4 November 2026, about 22:17 to 22:27 UTC), one network, signed out - no session, no cookies, no JavaScript. Cloudflare's edge sits in front of the origin in every measurement here, so a different point of presence could answer differently. I tested thirteen origins; there may be others in the list that I did not try. The allow-header list is what the preflight answers with, not proof that every header on it is meaningful on every route. I did not test an authenticated request.
If you want to read the app itself: app.onepassapps.com and the guides.
I work on OnePassApps, the team behind app.onepassapps.com.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.