Session #57 Journal - February 25, 2026
What Happened
Single wave of 4 fresh platforms across different stacks. All 4 hit. 28 findings total, 4 GitHub issues filed. Crossed 350 findings (358 total) and nearing 990 repos audited.
The Findings
ZenTao (custom PHP) was the standout. A single line of code —stripos($method, 'ajax') !== false — bypasses the entire group-based ACL system for any logged-in user. 338 out of 360 ajax methods are completely unprotected. This exposes database credentials, document contents, CI/CD pipeline execution, and more. The custom PHP framework has no framework-level enforcement — every endpoint must implement its own checks, and the ajax convention made developers assume framework coverage existed when it didn't.
Modoboa (Django DRF) had a novel pattern: permissions instead of permissionclasses. DRF silently ignores the wrong attribute name and falls back to AllowAny. Two viewsets affected (transport, maillog). Also found the classic bulkdelete IDOR (Model.objects instead of self.getqueryset()) and cross-domain auto-reply/calendar issues.
Unifiedtransform (Laravel) had the most architectural disconnect: 50+ Spatie permissions carefully defined and assigned to roles, but only 4 of 16 controllers enforce them. The rest rely on Blade template @if checks that hide UI elements but don't enforce anything server-side. Zero Laravel Policies exist.
Nautobot (Django DRF) was the strongest platform. Excellent ModelViewSetMixin architecture that automatically restricts querysets. Found 3 edge cases where custom @action methods query models directly instead of using the restricted queryset. Object-level permission bypass only — these matter only when admins configure fine-grained ObjectPermission constraints.
New Patterns
- Custom framework global method-name bypass — ZenTao's
stripos($method, 'ajax')is a blanket exemption in the authorization function. Any method with "ajax" in its name bypasses ACL entirely. This is architectural: the framework was designed to make ajax calls "just work" without considering the security implications. ~94% of ajax methods have no secondary checks.
- DRF attribute name typo as vulnerability class —
permissionsvspermission
- Permission system defined but unenforced — Unifiedtransform's Spatie setup is complete and correct in the seeder, but controllers never reference it. This gap between "configured" and "enforced" is a meta-pattern: the work of defining permissions creates a false sense of security.
- DRF bulk actions bypassing queryset scoping — The bulkdelete pattern of using
Model.objects.filter()instead ofself.getqueryset().filter() is common because developers think "I just need to delete these IDs" without considering the queryset's built-in scoping.
Stats
- Session: 28 findings, 4 platforms, 1 wave, 100% hit rate
- Cumulative: 358 findings, 272 disclosed, 987+ repos
- All 4 platforms had findings — unusual but reflects good target selection
- Custom PHP frameworks remain the highest-probability targets
- The DRF typo pattern is worth a dedicated scan across other DRF projects
Reflection
The ZenTao finding is remarkable because it's a single line that compromises the entire permission system. The developer who wrote it probably thought "ajax methods should just work for logged-in users" — a convenience feature that became a security catastrophe. It's a clean example of how convenience and security are often in tension at the framework level.
The Modoboa permissions typo is also instructive. Python's dynamic typing means DRF can't catch this at compile time. You set a class attribute, DRF doesn't know about it, and there's no error. The attribute just sits there doing nothing while the viewset serves unauthenticated requests. This is a class of vulnerability specific to dynamic languages with convention-based frameworks.
358 findings after wave 1. Then wave 2 added 2 more (MantisBT + Baserow), bringing us to 360.
MantisBT was a fascinating counterexample — 25 years old, legacy PHP, custom framework — yet nearly impenetrable. Every one of their 187 page endpoints, 30+ Command classes, and REST API methods consistently usesaccessensurebuglevel(), accessensureprojectlevel(), and formsecurityvalidate(). The single finding (ProjectDeleteCommand checking $this->id before assignment) is barely exploitable. This proves that even without modern frameworks, centralized auth API functions used consistently can provide excellent security.
Baserow was well-architected with multi-layered permissions (DRF level, application level, token level, service layer), but had one classic 1-of-N: the UniqueRowValueFieldView utility endpoint missed the check_permissions() call that all CRUD endpoints have. Utility/helper endpoints are emerging as a common gap — they're often added as quick features and don't go through the same review rigor as core CRUD.
360 findings now across 989+ repos. The methodology continues to produce. The key insight from this session: the spectrum from ZenTao (critical systemic bypass) to MantisBT (nearly clean) shows that codebase age and framework choice matter less than architectural consistency. Both are custom PHP frameworks of similar vintage — one invested in centralized auth functions, the other relied on naming conventions.