Skip to content

Restrict avatar/logo URL schemes at write time (template-context hardening) #103

Closed

Description

@shockalotti

What

AvatarURL, LogoURL and similar branding values flow into rendered templates (including the public directory pages). No javascript:/data:-URL rejection was found at the settings/upload layer. html/template neutralizes javascript: in URL attribute contexts, but only if each template places these values in a URL context (vs a CSS/style context, where escaping rules differ). This pass verified the Go side only, not every template insertion point.

Fix

  1. Confirm directory.html and the booking templates put avatar/logo only in src/href attribute contexts.
  2. Restrict stored avatar/logo URLs to http(s) plus relative paths at write time (defense in depth, independent of template context).

Also confirmed in the same pass: GET /v1/bookings/{id} (internal/server/server.go:454) is registered without auth or rate limit, unlike every neighboring route. IDs are UUIDv4 so enumeration is infeasible, and the response omits attendee PII, but any leaked ID discloses that booking's times, status and location_value with no token. Harden by requiring the manage token or rate-limiting. Tracking here to keep it with the audit batch; split out if preferred.

Found by the 0.10.1 pre-release audit (Layer 2, SSRF + isolation passes).

Activity

  1. pullfrog commented on Sep 29, 2026

    @pullfrog
    Contributor
  2. shockalotti commented on Sep 29, 2026

    @shockalotti
    ContributorAuthor

    Both parts addressed, one with code and one with verification:

    Avatar/logo schemes: verified safe, no change. Every insertion point (directory.html, book.html, manage.html) puts these values in src="..." attribute contexts only, where html/template neutralizes javascript: URLs. And there is no free-text URL write path to restrict: logo/banner come solely from file uploads (server-generated relative paths), avatars solely from uploads (/avatars/<id>) plus NULL-clearing - no OAuth picture URLs, no pasted-URL fields anywhere. Validating server-generated paths would be theater, so the defense here is the template contexts, now confirmed by inspection.

    GET /v1/bookings/{id}: rate-limited, contract kept. It stays public (deliberate - UUIDv4 IDs are not enumerable, no attendee PII in the response, and external API consumers may rely on it), but it now sits behind the same 20/min booking limiter as booking creation instead of unlimited. Auth-gating would have been stronger but breaks the public contract; the limiter bounds leaked-ID probing to irrelevance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions