Skip to content

P0 metadata: enforce visibility filter in List and Get #44

Closed

Description

@SlavaKuntsov

P0 — источник UUID приватных видео.

List (repository/video.go:64) и Get (handler/video.go:110) не фильтруют по visibility, gateway проксирует их без JWT (gateway/cmd/server/main.go:129). Публичный список отдаёт ID приватных видео — замыкает CRITICAL-цепочку data-plane обхода.

Фикс: WHERE (visibility='public' OR owner_id=$me) для List (owner из X-User-ID, если есть); Get — 403/404 для private не-владельцу; unlisted — не в List, но доступен по ссылке.
Verify: приватное видео отсутствует в ленте и 403 по прямому ID под чужим юзером.
Источник: docs/REVIEW-2026-09-13-merged.md §1, M1


Автоматически создано из сводного ревью. Сверяй актуальность номеров строк — они зафиксированы на коммите 91efb5b.

Activity

  1. self-assigned this
    on Sep 14, 2026
  2. SlavaKuntsov commented on Sep 15, 2026

    @SlavaKuntsov
    OwnerAuthor

    Ready for review

    Implemented

    • metadata List: SQL filter WHERE visibility='public' OR owner_id=$viewer — anonymous sees only public, owner sees own private/unlisted (repository/video.go)
    • metadata Get: private video → 403 for non-owner (and anonymous); unlisted stays reachable by direct link; /internal/* endpoints (transcoder, HLSAuth) bypass the check
    • metadata: new OptionalAuth middleware — viewer identity on public GETs comes only from a metadata-validated JWT (X-User-ID header is not trusted; metadata port is published). Refresh tokens rejected (type check)
    • gateway: public metadata GET routes wrapped in OptionalAuth — valid Bearer is forwarded, client-supplied X-User-ID stripped, invalid/expired token degrades to anonymous (no 401 on the public feed)
    • metadata/docs: swagger regenerated (Get now documents 403)

    Acceptance criteria

    • Приватное видео отсутствует в ленте (anonymous/чужой юзер)
    • 403 по прямому ID под чужим юзером (и anonymous)
    • Unlisted не в List, доступен по ссылке
    • Internal-эндпоинты работают как раньше (transcoder, HLS)

    Verification

    • make lint-go — 0 issues
    • make test-go — all pass (new: TestListVisibilityFilter, TestGetVisibility + forged-header/refresh-token cases; gateway TestOptionalAuth* ×4)
    • make swagger — regenerated for metadata
    • make e2e — not run locally (docker stack not started in this session)

    Review cycle

    • Iterations: 2 (independent review + re-review)
    • Fixed during review: forgeable X-User-ID trust boundary (→ metadata-side JWT), OptionalAuth 401 regression for expired tokens, refresh-token-as-identity, missing OptionalAuth tests
    • Remaining risks: existence oracle (403 vs 404 for private IDs) kept per ticket wording; pre-existing dev fallback: /internal/* open when INTERNAL_TOKEN is empty (out of scope)

    Suggested PR title

    fix(metadata): enforce visibility filter in list and get

  3. SlavaKuntsov commented on Sep 15, 2026

    @SlavaKuntsov
    OwnerAuthor

    Pull request opened

    #72 — will auto-close via Closes #44 on merge.

    Project status: In review (synced after PR creation).

    Verification

    • make lint-go — 0 issues
    • make test-go — all pass (new regression tests for visibility filter + optional auth)
    • make swagger — metadata docs regenerated
    • make e2e — not run in this session (docker stack not started)

    Remaining risks

    • Existence oracle (403 vs 404 for private IDs) kept per ticket wording
    • Pre-existing dev fallback: /internal/* open when INTERNAL_TOKEN is empty (out of scope)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:metadataarea:metadataphase:16Phase 16 security hotfix: CRITICAL chain + CVE

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions