
Trova le route API che presentano un'autorizzazione più debole rispetto a quelle dello stesso livello. CVE-2026-45316 recuperato dal codice sorgente. Include i risultati negativi.
Finds API routes that carry a weaker authorization control than their siblings.
It found CVE-2026-45316 in Open WebUI from source alone, with no advisory knowledge:
[MISMATCH] POST /notes/{id}/pin (notes.py:pin_note_by_id)
3/3 comparable write operations on note require has_access(write);
this route requires only has_access(read)
POST /notes/{id}/update has_access(write)
POST /notes/{id}/access/update has_access(write)
DELETE /notes/{id}/delete has_access(write)
Across 2,568 routes in five production codebases it produced 4 findings. Two were real. This README explains both numbers.
Una vasta classe di bug di autorizzazione non è un controllo errato. È un controllo mancante o indebolito, su una route le cui sorelle lo hanno invece implementato correttamente.
Portainer autorizzava quattro endpoint template sorelle e non il quinto. Signal K applicava il rate limiting al login HTTP ma non al login WebSocket. La route note-pin di Open WebUI mutava una nota verificando il permesso di lettura, mentre ogni altra mutazione di nota verificava quello di scrittura.
Sempre la stessa forma:
✓ ✓ ✓ ✓ ✗
Il repository contiene già la regola. Una route l'ha violata. Quindi ricostruisci la regola dal codice e segnala l'eccezione. Nessun file di policy, nessuna configurazione, nessuna annotazione. L'insieme di prova sono le route sorelle stesse.
python3 enforcement_coverage.py /path/to/repo
python3 enforcement_coverage.py /path/to/repo --density
python3 enforcement_coverage.py /path/to/repo --json
Python 3.10+, nessuna dipendenza. Solo FastAPI.
I verdetti sono MISSING, MISMATCH, PRESERVED, UNKNOWN. UNKNOWN si astiene
e non viene mai segnalato.
Estrazione. Risolve i controlli da Depends() nella firma, dal decorator
dependencies=[], dalle dipendenze a livello di router, dai corpi delle funzioni,
dalle funzioni wrapper e dalle liste di classi di permesso.
L'estrazione dai corpi delle funzioni è essenziale. In Open WebUI, 216 dei 608 handler contengono il controllo decisivo dentro la funzione:
if user.role != 'admin' and not await AccessGrants.has_access(
user_id=user.id, resource_type='note',
resource_id=note.id, permission='write', db=db,
):
raise HTTPException(status_code=403)
Classe di operazione deriva da ciò che l'handler fa alla risorsa, non dal
verbo HTTP. POST /notes/{id}/chat è una POST che legge una nota.
Classificazione del vocabolario. Due valori in una famiglia di controlli non formano necessariamente una scala di intensità:
has_access {read, write} LEVEL
ensure_flow_permission {create,delete,execute,read,write} ACTION
has_permission {features.notes, workspace.tools} SCOPE
Solo i vocabolari LEVEL supportano il confronto di intensità. Confrontare
FlowAction.CREATE con un WRITE dominante produceva sei falsi positivi in
Langflow prima che il problema venisse corretto.
Filtro di direzione. Vengono segnalate solo le deviazioni verso un controllo meno restrittivo. Essere più severi del precedente non è una vulnerabilità.
Due veri positivi.
POST /notes/{id}/pin in Open WebUI — CVE-2026-45316.
Un ulteriore risultato è un'asimmetria di permessi in Netflix Dispatch:
POST /{incident_id}/resources usa IncidentViewPermission mentre sei
operazioni di scrittura sorelle usano IncidentEditPermission.
IncidentViewPermission restituisce True per qualsiasi incidente non ristretto;
IncidentEditPermission richiede admin, commander o reporter. L'handler
accoda la creazione di ticket e gruppi senza ulteriori verifiche.
Non segnalato, perché non c'è nessun luogo dove segnalarlo: il repository è stato archiviato da Netflix il 3 settembre 2025 ed è in sola lettura, non ha un SECURITY.md, la segnalazione privata delle vulnerabilità è disabilitata per i repo archiviati, e Dispatch è esplicitamente elencato come fuori scope nel programma bounty di Netflix. Pubblicarlo qui è l'unico canale di divulgazione rimasto. È a bassa severità — richiede un membro autenticato dell'organizzazione e riguarda solo incidenti non ristretti — e il progetto non è più mantenuto.
Due falsi positivi. POST /tools/{id}/valves/user/update scrive le
impostazioni delle valve proprie dell'utente e legittimamente richiede solo la
lettura dello strumento — il bersaglio della mutazione è un'entità diversa dal
soggetto dell'autorizzazione. E una route della knowledge base di Langflow in cui
il controllo delle sorelle non è richiesto.
Questa è la parte utile.
Danswer ha una copertura dei controlli del 95.9% e ha prodotto zero risultati con l'80.7%
di UNKNOWN. Il suo vocabolario è require_permission('basic_access'),
('manage_connectors') — uno spazio dei nomi di capacità, non una scala di intensità.
Non si può dire che manage_connectors sia più debole di read_connectors.
Ciò che predice i risultati è una chiamata di permesso con ambito risorsa e un argomento
di intensità ordinato, come has_access(resource, read|write). Un codebase su
cinque lo aveva.
Cinque idiomi in cinque repository. Ciascuno ha richiesto lavoro sull'estrattore prima che l'analisi potesse essere eseguita.
In qualsiasi repository, con qualsiasi configurazione. La maggior parte delle route non appartiene a una famiglia di sorelle di tre o più elementi con un controllo coerente.
Nessuno di questi era stato previsto in anticipo.
Depends()Il difetto 13 è quello interessante. Le route di raccomandazione dei tag di Dispatch
non hanno CaseViewPermission che le loro sorelle hanno. Ma quel permesso restituisce
True per qualsiasi caso non ristretto, e il servizio verifica già
visibility == restricted inline. I percorsi sono equivalenti. Rilevarlo
richiede un'analisi di equivalenza semantica, non strutturale.
Il meccanismo funziona. Ha recuperato una CVE pubblicata e una lacuna di autorizzazione non segnalata dal codice sorgente, con insiemi di prova verificabili, usando solo informazioni disponibili prima che il commit vulnerabile venisse integrato.
Il rendimento è di circa un risultato ogni 1,000 route, e richiede un'architettura che uno su cinque codebase sostanziosi possedeva.
Utile come strumento di audit per un codebase con un vocabolario di permessi con ambito risorsa. Non, sulla base di queste evidenze, uno scanner generico.
La rilevazione statica delle vulnerabilità di controllo degli accessi mediante l'inferenza di assunzioni implicite risale a USENIX Security 2011. ACMiner ha estratto i controlli di autorizzazione nel middleware Android. Semgrep include una rilevazione basata su IA mirata all'autorizzazione mancante e ha riportato una precisione del 61% in una valutazione con un cliente. OWASP pubblica un cheat sheet sull'Authorization Regression Testing il cui approccio raccomandato è mantenere manualmente una matrice Attore × Risorsa × Azione.
Questo è un approccio ristretto e deterministico: recupera solo ciò che le route sorelle dimostrano, altrimenti si astiene.
Licenza MIT.
| repo | route | controllate | risultati |
|---|
| LiteLLM | 808 | 87.1% | 0 |
| Danswer / Onyx | 653 | 95.9% | 0 |
| Open WebUI | 529 | 97.2% | 2 |
| Netflix Dispatch | 291 | 36.1% | 1 |
| Langflow | 287 | 47.4% | 1 |
| repo | idioma |
|---|
| Open WebUI | AccessGrants.has_access(resource_type=, permission=) |
| Langflow | ensure_<resource>_permission(user, Action.X) |
| LiteLLM | confronto di ruolo su user_api_key_dict.user_role |
| Netflix Dispatch | Depends(PermissionsDependency([CaseEditPermission])) |
| Danswer | require_permission('basic_access') |