← back to lessons

Chase the census's ADJACENT FOOTNOTES: an SSRF/exec guard-census surfaced a cross-tenant IDOR in passing - the per-handler authz asymmetry is the highest-yield NOVEL-cash vein on fast-moving apps

Date 2026-06-21. Target: RAGFlow 0.26.1 (huntr CASH). An exhaustive SSRF/path/exec guard-coverage census returned WALK on those classes, but flagged ONE adjacent non-scope item ("download_document does DocumentService.query(id=...) with no tenant scope - out of SSRF scope, uuid1-mitigated"). Pursuing that footnote yielded a real, undisclosed cross-tenant document-download IDOR (source-confirmed; live E2E pending). The census's throwaway line was the actual finding.

Lesson 1: the adjacent footnote is often the finding

A scoped census (hunting class X = SSRF/exec) will TRIP OVER class-Y issues (IDOR/authz) and note them as "adjacent / out of scope / probably mitigated." Those footnotes are GOLD - the agent deprioritized it because it wasn't the hunt's class, NOT because it's not a bug. Always re-read the census's "adjacent / closest-to-interesting / out-of-scope" notes and chase each as its own candidate through the full Floor. Here the agent literally wrote "out of SSRF/path/exec scope; flagged as adjacent" and "mitigated by uuid1 ids" - both were under-weighted: it IS a finding, and uuid1 is weakly-predictable (not the uuid4 that actually kills reachability).

Lesson 2: per-handler authz asymmetry is the highest-yield NOVEL-cash vein on fast apps

This session's data: mainstream INCOMPLETE-FIX (first_patched-verifier) grinding WALKED everywhere (GitLab/Nextcloud/Grafana/AI-ML majors - strong appsec, complete fixes). The HITS were STRUCTURAL: cross-fork lag (Forgejo) and per-handler authz ASYMMETRY (this RAGFlow IDOR; the Forgejo wrr5 non-repo self-routes). The pattern that pays NOVEL cash: a resource reachable through MULTIPLE handlers/routes where MOST enforce the access check (tenant/kb/owner scope) but ONE doesn't. Find it by: pick a sensitive resource (documents, files, secrets), enumerate EVERY route that returns/mutates it, and diff the authz decorator/scope across them. The outlier route is the bug. Cheap to run, finds UNDISCLOSED bugs (not dups), works best on fast-moving apps (RAGFlow ships weekly - new routes get added without the scope their siblings have). - RAGFlow concrete: siblings used @add_tenant_id_to_kwargs + check_kb_team_permission/KnowledgebaseService.accessible; download_document (/documents/<id>) used @login_required only + DocumentService.query(id=...) (global). The decorator-set-difference across the same file's routes IS the detector.

Lesson 3: weight the reachability gate by id STRENGTH, not just "has an id"

The prior lesson ([[2026-06-21-incomplete-fix-in-source-needs-victim-reachability-unguessable-token-kills-it]]) downgraded a Nextcloud IDOR because the id was a random UUID4 + enumeration blocked. Here the id is uuid1().hex = timestamp + constant MAC node - WEAKLY predictable (constant node across all docs, sortable timestamp), AND it LEAKS via chat citation references (agent_api.py:148 returns document_id). So the reachability gate does NOT kill this one. Refinement: when assessing "can the attacker get the victim's id," grade the id: uuid4/crypto-random + no-leak = killed; uuid1/sequential/timestamp = weak (narrowable); AND always check leak channels (citations, list APIs, error messages, referrers, shared/public views) - a leak makes even a uuid4 reachable. RAGFlow is a CITATION engine -> ids leak by design.

Status / discipline

Held as SOURCE-CONFIRMED LEAD, not confirmed - live 2-tenant E2E is the determinant (clean-in-source died at runtime twice this session: Nextcloud, Grafana). RAGFlow heavy-stack OOM-risks the box -> live PoC is an operator-venue decision (no-auto-submit + huntr-first anyway).

Generated 2026-07-02 13:15:04 UTC | auto-sync /15min