🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW
Tutte le route dell'API di gestione di OmniRoute sono classificate in uno dei tre
livelli di protezione. La classificazione è statica, definita in src/server/authz/routeGuard.ts,
e viene valutata prima dell'esecuzione di qualsiasi altro ramo di autenticazione.
Applicato da: isLocalOnlyPath(path) → verifica dell'host di loopback
Bypass: nessuno per impostazione predefinita. Eccezione limitata per i percorsi in
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES quando la richiesta include una chiave
API valida con ambito manage (vedere Eccezione per l'ambito manage).
Queste route generano processi figlio o eseguono codice a runtime. Esporle al traffico non di loopback consentirebbe a un autore di attacchi in possesso di un JWT valido (ad es., tramite un tunnel Cloudflared/Ngrok) di attivare la generazione di processi, una classe di CVE nota (GHSA-fhh6-4qxv-rpqj).
Che cos'è GHSA-fhh6-4qxv-rpqj (la classe di attacco): un server di gestione/agente
espone un endpoint che avvia un sottoprocesso (npm install, node, un browser,
un proxy, git, tar, …). Se tale endpoint è raggiungibile dall'esterno dell'host, perché
l'operatore ha collocato OmniRoute dietro un tunnel nginx/Cloudflare/Tailscale e un JWT
è trapelato, oppure l'autenticazione è stata configurata in modo errato, l'autore dell'attacco trasforma «chiamare
un'API» in «eseguire un comando sull'host» (esecuzione di codice in remoto). OmniRoute impedisce
questo comportamento applicando incondizionatamente una verifica dell'host di loopback, prima di qualsiasi verifica dell'autenticazione,
a ogni route in grado di generare processi: un token trapelato attraverso un tunnel non può comunque
raggiungere la funzionalità di generazione dei processi.
L'insieme LOCAL_ONLY completo. La fonte autorevole è costituita da
LOCAL_ONLY_API_PREFIXES / LOCAL_ONLY_API_PATTERNS in
src/server/authz/routeGuard.ts; la tabella seguente rispecchia lo stato attuale. Il
controllo check-route-guard-membership enumera ogni route.ts nelle directory
con prefissi in grado di generare processi e fa fallire la CI se una qualsiasi route non è classificata come locale.
| Prefisso / pattern | Perché è disponibile solo in locale |
|---|---|
/api/mcp/ |
Server MCP — avvia bridge stdio + gestori SSE |
/api/cli-tools/runtime/ |
Runtime degli strumenti CLI — esegue codice arbitrario dei plugin |
/api/cli-tools/{omp,letta,grok-build,forge,jcode,qwen}-settings |
Writer delle impostazioni per ciascuno strumento, in grado di modificare binari/configurazioni dello strumento sull'host |
/api/cli-tools/{claude,cline,codewhale,codex,crush,deepseek-tui,droid,kilo,openclaw,pi,smelt}-settings |
Stesso avvio di getCliRuntimeStatus() dei sei endpoint analoghi sopra (GHSA-35fw-cv32-2373) |
/api/cli-tools/{all-statuses,status,detect} |
Sonde di inventario CLI — avviano command -v / --version per ogni strumento (GHSA-35fw-cv32-2373) |
/api/cli-tools/antigravity-mitm |
Controllo del proxy MITM Antigravity (avvia/imposta il proxy di sistema) |
/api/modality-bridge/video/ |
Sonda runtime Video Bridge rigorosamente limitata al loopback attendibile e bridge interno di estrazione |
/api/services/ |
Servizi incorporati (9Router / CLIProxy / Bifrost / Mux / Dario) — npm install + avvio |
/dashboard/providers/services/ |
Proxy inverso verso le UI dei servizi incorporati |
/api/tunnels/cloudflared |
Installa/avvia il binario cloudflared |
/api/tunnels/tailscale/{install,enable,disable,login,start-daemon} |
Installa/controlla tailscaled sull'host |
/api/copilot/ |
Driver LLM non autenticato — per impostazione predefinita disponibile solo tramite CLI |
/api/tools/agent-bridge/ |
AgentBridge — avvia il server MITM + modifica il DNS |
/api/tools/traffic-inspector/ |
Traffic Inspector — listener http-proxy + proxy di sistema |
/api/settings/mitm |
Abilita l'intercettazione MITM (stato del proxy a livello di sistema) |
/api/issue-agent/ |
Agente per le issue — avvia strumenti locali sul repository |
/api/plugins/, /api/plugins |
Plugin — caricati/eseguiti tramite worker_threads + child_process |
/api/middleware/ |
Middleware utente — carica/esegue il codice dell'operatore nel processo |
/api/system/version |
Aggiornamento automatico (solo POST; GET/HEAD/OPTIONS esenti) — avvia git checkout + npm install |
/api/db-backups/exportAll |
Avvia tar per l'archivio di esportazione |
/api/local/ |
Avvii locali con un clic (attualmente Redis) — avvia podman/docker |
/api/headroom/start, /api/headroom/stop |
Ciclo di vita del proxy Headroom — avvia la CLI python / invia segnali al PID |
/api/jobs, /api/jobs/ |
Controllo dell'esecutore di job — esegue attività pianificate sul lato host |
/api/oauth/cursor/auto-import |
execFile("which", ["cursor"]) prima di importare le credenziali |
/api/oauth/kiro/auto-import |
Legge i file delle credenziali della CLI Kiro dall'host |
/api/skills/collect/ |
Raccolta delle skill — rileva/installa strumenti locali |
/api/skills/install, /api/skills/executions |
Registrazione + esecuzione dei gestori delle skill — raggiungono l'avvio del container sandbox (GHSA-jx89) |
/api/discovery/ |
Sonde di rilevamento della rete locale/dei provider |
/api/vnc-session (VNC_ROUTE_PREFIX) |
Avvia un browser con interfaccia grafica + una sessione VNC per accessi interattivi |
/api/acp/agents |
ACP — rileva e avvia i binari degli agenti CLI locali |
/api/resilience/connections |
JSON di resilienza per account (cooldown, interruttore, blocco). L'HTML della dashboard non è riservato all'accesso locale. |
/api/providers/cursor/agent-availability |
Controllo della dashboard che suggerisce l'installazione — avvia cursor-agent status --format json |
/api/providers/{id}/login (regex) |
Avvia Chromium Playwright con interfaccia grafica per l'accesso tramite cookie web |
/api/providers/volcengine-plan/connect (regex) |
Flusso manuale con interfaccia grafica + accesso automatico tramite telefono/SMS basato sulla sessione (avvia Playwright) |
/api/providers/{id}/refresh-cursor (regex) |
Rinnovo manuale della sessione Cursor — sollecita cursor-agent |
/api/providers/{id}/chatgpt-web-codex-doctor (regex) |
Diagnostica l'installazione locale della CLI Codex (avvia il binario) |
Risposta in caso di violazione: 403 LOCAL_ONLY
Un sottoinsieme dei percorsi LOCAL_ONLY PUÒ essere accessibile anche da indirizzi non loopback se e
solo se la richiesta contiene un'intestazione Authorization: Bearer <api-key> i cui
metadati includono l'ambito manage (o admin). L'eccezione è abilitata
esplicitamente per ogni percorso tramite LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES, così
l'impostazione predefinita per ogni nuovo percorso LOCAL_ONLY rimane rigorosamente limitata al loopback. Le richieste
non autenticate e quelle con chiavi prive dell'ambito manage vengono comunque rifiutate con
403 LOCAL_ONLY.
Attualmente, l'unico prefisso per cui è possibile applicare l'eccezione è /api/mcp/. /api/cli-tools/runtime/ e
/api/services/ sono intenzionalmente esclusi perché possono avviare sottoprocessi
arbitrari (npm install, node), ossia esattamente la classe di CVE che il
livello LOCAL_ONLY è progettato per prevenire.
#7895 — ambito ristretto mcp:connect: l'eccezione per /api/mcp/ accetta ANCHE
una chiave Bearer con l'ambito ristretto mcp:connect
(src/shared/constants/managementScopes.ts::MCP_CONNECT_SCOPE), verificato tramite
hasMcpConnectOrManageScope() in src/server/authz/policies/management.ts.
Questo è limitato ESCLUSIVAMENTE a /api/mcp/ — mcp:connect non concede alcun privilegio su nessun'altra
rotta di gestione (incluso qualsiasi altro prefisso di eccezione LOCAL_ONLY, qualora ne
venisse aggiunto uno) ed è deliberatamente escluso da
MANAGEMENT_API_KEY_SCOPES. Una chiave con manage/admin continua a superare
l'eccezione esattamente come prima; mcp:connect è un'alternativa con privilegi
inferiori per i chiamanti remoti esclusivamente MCP che non necessitano di un ampio accesso di gestione.
| Richiesta | Percorso | Risultato |
|---|---|---|
| Non loopback, nessun Bearer | /api/mcp/* |
403 LOCAL_ONLY |
Non loopback, Bearer con ambito manage |
/api/mcp/* |
Consentito |
Non loopback, Bearer con ambito mcp:connect |
/api/mcp/* |
Consentito |
Non loopback, Bearer senza manage/mcp:connect |
/api/mcp/* |
403 LOCAL_ONLY |
Non loopback, Bearer con ambito mcp:connect |
/api/cli-tools/runtime/* |
403 LOCAL_ONLY |
Non loopback, Bearer con ambito manage |
/api/cli-tools/runtime/* |
403 LOCAL_ONLY |
| Loopback, con qualsiasi Bearer o senza Bearer | qualsiasi LOCAL_ONLY | Consentito (il controllo viene superato) |
Se esegui OmniRoute dietro un reverse proxy o un tunnel (nginx, Caddy, Cloudflare
Tunnel, Tailscale, Ngrok), il controllo loopback continua a proteggere le rotte in grado di
avviare processi indicate sopra: una richiesta il cui indirizzo client non è loopback viene rifiutata con
403 LOCAL_ONLY prima dell'esecuzione dell'autenticazione, quindi un JWT compromesso non può raggiungere una funzione di avvio. Restano
due responsabilità per gli operatori:
- Non "correggere" un errore 403 falsificando l'IP del client come loopback. Impostare
X-Forwarded-For: 127.0.0.1, oppure utilizzare un proxy che riscrive l'indirizzo sorgente come loopback, riapre esattamente la classe di vulnerabilità RCE che questo livello blocca. Esponi la dashboard/API tramite il proxy, mai le rotte in grado di avviare processi. - Mantieni minima l'eccezione per l'ambito manage. Solo
/api/mcp/può usufruire dell'eccezione, e solo con una chiave API con ambitomanage. Gli elementi diSPAWN_CAPABLE_PREFIXESnon possono mai essere aggiunti all'elenco delle eccezioni: lo schema zod li rifiuta eisLocalOnlyBypassableByManageScopeli nega in fase di esecuzione (difesa in profondità), che è ciò che la dashboard intende con "non può essere reso bypassabile". Le rotte con segmenti dinamici e percorsi statici in grado di avviare processi sotto/api/providers/(ad esempio/login,/refresh-cursor) sono coperte dalle controparti basate su regexSPAWN_CAPABLE_PATTERNS/SPAWN_CAPABLE_PATTERN_ANCESTORSinsrc/shared/constants/spawnCapablePrefixes.ts, non dall'array piattoSPAWN_CAPABLE_PREFIXES: per intercettarle, l'array piatto dovrebbe coprire l'intero prefisso/api/providers/, ampliando eccessivamente un albero di rotte che le dashboard remote utilizzano legittimamente per le operazioni CRUD sui provider.
Verifica degli accessi — per verificare che nessuna origine esterna all'host stia raggiungendo queste rotte:
- Apri Authorization Inventory in
/dashboard/settings/security: mostra l'elenco aggiornato dei prefissi LOCAL_ONLY, quali prefissi possono essere ignorati e l'insieme definito in fase di compilazione con capacità di avviare processi ("non può essere reso ignorabile"). - Cerca nei log del reverse proxy / di accesso i prefissi indicati sopra associati a un
indirizzo client non loopback. Qualsiasi richiesta di questo tipo che abbia restituito
200anziché403 LOCAL_ONLYindica che il proxy sta mascherando l'IP reale del client: correggi il proxy. - Un
403 LOCAL_ONLYnei log di OmniRoute per uno di questi percorsi indica che la protezione funziona come previsto, non è un errore da sopprimere.
Applicato da: isAlwaysProtectedPath(path) → ignora il bypass requireLogin=false
Bypass: Nessuno quando requireLogin=false; JWT sempre obbligatorio
Queste route sono distruttive o irreversibili. Consentirle in un'installazione "senza password" significherebbe permettere a chiunque sulla stessa LAN di cancellare il database o terminare il processo del server.
| Percorso | Motivo |
|---|---|
/api/shutdown |
Termina il processo del server |
/api/settings/database |
Esportazione, importazione e cancellazione del database |
/api/db-backups |
Accesso all'archivio di backup completo del database |
/api/settings/export-json |
Esporta l'intero blob delle impostazioni (segreti inclusi) |
/api/settings/import-json |
Sostituisce l'intero blob delle impostazioni |
/api/providers/health-autopilot/actions |
Esegue le azioni correttive dell'autopilot |
/api/settings/obsidian |
Genera credenziali WebDAV riutilizzabili per qualsiasi radice del vault |
Risposta in caso di violazione: 401 Authentication required
/api/settings/obsidian include la route figlia /webdav: POST indirizza il servizio file WebDAV —
servito dal livello Node personalizzato prima di Next.js, al di fuori di questa pipeline — verso una
radice scelta dal chiamante e restituisce nuove credenziali Basic appena generate, DELETE le ruota
e il POST del percorso padre memorizza il token dell'API REST di Obsidian. GHSA-62vw mascherava
soltanto la rivelazione della password tramite GET; la generazione rimaneva comunque nel livello
fail-open (GHSA-7pq4-8pvv-rx7r). enableObsidianVaultSync() rifiuta inoltre un vault che coincida
con la directory dei dati, sia contenuto al suo interno o la contenga.
Quando non è configurata alcuna password di gestione (e non è presente INITIAL_PASSWORD),
isAuthRequired() in src/shared/utils/apiAuth.ts mantiene aperto il bootstrap anonimo
solo per i peer loopback. Il loopback viene determinato usando, nell'ordine, i segnali attendibili
del peer: il peer TCP reale contrassegnato tramite token (PEER_IP_HEADER + VIA_PROXY_HEADER,
ciò che vede la policy), il verdetto AUTHZ_HEADER_PEER_LOCALITY della pipeline stessa
(ciò che vedono i gestori delle route, considerato attendibile solo finché
OMNIROUTE_PEER_STAMP_TOKEN è impostato) oppure il peer socket reale per i chiamanti diretti.
Host / nextUrl.hostname non vengono mai consultati e l'impostazione della prima password
(POST /api/settings/require-login) è soggetta allo stesso vincolo, anziché essere aperta a ogni
peer di rete (GHSA-7pq4-8pvv-rx7r). managementPolicy passa esplicitamente verso il basso il
proprio verdetto peerContext, quindi le intestazioni della richiesta ORIGINAL (prima della
rimozione) non lo determinano mai.
Tutte le altre route di gestione. L'autenticazione è obbligatoria, a meno che non sia
configurato requireLogin=false. I token CLI possono autenticare queste route (loopback + HMAC valido).
managementPolicy.evaluate(ctx)
1. isLocalOnlyPath(path)?
→ loopback → prosegui
→ non-loopback, Bearer con ambito manage
AND isLocalOnlyBypassableByManageScope → consenti (management_key)
→ altrimenti → rifiuta 403 LOCAL_ONLY
2. isInternalModelSyncRequest(ctx)?
→ consenti (system)
3. hasValidCliToken(headers)?
→ consenti (cli) [loopback + controllo HMAC con timingSafeEqual]
4. isAlwaysProtectedPath(path) o requireLogin=true?
→ isDashboardSessionAuthenticated?
→ consenti (dashboard_session)
→ Bearer con ambito manage su un percorso non aggirabile?
→ consenti (management_key)
→ rifiuta 401/403
5. requireLogin=false?
→ consenti (anonymous)
Il ramo con ambito manage del passaggio 1 è l'unico percorso autenticato in grado di soddisfare una rotta LOCAL_ONLY; la modalità di errore dell'auth-backend restituisce 503 (non 403), affinché un DB scaduto non venga silenziosamente declassato a "nega".
- Aggiungi il prefisso del percorso a
LOCAL_ONLY_API_PREFIXESinsrc/server/authz/routeGuard.ts - Aggiungi un test in
tests/unit/authz/routeGuard.test.tsche verifichi cheisLocalOnlyPath()restituisca true per il nuovo prefisso - Non saltare mai questo passaggio — consulta la Regola tassativa n. 15 in
CLAUDE.md - Decidi: questa rotta deve essere inclusa ANCHE in
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES? La risposta predefinita è no. Abilitala esplicitamente solo quando la rotta può essere esposta in sicurezza a un titolare dell'ambito manage (ovvero NON avvia codice arbitrario controllato dall'utente).
- Verifica che la rotta non esegua codice o comandi forniti dall'utente. Se lo fa, fermati: questa eccezione non è lo strumento appropriato.
- Aggiungi il prefisso a
LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXESinsrc/server/authz/routeGuard.ts - Aggiungi test in
tests/unit/authz/management-policy.test.tsper tutte e quattro le tipologie di richiesta: nessun Bearer (403), Bearer manage (consenti), Bearer non-manage (403) e il test di regressione per prefisso che verifica che/api/cli-tools/runtime/*rimanga strettamente limitato al loopback anche con un Bearer manage.
| File | Scopo |
|---|---|
src/server/authz/routeGuard.ts |
Costanti e funzioni di supporto |
src/server/authz/policies/management.ts |
Logica di valutazione |
tests/unit/authz/routeGuard.test.ts |
Test unitari per gli helper di livello |
tests/unit/authz/management-policy.test.ts |
Test unitari per evaluate() |
Quando aggiungi una nuova rotta a docs/openapi.yaml, applica l'estensione
del fornitore corrispondente se la rotta è classificata da routeGuard.ts:
| Classificazione di routeGuard.ts | Annotazione YAML | Applicazione |
|---|---|---|
LOCAL_ONLY_API_PREFIXES |
x-loopback-only: true |
Bloccata incondizionatamente per richieste non loopback |
ALWAYS_PROTECTED_API_PATHS |
x-always-protected: true |
Autenticazione richiesta anche con requireLogin=false |
| Rotta interna di amministrazione/debug | x-internal: true |
Nascosta per impostazione predefinita da /dashboard/api-endpoints |
| Nessuna (pubblica/autenticazione standard) | (nessuna annotazione necessaria) | Accesso standard controllato da requireLogin |
Due script assicurano la coerenza tra le annotazioni YAML e routeGuard.ts:
scripts/check/check-openapi-coverage.mjs— non riesce se la copertura è < 99%scripts/check/check-openapi-security-tiers.mjs— non riesce se le annotazionix-loopback-onlyox-always-protecteddivergono dalle costanti definite in fase di compilazione
Entrambi gli script vengono eseguiti nell'hook pre-commit e nella CI.
Se x-always-protected o x-loopback-only viene annotato su una rotta che NON è inclusa nella
costante di routeGuard.ts, lo script di copertura non riesce. La soluzione consiste sempre nell'allineare lo
YAML a ciò che routeGuard.ts applica effettivamente, non nell'aggiungere rotte a routeGuard.ts
senza implementare anche la logica di applicazione.
docs/security/CLI_TOKEN.md— token ID macchina della CLIdocs/architecture/AUTHZ_GUIDE.md— pipeline di autorizzazione completadocs/frameworks/MCP-SERVER.md— trasporti e ambiti del server MCP