Date: 2026-06-23 Target: GitLab CE :latest, self-hosted docker (gl-fuzz, :8929), disposable Tool: fuzzer-arm v0.3 (full-surface adapter targets/gitlab_full.py + run_gitlab_full.py) Cash-gate: GitLab HackerOne program ACTIVE + PAYING (policy updated 2026-05-05, avg bounty $5,667); product source code in-scope at highest reward tier; GitLab explicitly recommends self-managed/local testing. IDOR/BAC = standard in-scope. Self-hosted differential is in-ROE + cash-eligible.
v0 tested 12 CURATED endpoints (looked clean -> "is it clean only because we barely looked?").
v0.3 enumerated the ENTIRE surface from the live Grape route table and tested ~100x more:
- 1419 REST routes dumped via gitlab-rails runner over API::API.routes
(641 GET, 332 POST, 272 PUT, 155 DELETE, 19 PATCH; 550 GET-with-id-param)
- + 9 GraphQL GID/fullPath cross-tenant probes (introspection-independent)
- 1179 endpoints actually TESTED (508 read pass + 671 write pass), rest unfillable.
The "old run only tried 12" hypothesis is REFUTED: clean at 1179 too. GitLab CE's per-object
authorization (declarative_policy + find_project!/authorize!) holds across the full surface.
Grape serves no usable OpenAPI -> dump the route table from the running container:
docker exec <c> gitlab-rails runner 'API::API.routes.each{|r| puts "#{r.request_method}\t#{r.path}"}'
then normalize Grape templates to {param}: strip (.:format), :version->v4, and name bare :id
after the preceding literal segment (/projects/:id -> {project_id}, /groups/:id -> {group_id}).
This converts a "no-spec" target into a fully-enumerable one. Reusable for any Grape/Rails-mounted API.
/self literal in path -> false write-BAC. DELETE /personal_access_tokens/self returned B=204;
self resolves to the CALLER's own token (bob deleted bob's), caller-scoped self-service, not
cross-tenant. Any /self, /me, /current literal segment is caller-scoped -> exclude from BAC.ACTION (next engine tick, <=24h per Impl Rule): add an FP-allowlist to differ.triage for
follow/unfollow/star + /self|/me|/current segments so the write pass auto-demotes these to
benign instead of FLAGGED. Until then they are correctly surfaced (not silently dropped) and
human-dismissed.
Doctrine echo: WALK CLEAN still banks lessons. The capability (full-surface Grape enumeration + 1179-endpoint low-noise differential) is the asset; point it next at a fresher/less-audited multi-tenant API. Per directive, GitLab genuinely-clean -> rotate to Mattermost CORE /api/v4 (Bugcrowd active+paying, cash-gate already confirmed, core API never fuzzed - only plugins). NO auto-submit anywhere.