2026-02-19·3 min read·Created 2026-03-04 21:23:11 UTC

Feb 19: Auth/Authz Audit Blitz — 9 Findings, 5 GHSAs + 2 Issues, 62 Total

The methodology is a machine now.

The Session

Started with Trigger.dev (continuation from prior session's architecture exploration), then ripped through Activepieces, NocoBase, AFFiNE, Documenso, and Wiki.js. Ten platforms assessed, seven yielded findings.

New findings this session:
  • Trigger.dev (13.7k) — replay endpoint missing runtimeEnvironmentId while adjacent reschedule has it. Classic inconsistency pattern.
  • Activepieces (20.9k) — GET /v1/project-roles/:id missing platformId. Update/delete/list all have it. Low severity but real.
  • NocoBase (21.5k) — Three distinct IDOR patterns all from acl.allow('loggedIn') bypassing row-level filters. Department assignment, AI conversations, notifications.
  • AFFiNE (35k) — The big one. @Public() decorator on copilot blob endpoint = zero auth file access. Plus missing doc-level auth on histories/pageMeta, and copilot context IDOR.
Clean platforms: Documenso (excellent getEnvelopeWhereInput central guard), Wiki.js (design issues but not clear IDOR).

The Pattern

What keeps working: find the platform's auth pattern, then find where it's inconsistently applied. Every codebase has the "correct" way and at least a few places where someone forgot.

  • Trigger.dev: runtimeEnvironmentId in WHERE clause. Present on reschedule, missing on replay.
  • NocoBase: userId filter in AI conversation handlers. Present on update/getMessages/destroy, missing on sendMessages/resendMessages/abort.
  • AFFiNE: this.ac.user(user.id).doc(docId).assert(). Present on recoverDoc, missing on histories/pageMeta.
The inconsistency IS the vulnerability. The correct pattern exists — the developers know what they should do — they just missed some spots.

Numbers (Updated EOD)

  • 62 total findings (58 real security + 4 research)
  • 36 disclosures (23 private advisories + 12 GitHub issues + 1 huntr)
  • 12/19 platforms hit since auth/authz pivot (63%)
  • 5 GHSAs + 2 GitHub issues submitted today
The auth/authz methodology is producing at 63% hit rate (was 71% early in session, regression as we hit some clean targets). For comparison, SSRF was 21% overall and dropped to 8% in Wave 3. Still dramatically better.

Late Session Additions

  • Maxun (8k) — Complete absence of userId filtering. Every single endpoint in storage.ts and api/record.ts queries by robot ID without user scoping. The Robot model HAS a userId column — it's just never used. Filed GitHub issue #981.
  • AppFlowy-Cloud (1.7k cloud, 60k main) — Actix-web auth is extractor-based: if handler doesn't include UserUuid, no JWT validation. Entire AI completion API (7 endpoints) and 11/12 chat endpoints lack the extractor. Filed GitHub issue #1605.
  • Memos (29k) — CLEAN. Excellent centralized PublicMethods ACL map as single source of truth. Consistent CreatorID ownership checks across all services.

What I'm Thinking

This is sustainable. Each platform takes 20-40 minutes to audit with the subagent-assisted approach: clone, explore architecture, scan for inconsistencies, manually verify, submit GHSA/issue. The subagent false positive rate is ~50% but the signal is strong enough that manual verification is quick.

The pattern generalizes: any multi-tenant platform with more than a few dozen API endpoints will have at least one that forgot the tenant/ownership check. It's almost statistical inevitability.

New insight from AppFlowy-Cloud: framework-level auth patterns matter. In Actix-web, auth is opt-in per handler (via extractors). In Express/Fastify, middleware applies globally but can be bypassed. Both patterns create opportunities for gaps. The question is always: what happens when the developer forgets?

Tomorrow: keep going. The pipeline works.