Skip to content

Latest commit

 

History

History
299 lines (246 loc) · 29.5 KB

ROUTE_GUARD_TIERS.md

File metadata and controls

299 lines (246 loc) · 29.5 KB

Route Guard Tiers (Italiano)

🌐 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


Panoramica

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.

Livelli

Livello 1 — LOCAL_ONLY

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

Eccezione per l'ambito manage

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)

Indicazioni per gli operatori e verifica

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 ambito manage. Gli elementi di SPAWN_CAPABLE_PREFIXES non possono mai essere aggiunti all'elenco delle eccezioni: lo schema zod li rifiuta e isLocalOnlyBypassableByManageScope li 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 regex SPAWN_CAPABLE_PATTERNS / SPAWN_CAPABLE_PATTERN_ANCESTORS in src/shared/constants/spawnCapablePrefixes.ts, non dall'array piatto SPAWN_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 200 anziché 403 LOCAL_ONLY indica che il proxy sta mascherando l'IP reale del client: correggi il proxy.
  • Un 403 LOCAL_ONLY nei log di OmniRoute per uno di questi percorsi indica che la protezione funziona come previsto, non è un errore da sopprimere.

Livello 2 — ALWAYS_PROTECTED

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.

Il bootstrap di una nuova installazione è riservato al loopback — in base al peer reale, non a Host

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.

Livello 3 — MANAGEMENT (predefinito)

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).

Ordine di valutazione

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".

Aggiunta di una nuova rotta in grado di avviare processi

  1. Aggiungi il prefisso del percorso a LOCAL_ONLY_API_PREFIXES in src/server/authz/routeGuard.ts
  2. Aggiungi un test in tests/unit/authz/routeGuard.test.ts che verifichi che isLocalOnlyPath() restituisca true per il nuovo prefisso
  3. Non saltare mai questo passaggio — consulta la Regola tassativa n. 15 in CLAUDE.md
  4. 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).

Aggiunta di un percorso aggirabile tramite ambito manage

  1. Verifica che la rotta non esegua codice o comandi forniti dall'utente. Se lo fa, fermati: questa eccezione non è lo strumento appropriato.
  2. Aggiungi il prefisso a LOCAL_ONLY_MANAGE_SCOPE_BYPASS_PREFIXES in src/server/authz/routeGuard.ts
  3. Aggiungi test in tests/unit/authz/management-policy.test.ts per 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

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()

Documentazione dei livelli di sicurezza in OpenAPI

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

Validazione

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 annotazioni x-loopback-only o x-always-protected divergono dalle costanti definite in fase di compilazione

Entrambi gli script vengono eseguiti nell'hook pre-commit e nella CI.

Regola sui falsi positivi

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.


Vedi anche

  • docs/security/CLI_TOKEN.md — token ID macchina della CLI
  • docs/architecture/AUTHZ_GUIDE.md — pipeline di autorizzazione completa
  • docs/frameworks/MCP-SERVER.md — trasporti e ambiti del server MCP