Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitHubGitHub/arian-gogani/enforcement-coverage
Static Code Analysis (SAST)Vulnerability AnalysisCode AnalysisPenetration TestingDevSecOpsAPI Security
GitHubarian-gogani/enforcement-coverage

enforcement-coverage

Finds API routes carrying weaker authorization than their siblings. Recovered CVE-2026-45316 from source. Includes the negative results.

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
View Repository
15h 36m agoNot yet reviewed

Enforcement Coverage

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:

root@kitploit:~
[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.

The idea

A large class of authorization bugs is not a broken check. It is a missing or weakened one, on a route whose siblings all got it right.

Portainer authorized four sibling template endpoints and not the fifth. Signal K rate-limited HTTP login but not WebSocket login. Open WebUI's note-pin route mutated a note while checking read permission, when every other note mutation checked write.

Always the same shape:

root@kitploit:~
✓ ✓ ✓ ✓ ✗

The repository already contains the rule. One route broke it. So reconstruct the rule from the code and report the exception. No policy file, no config, no annotations. The proof set is the sibling routes themselves.

Run it

root@kitploit:~
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+, no dependencies. FastAPI only.

Verdicts are MISSING, MISMATCH, PRESERVED, UNKNOWN. UNKNOWN abstains and is never reported.

What it does

Extraction. Resolves controls from signature Depends(), decorator dependencies=[], router-level dependencies, function bodies, wrapper functions, and permission-class lists.

Body extraction is essential. In Open WebUI, 216 of 608 handlers carry the decisive check inside the function:

root@kitploit:~
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)

Operation class comes from what the handler does to the resource, not from the HTTP verb. POST /notes/{id}/chat is a POST that reads a note.

Vocabulary classification. Two values in one control family do not necessarily form a strength scale:

root@kitploit:~
has_access             {read, write}                      LEVEL
ensure_flow_permission {create,delete,execute,read,write}  ACTION
has_permission         {features.notes, workspace.tools}   SCOPE

Only LEVEL vocabularies support strength comparison. Comparing FlowAction.CREATE against a dominant WRITE produced six false positives in Langflow before this was fixed.

Direction filter. Only deviations toward a less restrictive control are reported. Stricter than precedent is not a vulnerability.

What it found

Two true positives.

POST /notes/{id}/pin in Open WebUI — CVE-2026-45316.

One further finding is a permission asymmetry in Netflix Dispatch: POST /{incident_id}/resources uses IncidentViewPermission while six sibling write operations use IncidentEditPermission. IncidentViewPermission returns True for any non-restricted incident; IncidentEditPermission requires admin, commander, or reporter. The handler queues ticket and group creation with no further check.

Not reported, because there is nowhere to report it: the repository was archived by Netflix on 3 September 2025 and is read-only, it has no SECURITY.md, private vulnerability reporting is disabled for archived repos, and Dispatch is explicitly listed as out of scope on Netflix's bounty program. Publishing it here is the only remaining disclosure channel. It is low severity — it requires an authenticated organization member and affects only non-restricted incidents — and the project is no longer maintained.

Two false positives. POST /tools/{id}/valves/user/update writes the user's own valve settings and legitimately needs only read on the tool — the mutation target is a different entity than the authorization subject. And a Langflow knowledge-base route where the sibling guard is not required.

Why it mostly does not work

This is the useful part.

Density does not predict findings

Danswer has 95.9% control coverage and produced zero findings with 80.7% UNKNOWN. Its vocabulary is require_permission('basic_access'), ('manage_connectors') — a capability namespace, not a strength scale. You cannot say manage_connectors is weaker than read_connectors.

What predicts findings is a resource-scoped permission call with an ordered strength argument, like has_access(resource, read|write). One codebase in five had that.

Every codebase needed different extraction

Five idioms across five repositories. Each needed extractor work before the analysis could run at all.

UNKNOWN never dropped below 72%

In any repository, under any configuration. Most routes do not belong to a sibling family of three or more with a consistent control.

Defects found by running against real code

None of these were anticipated in advance.

  1. authorization lives in function bodies, not Depends()
  2. routes carry several control families at once
  3. HTTP verb is not operation class
  4. authorization idioms differ per repository
  5. action vocabularies are not strength scales
  6. stricter-than-precedent is not a vulnerability
  7. vocabulary classified from precedents alone hides single-value families
  8. validation helpers raising 422 are not authorization
  9. wrapper functions hide the real control
  10. permission-class-list idioms need name splitting
  11. loosening sibling families raised findings 7.5x and dropped precision from 50% to 13%
  12. a route with a different sufficient control is not missing one
  13. equivalent enforcement implemented inline rather than as a dependency — unsolved

Defect 13 is the interesting one. Dispatch's tag-recommendation routes lack the CaseViewPermission their siblings carry. But that permission returns True for any non-restricted case, and the service already checks visibility == restricted inline. The paths are equivalent. Detecting that needs semantic equivalence analysis, not structure.

Honest assessment

The mechanism works. It recovered a published CVE and one unreported authorization gap from source, with auditable proof sets, using only information available before the vulnerable commit merged.

The yield is roughly one finding per 1,000 routes, and it needs an architecture that one of five substantial codebases had.

Useful as an audit tool for a codebase with a resource-scoped permission vocabulary. Not, on this evidence, a general-purpose scanner.

Prior art

Static detection of access-control vulnerabilities by inferring implicit assumptions dates to USENIX Security 2011. ACMiner mined authorization checks in Android middleware. Semgrep ships AI-powered detection targeting missing authorization and reported 61% precision in one customer evaluation. OWASP publishes an Authorization Regression Testing cheat sheet whose recommended approach is maintaining an Actor × Resource × Action matrix by hand.

This is a narrow, deterministic take: recover only what sibling routes prove, abstain otherwise.

MIT licensed.

Download Tool
reporoutescontrolledfindings
LiteLLM80887.1%0
Danswer / Onyx65395.9%0
Open WebUI52997.2%2
Netflix Dispatch29136.1%1
Langflow28747.4%1
repoidiom
Open WebUIAccessGrants.has_access(resource_type=, permission=)
Langflowensure_<resource>_permission(user, Action.X)
LiteLLMrole compare on user_api_key_dict.user_role
Netflix DispatchDepends(PermissionsDependency([CaseEditPermission]))
Danswerrequire_permission('basic_access')