mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-20 22:22:57 +03:00
feat(cursor): adds local-only manual refresh route
Adds POST /api/providers/[id]/refresh-cursor, a dedicated loopback-only route that calls the renewal orchestrator on demand for a single Cursor connection, bounded by a 30s per-connection cooldown. Classifies the new route in LOCAL_ONLY_API_PATTERNS and closes the manage-scope-bypass gap for dynamic-segment spawn-capable routes under /api/providers/ via a new SPAWN_CAPABLE_PATTERNS / SPAWN_CAPABLE_PATTERN_ANCESTORS mechanism, which also retroactively covers the pre-existing /login route. The existing shared /api/providers/[id]/refresh route is untouched and stays remote-reachable for every other provider.
This commit is contained in:
committed by
diegosouzapw
parent
71f1804e99
commit
df3b691809
@@ -36,3 +36,31 @@ export const SPAWN_CAPABLE_PREFIXES: ReadonlyArray<string> = [
|
||||
"/api/headroom/stop", // kills tracked PID — must never be bypassable (Hard Rules #15 + #17)
|
||||
"/api/vnc-session", // #7892: spawns Docker containers via child_process.spawn (src/lib/vncSession/service.ts) — must never be whitelistable via manage-scope bypass (Hard Rules #15 + #17)
|
||||
];
|
||||
|
||||
/**
|
||||
* Regex-matched companion to `SPAWN_CAPABLE_PREFIXES`, for spawn-capable
|
||||
* routes whose spawn-capable segment sits AFTER a dynamic path parameter
|
||||
* (e.g. `/api/providers/{id}/refresh-cursor`) — a flat prefix would either
|
||||
* miss them entirely or require over-broadening the shared `/api/providers/`
|
||||
* prefix (used for legitimate remote provider CRUD). Mirrors the
|
||||
* `LOCAL_ONLY_API_PREFIXES`/`LOCAL_ONLY_API_PATTERNS` split already
|
||||
* established in `routeGuard.ts` for this exact shape. Checked against a
|
||||
* CONCRETE resolved request path — an exact regex match, no approximation.
|
||||
*/
|
||||
export const SPAWN_CAPABLE_PATTERNS: ReadonlyArray<RegExp> = [
|
||||
/^\/api\/providers\/[^/]+\/login\/?$/, // pre-existing gap: in LOCAL_ONLY_API_PATTERNS today but never in a spawn-capable deny-list
|
||||
/^\/api\/providers\/[^/]+\/refresh-cursor\/?$/, // spawns cursor-agent via renewal.ts (Hard Rules #15 + #17)
|
||||
];
|
||||
|
||||
/**
|
||||
* Companion to `SPAWN_CAPABLE_PATTERNS`, used ONLY by the zod-level candidate
|
||||
* bypass-prefix check (`settingsSchemas.ts`), which validates a candidate
|
||||
* BYPASS PREFIX STRING (not a concrete path) at `PATCH /api/settings` time —
|
||||
* general prefix-vs-regex reachability is undecidable, so this conservatively
|
||||
* treats the shared literal ancestor of the dynamic/static-segment patterns
|
||||
* as off-limits. Intentionally coarser than `SPAWN_CAPABLE_PATTERNS`'s exact
|
||||
* per-route match, but costs nothing security-wise: the runtime check in
|
||||
* `isLocalOnlyBypassableByManageScope` (Layer 2) is the actual enforcement
|
||||
* boundary and stays exact. `SPAWN_CAPABLE_PREFIXES` itself is untouched.
|
||||
*/
|
||||
export const SPAWN_CAPABLE_PATTERN_ANCESTORS: ReadonlyArray<string> = ["/api/providers/"];
|
||||
|
||||
@@ -15,7 +15,10 @@ import { RESPONSES_PREVIOUS_RESPONSE_ID_MODES } from "@/shared/constants/respons
|
||||
// Import from the server-free constants leaf, NOT from `@/server/authz/routeGuard`:
|
||||
// this schema is reachable from client components (dashboard onboarding wizard), and
|
||||
// routeGuard drags in server runtime (→ ioredis) that breaks the client/CLI build.
|
||||
import { SPAWN_CAPABLE_PREFIXES } from "@/shared/constants/spawnCapablePrefixes";
|
||||
import {
|
||||
SPAWN_CAPABLE_PREFIXES,
|
||||
SPAWN_CAPABLE_PATTERN_ANCESTORS,
|
||||
} from "@/shared/constants/spawnCapablePrefixes";
|
||||
|
||||
const signatureCacheModeValues = ["enabled", "bypass", "bypass-strict"] as const;
|
||||
|
||||
@@ -135,7 +138,10 @@ export const updateSettingsSchema = z.object({
|
||||
showProviderTopologyOnHome: z.boolean().optional(),
|
||||
localOnlyManageScopeBypassEnabled: z.boolean().optional(),
|
||||
// Layer 1 of the spawn-capable guard (Hard Rules #15/#17): reject any bypass
|
||||
// prefix that reaches a SPAWN_CAPABLE_PREFIXES path at PATCH time, with the
|
||||
// prefix that reaches a SPAWN_CAPABLE_PREFIXES path, or a
|
||||
// SPAWN_CAPABLE_PATTERN_ANCESTORS ancestor (e.g. /api/providers/, the
|
||||
// shared ancestor of the dynamic-segment routes in SPAWN_CAPABLE_PATTERNS
|
||||
// such as /login and /refresh-cursor), at PATCH time, with the
|
||||
// BYPASS_PREFIX_NOT_ALLOWED code the settings route handler translates.
|
||||
// Layer 2 (isLocalOnlyBypassableByManageScope) still refuses spawn paths at
|
||||
// runtime even if a malformed DB row claims otherwise. This refine was in the
|
||||
@@ -149,7 +155,10 @@ export const updateSettingsSchema = z.object({
|
||||
.refine(
|
||||
(prefix) => {
|
||||
const normalized = prefix.endsWith("/") ? prefix : `${prefix}/`;
|
||||
return !SPAWN_CAPABLE_PREFIXES.some((sp) => normalized.startsWith(sp));
|
||||
return (
|
||||
!SPAWN_CAPABLE_PREFIXES.some((sp) => normalized.startsWith(sp)) &&
|
||||
!SPAWN_CAPABLE_PATTERN_ANCESTORS.some((sp) => normalized.startsWith(sp))
|
||||
);
|
||||
},
|
||||
{
|
||||
message:
|
||||
|
||||
Reference in New Issue
Block a user