From d7bbfcb3fda6e46771feaadb1cb828f447bb0f03 Mon Sep 17 00:00:00 2001 From: hokiepokedad2 <38219945+hokiepokedad2@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:53:24 -0400 Subject: [PATCH] security: stop gym-picker images leaking a Referer to third-party hosts (#242) The gym picker renders two kinds of remote image: the scanner DB's gym.url photo, and the team-icon fallback served from raw.githubusercontent.com. Neither carried a referrer policy, so every image request told the remote host which PoracleWeb instance the user was browsing. All four tags now set referrerpolicy="no-referrer". Browsers already default to strict-origin-when-cross-origin, so the pre-existing leak was the origin rather than the full URL; this closes the remainder. Presentation-only. GymSearchResult.Url still carries the raw scanner URL, and no API, model, or scanner query changed. The photo-proxy endpoint suggested in #242 is deliberately not implemented: fetching a URL supplied by a database PoracleWeb does not own would convert a passive disclosure into an authenticated outbound-request primitive from a host that can reach Poracle, Koji, Golbat and both MySQL servers. Refs #242 --- .../components/gym-picker/gym-picker.component.html | 8 ++++---- CHANGELOG.md | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html index 911b7806..61135ca0 100644 --- a/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html +++ b/Applications/Pgan.PoracleWebNet.App/ClientApp/src/app/shared/components/gym-picker/gym-picker.component.html @@ -1,9 +1,9 @@ @if (selectedGym(); as gym) {
@if (gym.url) { - + } @else { - + }
{{ gym.name ?? gym.id }} @@ -31,9 +31,9 @@
@if (gym.url) { - + } @else { - + }
{{ gym.name ?? gym.id }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1f41ec..9c43ca6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - **Role gating required *every* listed role, and quoted values locked everyone out** ([#367](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/367)): `allowed_role_ids` is described everywhere as an allow-list of Discord roles that grant access, but `CheckRoleAccessAsync` compared with `HashSet.IsSubsetOf`, which only returned true when the user held **all** of the listed roles. Configuring `123,456` therefore denied anyone who had just one of them. It now matches on `Overlaps` — holding any one listed role is enough. Two smaller problems around the same setting are fixed with it. The setting's tooltip rendered its example wrapped in quotes (`"123456789,987654321"`), so admins pasted the quotes in and the comma-split produced entries like `"123456789` that can never equal a Discord role ID; every non-admin was then denied with nothing but an info-level log to explain it (admins bypass the role check, which is why the site looked admin-only). Values are now parsed with surrounding quotes stripped — straight, curly, guillemet, and low-9 variants, matching the quote styles used across the translated tooltips — and entries that aren't numeric snowflakes are dropped and logged as a warning instead of being kept as unmatchable garbage. If a non-empty setting yields no usable IDs at all, non-admin logins are refused with `role_check_failed` and an error log rather than silently falling open to "allow everyone". Finally the tooltip copy (all 11 locales) and the settings/SSO docs now drop the misleading quotes and state the any-of semantics outright. Both callers are affected — the Discord OAuth callback and the OIDC/external-SSO path share this check. Unit tests cover the parser (quote styles, whitespace, non-snowflake entries, dedup, empty values) and the any-of grant/deny decision. +### Security +- **Gym-picker images no longer send a `Referer` header to third-party hosts** ([#242](https://github.com/PGAN-Dev/PoracleWeb.NET/issues/242)): the gym picker renders two kinds of remote image — the scanner DB's `gym.url` photo (a Niantic CDN URL in stock Golbat/RDM deployments, though an operator can rewrite the column to point at a self-hosted mirror) and the team-icon fallback from `raw.githubusercontent.com`. Neither carried a referrer policy, so every image request told the remote host which PoracleWeb instance the user was browsing. All four `` tags in `gym-picker.component.html` now set `referrerpolicy="no-referrer"`. Modern browsers already default to `strict-origin-when-cross-origin`, so the pre-existing leak was the origin rather than the full URL — this closes the remainder. Presentation-only: no API, model, or scanner-query change, and `GymSearchResult.Url` still carries the raw scanner URL as before. The photo-proxy endpoint floated in the original issue was **not** implemented: server-side fetching of a URL supplied by a database PoracleWeb does not own would turn a passive disclosure into an authenticated outbound-request primitive from a host that can reach Poracle, Koji, Golbat, and both MySQL servers. A host allowlist applied at projection remains the cheaper option if a deployment ever needs the mirror case handled. + ## [2.11.1] - 2026-06-05 ### Fixed