Skip to content

🐛(y-provider) send X-Forwarded-Proto on internal backend calls - #2542

Open
lucletoffe wants to merge 1 commit into
suitenumerique:mainfrom
lucletoffe:fix/y-provider-forwarded-proto
Open

🐛(y-provider) send X-Forwarded-Proto on internal backend calls#2542
lucletoffe wants to merge 1 commit into
suitenumerique:mainfrom
lucletoffe:fix/y-provider-forwarded-proto

Conversation

@lucletoffe

@lucletoffe lucletoffe commented Jul 27, 2026

Copy link
Copy Markdown

Purpose

Fixes #2541.

On a self-hosted deployment where COLLABORATION_BACKEND_BASE_URL points at the backend directly (internal docker network, e.g. http://docs-backend:8000), collaboration can never connect. Every onConnect fails with [onConnect] Backend error: Unauthorized, whatever the credentials.

The cause is not authentication, it is SECURE_SSL_REDIRECT:

  1. Production.SECURE_SSL_REDIRECT = True is hardcoded in src/backend/impress/settings.py and cannot be driven by an environment variable.
  2. Django only learns that TLS was terminated in front of it through SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https").
  3. The y-provider call to the backend is internal: it does not cross the reverse proxy, so nothing sets that header. Django answers 301 to https://<host>:8000, and the client follows it — speaking TLS to a port that serves plain HTTP (wrong version number).

Reproduced from inside the y-provider container (v5.4.1):

$ wget -S --spider http://docs-backend:8000/api/v1.0/config/
  HTTP/1.1 301 Moved Permanently
  Location: https://docs-backend:8000/api/v1.0/config/
  error:0A00010B:SSL routines:tls_validate_record_header:wrong version number

$ wget -S --spider --header="X-Forwarded-Proto: https" http://docs-backend:8000/api/v1.0/config/
  HTTP/1.1 200 OK

The backend log showed 46 × 301 Moved Permanently on /api/v1.0/documents/<id>/ within the last 200 lines, all from the y-provider container IP.

Proposal

  • collaborationBackend.ts always sends X-Forwarded-Proto: https, next to the cookie and origin headers it already relays.

    https is hardcoded rather than relayed from the incoming request. An ingress configured with use-forwarded-headers: true — required as soon as a load-balancer sits upstream — passes the client's own header through unchanged. A client that sends X-Forwarded-Proto: http on its WebSocket upgrade would therefore cause Django to answer a 301, axios would follow the redirect to a plain-HTTP port, and the connection would break. That is a vector this fix has no reason to introduce. Since SECURE_PROXY_SSL_HEADER is only declared in the Production class where SECURE_SSL_REDIRECT = True already makes a plain-HTTP deployment unreachable, https is the safe default given the current Production settings.

  • Test suite updated: the two cases that tested relay behaviour are removed; one test checks that X-Forwarded-Proto: https is always sent to the backend even when the incoming request carries X-Forwarded-Proto: http.

  • Changelog entry.

Ran locally on this branch: yarn COLLABORATION_SERVER run test (6 files, 56 tests passing), run lint, run build.

Why not fix this on the backend instead?

A maintainer might reasonably suggest extending SECURE_REDIRECT_EXEMPT (src/backend/impress/settings.py) with the internal API prefixes called by the y-provider, rather than touching the y-provider itself. That approach is legitimate and would fix the problem at its source in Python.

The trade-off is maintenance surface: every internal path that could be called before the reverse proxy sets the header would need to be listed and kept in sync as the API evolves. The y-provider fix applies unconditionally for any topology where COLLABORATION_BACKEND_BASE_URL points at the backend directly.

It is worth noting that the topology recommended by the official Helm chart (documentation/examples/helm/impress.values.yaml, where COLLABORATION_BACKEND_BASE_URL is set to the public HTTPS URL) does not trigger this bug at all — the call goes through the ingress, which sets the header correctly. Routing an in-cluster call out through the public ingress and back in adds unnecessary latency and a dependency on external DNS resolution from inside the cluster; the direct-backend URL is the natural choice for in-cluster deployments, and this fix makes it work without requiring any backend change.

Alternative considered

Making SECURE_SSL_REDIRECT configurable through an environment variable would also work, but it weakens a security default for every deployment in order to fix an internal call. Sending https unconditionally keeps the production posture untouched.

External contributions

General requirements

CI requirements

  • I made sure that all existing tests are passing
  • I have signed off my commits with git commit --signoff (DCO compliance)
  • I have signed my commits with my SSH or GPG key (git commit -S)
  • My commit messages follow the required format: <gitmoji>(type) title description
  • I have added a changelog entry under ## [Unreleased] section (if noticeable change)

AI requirements

  • I used AI assistance to produce part or all of this contribution
  • I have read, reviewed, understood and can explain the code I am submitting
  • I can jump in a call or a chat to explain my work to a maintainer

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7c3ef9c2-e1fb-4a01-a220-3c27b87bb005

📥 Commits

Reviewing files that changed from the base of the PR and between 1032886 and b1affa7.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/frontend/servers/y-provider/__tests__/collaborationBackend.test.ts
  • src/frontend/servers/y-provider/src/api/collaborationBackend.ts

Walkthrough

The y-provider’s internal backend requests now always include X-Forwarded-Proto: https. Documentation explains the reverse-proxy behavior, a regression test verifies incoming http is overridden, and the changelog records the fix.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: antolc

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: sending X-Forwarded-Proto on internal y-provider backend calls.
Description check ✅ Passed The description is directly about the same collaboration redirect bug and the fix applied in this PR.
Linked Issues check ✅ Passed The PR satisfies #2541 by forcing X-Forwarded-Proto: https on backend requests and adding coverage for it.
Out of Scope Changes check ✅ Passed The changes stay within scope: code fix, test update, and changelog entry for the reported collaboration issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/frontend/servers/y-provider/src/api/collaborationBackend.ts`:
- Line 74: Validate the X-Forwarded-Proto value used in the requestHeaders
mapping before forwarding it to Django. In the collaboration backend request
construction, only preserve the inbound value when it is exactly “http” or
“https”; otherwise use the existing “https” fallback, preventing arbitrary
protocol values from reaching Django.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 65139690-e0aa-4d4b-be1b-38c1e54c62e4

📥 Commits

Reviewing files that changed from the base of the PR and between 61c2183 and 44f7db0.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/frontend/servers/y-provider/__tests__/collaborationBackend.test.ts
  • src/frontend/servers/y-provider/src/api/collaborationBackend.ts

Comment thread src/frontend/servers/y-provider/src/api/collaborationBackend.ts Outdated
@lucletoffe

Copy link
Copy Markdown
Author

Good catch, fixed in e6f7692: the value is now restricted to http or https before being forwarded, anything else (including a chained https, http) falls back to https. Added a test for it — 58 tests passing in the y-provider workspace.

@lucletoffe
lucletoffe force-pushed the fix/y-provider-forwarded-proto branch from e6f7692 to 70c9249 Compare July 28, 2026 06:27
@lucletoffe

Copy link
Copy Markdown
Author

Correction to my earlier comment — the branch was squashed into a single commit after I wrote it, and the approach changed with it.

e6f76923 is no longer part of this branch, and the value is no longer relayed at all: X-Forwarded-Proto: https is now hardcoded (70c9249).

Why the change. An ingress configured with use-forwarded-headers: true — required as soon as a load balancer sits upstream — passes the client's own X-Forwarded-Proto through unchanged. A client sending X-Forwarded-Proto: http on its WebSocket upgrade would then make Django answer a 301, axios would follow it to a plain-HTTP port, and the connection would break. Validating the relayed value narrows that down but still leaves the client in control of it, and this fix has no reason to introduce that vector at all.

Since SECURE_PROXY_SSL_HEADER is only declared in the Production class, where SECURE_SSL_REDIRECT is enabled too, https is the one value that is always correct on this internal call.

Apologies for the noise — the earlier comment describes code that is no longer in the PR.

The backend hardcodes SECURE_SSL_REDIRECT in its Production settings class
and relies on SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
to know that a TLS termination sits in front of it.

When COLLABORATION_BACKEND_BASE_URL points at the backend directly, for
instance over an internal docker network, the y-provider call never crosses
the reverse proxy, so nothing sets that header. Django answers a 301 to
https://<host>:8000 and the client then speaks TLS to a port serving plain
HTTP. Every onConnect fails with "[onConnect] Backend error: Unauthorized"
and collaborative editing never starts, whatever the credentials.

Measured on a self-hosted 5.4.1 deployment, from inside the y-provider
container:

  wget http://docs-backend:8000/api/v1.0/config/
    -> 301 Location: https://docs-backend:8000/...
    -> SSL routines:tls_validate_record_header:wrong version number
  the same call with "X-Forwarded-Proto: https" -> 200 OK

Rather than relaying the client's own X-Forwarded-Proto, always send https.
An ingress configured with use-forwarded-headers: true (required when a
load-balancer sits upstream) passes the client's header through unchanged. A
client that sends X-Forwarded-Proto: http on its WebSocket upgrade would then
cause Django to answer a 301, axios would follow the redirect to a plain-HTTP
port, and the connection would break — a vector this fix has no reason to
introduce. Since SECURE_SSL_REDIRECT is enabled in the Production class,
https is the safe default given the current Production settings.

Fixes suitenumerique#2541

Signed-off-by: lucletoffe <15689941+lucletoffe@users.noreply.github.com>
@lucletoffe
lucletoffe force-pushed the fix/y-provider-forwarded-proto branch from 1032886 to b1affa7 Compare July 29, 2026 13:40
@lucletoffe

Copy link
Copy Markdown
Author

lint-git was failing on the commit title only — 76 characters against gitlint's 72 limit. Force-pushed b1affa7b: title shortened to 🐛(y-provider) always send X-Forwarded-Proto: https to the backend (65), and the branch rebased onto eed828d8 so it is no longer behind main. The diff is unchanged.

The workflow runs are waiting on maintainer approval (action_required) — nothing else to do on my side.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Collaboration never connects when y-provider reaches the backend directly: SECURE_SSL_REDIRECT 301s the internal call

2 participants