Skip to content

Harden customer portal plan validation and outbound requests - #22

Merged
rrader26 merged 4 commits into
mainfrom
hardening/portal-and-outbound
Oct 17, 2026
Merged

rrader26 merged 4 commits into
mainfrom
hardening/portal-and-outbound

Conversation

@rrader26

Copy link
Copy Markdown

Summary

Tightens input handling in the customer portal, outbound HTTP calls, organization creation, the white-label API and the white-label agreement gate.

Customer portal plans

  • New CustomerPortal::AvailablePlansQuery holds the plan visibility rules (top-level, self_serve, not hidden_in_portal, visible_to_products). The customerPortalAvailablePlans resolver and the portal create / changePlan mutations now share it, so the server accepts exactly the plans the portal lists.
  • product_key lookups escape LIKE wildcards.
  • Portal mutation errors now use the correct execution_error signature.

Outbound HTTP

  • New LagoHttpClient::OutboundUrlGuard resolves the destination host and only allows public addresses (IPv4 and IPv6, including IPv4-mapped/NAT64/6to4 forms).
  • LagoHttpClient::Client accepts restrict_outbound: true. With it, the connection is pinned to the checked address (Host header and SNI keep the hostname), the proxy is turned off, and redirects are not followed.
  • Applied to webhook delivery and Okta calls. Webhook endpoint URLs and Okta hosts are also checked when saved.
  • LAGO_OUTBOUND_ALLOW_PRIVATE=true turns the checks off for local development. It has no effect in production.

Organization creation

  • createOrganization follows LAGO_DISABLE_SIGNUP: when signup is disabled, only users who are already admin of an organization can create one.
  • Each user can be admin of at most LAGO_MAX_ORGANIZATIONS_PER_USER organizations (default 10).

White-label

  • New white_label API key permission resource. provision and reset need write access and agreements needs read access. A migration grants it to existing keys that already have full access.
  • Gate links now carry a nonce that changes when an agreement is accepted or reset, so a link only works for the state it was issued for. Links issued before this change keep working only while the agreement is still pending.
  • After acceptance, signing_url is no longer returned. A repeat visit to the gate goes back to Stripe checkout only and never falls back to the customer portal.

Customer portal URL

  • return_to is kept only if its host is listed in LAGO_PORTAL_RETURN_TO_HOSTS (comma-separated, *.example.com supported). Otherwise it is dropped.

CI

  • build-and-push only runs for refs/heads/main in this repository.

New configuration

Variable Default Purpose
LAGO_PORTAL_RETURN_TO_HOSTS empty (drop all) Hosts allowed in portal return_to
LAGO_MAX_ORGANIZATIONS_PER_USER 10 Maximum number of organizations a user can administer when creating new ones
LAGO_OUTBOUND_ALLOW_PRIVATE unset Local development only; ignored in production

Migrations

  • AddWhiteLabelToFullAccessApiKeys (data, reversible)
  • AddTokenNonceToWhiteLabelAgreements (nullable column)

Tests

New and updated specs cover the plan query, the portal mutations, the outbound guard and client, webhook endpoints, Okta, organization creation, white-label agreements, the gate, the API permissions and portal URL generation.

rrader26-sys and others added 4 commits October 17, 2026 10:31
## Context

Several code paths accepted input more broadly than the UI or
configuration intended.

## Description

- Share the customer portal plan visibility rules between the available
  plans resolver and the portal subscription mutations, and escape LIKE
  wildcards in product key lookups.
- Resolve and check outbound destinations for webhook endpoints and
  Okta hosts, pin connections to the checked address, and validate on
  save and on send. LAGO_OUTBOUND_ALLOW_PRIVATE disables the checks
  outside production.
- Apply the signup setting and a per-user limit to organization creation.
- Add a white_label API key permission resource.
- Bind white-label gate links to a nonce rotated on acceptance and reset,
  and stop falling back to the customer portal on repeat visits.
- Restrict portal return_to to LAGO_PORTAL_RETURN_TO_HOSTS.
- Limit the image build workflow to the main branch.
Regenerated by running all migrations on a fresh database and dumping
with db:schema:dump (pg_dump 15).
Records created in around hooks were resolved before the before-hook
stub was installed.
@rrader26
rrader26 merged commit c2ba5eb into main Oct 17, 2026
2 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants