mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-26 17:12:27 +03:00
fix(authz): match exact public routes exactly, not as prefixes (#11417)
`isPublicApiRoute()` matched every entry of PUBLIC_API_ROUTE_PREFIXES with `startsWith()`, but 11 of the 15 entries name ONE route, not a subtree. As a prefix each also marked every adjacent path sharing its leading characters as PUBLIC, which skips the MANAGEMENT auth gate. That is reachable today: Next resolves `/api/usage/om-usage<anything>` to the dynamic route `/api/usage/[connectionId]`, and that handler carries no auth of its own — it relies entirely on being classified MANAGEMENT. An unauthenticated caller therefore reaches `fetchAndPersistProviderLimits()`, which is an existence oracle over connection ids (409/404/400/200) and, for a connection id actually starting with `om-usage`, discloses live quota JSON and can drive an OAuth token refresh (a write side effect) with no credentials. Split the allowlist by shape: - PUBLIC_API_ROUTE_PREFIXES keeps only genuine subtrees, every entry ending in "/" (asserted by a unit test, so the class cannot come back silently). - PUBLIC_API_ROUTES_EXACT holds the single routes, matched exactly in both spellings. - The three read-only "prefixes" were single routes too and move to PUBLIC_READONLY_CORS_API_ROUTES, matched exactly. classify.ts now asks `isPublicReadonlyCorsRoute()` instead of scanning the raw list, so the CORS origin relaxation pipeline.ts keys on cannot be inherited by a sibling either (`/api/monitoring/health-detail` was taking it). - `/api/health` deliberately stays in its own set so it keeps classifying as `public_prefix`; folding it into the read-only set would widen CORS on it. dashboardCsrf.ts had a second copy of the prefix scan; it now shares `isPublicApiRoute()` so the client CSRF exemption and the server classification cannot disagree. Side effect in the safe direction: the three LOCAL_ONLY oauth auto-import routes were CSRF-exempt on the client while the server already required the token — the client now attaches it. Reported by @ntdat812 (GHSA-74g9-q8f6-793h), with the shape of the fix and the two gotchas above called out in the report. Closes GHSA-74g9-q8f6-793h Co-authored-by: Xiangzhe <bakryun0718@proton.me> Co-authored-by: Nguyen Thanh Dat <ntdat812.dev@gmail.com>
This commit is contained in:
committed by
GitHub
parent
56d64e29a4
commit
bbc7bf4351
@@ -1,5 +1,5 @@
|
||||
import { DASHBOARD_CSRF_HEADER } from "@/shared/constants/dashboardCsrf";
|
||||
import { PUBLIC_API_ROUTE_PREFIXES } from "@/shared/constants/publicApiRoutes";
|
||||
import { isPublicApiRoute } from "@/shared/constants/publicApiRoutes";
|
||||
|
||||
interface CachedDashboardCsrfToken {
|
||||
token: string;
|
||||
@@ -113,11 +113,7 @@ function isClientApiPath(pathname: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isPublicApiPath(pathname: string): boolean {
|
||||
return PUBLIC_API_ROUTE_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
||||
}
|
||||
|
||||
function shouldAttachDashboardCsrf(url: URL): boolean {
|
||||
function shouldAttachDashboardCsrf(url: URL, method: string): boolean {
|
||||
if (
|
||||
TOP_LEVEL_MANAGEMENT_PATH_PREFIXES.some(
|
||||
(prefix) => url.pathname === prefix || url.pathname.startsWith(prefix + "/")
|
||||
@@ -129,7 +125,10 @@ function shouldAttachDashboardCsrf(url: URL): boolean {
|
||||
return (
|
||||
url.pathname.startsWith("/api/") &&
|
||||
url.pathname !== "/api/auth/csrf" &&
|
||||
!isPublicApiPath(url.pathname) &&
|
||||
// Share the server's PUBLIC classification instead of re-scanning the
|
||||
// prefix list here — a second copy is a second chance to disagree with the
|
||||
// authz pipeline (GHSA-74g9-q8f6-793h).
|
||||
!isPublicApiRoute(url.pathname, method) &&
|
||||
!isClientApiPath(url.pathname)
|
||||
);
|
||||
}
|
||||
@@ -150,7 +149,7 @@ function sameOriginDashboardMutation(input: RequestInfo | URL, init?: RequestIni
|
||||
return false;
|
||||
}
|
||||
|
||||
return url.origin === window.location.origin && shouldAttachDashboardCsrf(url);
|
||||
return url.origin === window.location.origin && shouldAttachDashboardCsrf(url, method);
|
||||
}
|
||||
|
||||
function mergedHeaders(input: RequestInfo | URL, init?: RequestInit): Headers {
|
||||
|
||||
Reference in New Issue
Block a user