Related problem
When Invio is accessed over a hostname different from the configured ORIGIN, login and other form actions fail with:
{"message":"Cross-site POST form submissions are forbidden"}
This affects self-hosted setups where users reach the same instance through more than one URL, for example:
- Local:
http://umbrel.local:8003
- Remote via Tailscale MagicDNS:
http://umbrel-home.<tailnet>.ts.net:8003
- LAN IP:
http://192.168.x.x:8003
The login page loads fine, but the POST to the SvelteKit form action (/login?/login) is rejected by CSRF origin validation.
On Umbrel, the app package currently sets a single canonical origin:
ORIGIN: "http://${DEVICE_DOMAIN_NAME}:8003"
That works for .local access but not when the browser sends a different Origin header (e.g. over Tailscale). This makes remote access unusable for anyone who relies on form-based login.
Proposed solution
Add optional, runtime-configurable support for multiple trusted origins on form submissions, while keeping CSRF protection enabled.
Suggested approach:
- Introduce a
TRUSTED_ORIGINS environment variable (comma-separated full origins, including scheme and port).
- Keep
ORIGIN as the primary/canonical origin for backward compatibility.
- Allow glob patterns where useful (e.g.
http://*.ts.net:8003) and document a trusted-network-only * option.
- Preserve existing behavior when
TRUSTED_ORIGINS is unset: only ORIGIN is allowed.
- Skip the check in development (matching SvelteKit's current production-only CSRF behavior).
- Fall back to the request-derived origin when neither
ORIGIN nor TRUSTED_ORIGINS is set.
Example Umbrel frontend env (for the separate umbrel-apps package):
ORIGIN: "http://${DEVICE_DOMAIN_NAME}:8003"
TRUSTED_ORIGINS: "http://${DEVICE_DOMAIN_NAME}:8003,http://${DEVICE_HOSTNAME}:8003,http://*.ts.net:8003"
This follows the same general pattern as other Umbrel apps (e.g. gitingest's ALLOWED_HOSTS) and keeps the change opt-in: existing installs with only ORIGIN should not need config changes.
Additional context
- Root cause: SvelteKit CSRF compares the browser
Origin header to a single server-side origin. With ORIGIN pinned to .local, Tailscale hostnames are treated as cross-site.
- The backend is not involved; the failure happens in the SvelteKit frontend before the login action reaches the API.
- Related Umbrel app config: https://github.com/getumbrel/umbrel-apps/blob/master/invio/docker-compose.yml
- Umbrel packaging guidance recommends keeping trusted origins narrow rather than disabling CSRF entirely.
Benefits for the community
- Enables remote access for homelab and Umbrel users without weakening CSRF for the default
.local URL.
- Keeps backward compatibility for Docker and Umbrel installs that only set
ORIGIN.
- Aligns Invio with common self-hosted access patterns (local hostname, Tailscale, LAN IP) without requiring users to pick a single canonical URL.
Related problem
When Invio is accessed over a hostname different from the configured
ORIGIN, login and other form actions fail with:{"message":"Cross-site POST form submissions are forbidden"}This affects self-hosted setups where users reach the same instance through more than one URL, for example:
http://umbrel.local:8003http://umbrel-home.<tailnet>.ts.net:8003http://192.168.x.x:8003The login page loads fine, but the POST to the SvelteKit form action (
/login?/login) is rejected by CSRF origin validation.On Umbrel, the app package currently sets a single canonical origin:
That works for
.localaccess but not when the browser sends a differentOriginheader (e.g. over Tailscale). This makes remote access unusable for anyone who relies on form-based login.Proposed solution
Add optional, runtime-configurable support for multiple trusted origins on form submissions, while keeping CSRF protection enabled.
Suggested approach:
TRUSTED_ORIGINSenvironment variable (comma-separated full origins, including scheme and port).ORIGINas the primary/canonical origin for backward compatibility.http://*.ts.net:8003) and document a trusted-network-only*option.TRUSTED_ORIGINSis unset: onlyORIGINis allowed.ORIGINnorTRUSTED_ORIGINSis set.Example Umbrel frontend env (for the separate
umbrel-appspackage):This follows the same general pattern as other Umbrel apps (e.g. gitingest's
ALLOWED_HOSTS) and keeps the change opt-in: existing installs with onlyORIGINshould not need config changes.Additional context
Originheader to a single server-side origin. WithORIGINpinned to.local, Tailscale hostnames are treated as cross-site.Benefits for the community
.localURL.ORIGIN.