Clone URL rewritten to browsing hostname by client-side JS, ignoring ROOT_URL setting #12419

Open
opened 2026-05-05 03:19:04 +02:00 by theoryshaw · 4 comments
theoryshaw commented 2026-05-05 03:19:04 +02:00 (Migrated from codeberg.org)

Does your problem still exist on the latest Forgejo version?

Unknown, I can't try it for some reason (please explain)

About your usage of Forgejo

Small architecture firm using Forgejo as a self-hosted git forge for open-source building design projects. We host large binary files (BIM models, PDFs, blender files) via Git LFS, some exceeding 100MB. We have a mix of technical and non-technical collaborators.

Problem description

We run Forgejo behind two domains pointing to the same instance:

  • hub.openingdesign.com — proxied through Cloudflare (web UI)
  • git.openingdesign.com — DNS only, bypasses Cloudflare (needed for large LFS file transfers >100MB which exceed Cloudflare's request body limit)

We set ROOT_URL = https://git.openingdesign.com/ in app.ini so that clone URLs generated by Forgejo point to git.openingdesign.com, bypassing Cloudflare for git operations.

This works correctly — the raw HTML served by Forgejo contains the correct git.openingdesign.com clone URL. However, when a user browses via hub.openingdesign.com, Forgejo's client-side JavaScript rewrites the clone URL input field to match the current browsing hostname, overriding the ROOT_URL setting. The result is that users see hub.openingdesign.com in the clone URL box, which fails for large LFS pushes/pulls due to Cloudflare's 100MB limit.

test url: https://hub.openingdesign.com/OpeningDesign/3_Season_Porch_URGC

it renders out as: https://hub.openingdesign.com/OpeningDesign/3_Season_Porch_URGC.git
the raw link is: https://git.openingdesign.com/OpeningDesign/3_Season_Porch_URGC.git

20260504_2010

Potential workarounds

  • Redirecting hub.openingdesign.comgit.openingdesign.com via Caddy, but this loses Cloudflare bot protection on the web UI
  • Manually updating each local repo's remote URL — works but not discoverable for new users

Forgejo Version

Forgejo version 8.0.3+gitea-1.22.0 (release name 8.0.3) built with GNU Make 4.4.1, go1.22.7 : bindata, timetzdata, sqlite, sqlite_unlock_notify

Other details about your environment (software names and versions)

  • Ubuntu 22.04
  • Caddy reverse proxy
  • MariaDB
  • AWS EC2 t4g.medium
  • Cloudflare Pro plan

Solutions

Accepted solutions to address this problem will go here

### Does your problem still exist on the latest Forgejo version? Unknown, I can't try it for some reason (please explain) ### About your usage of Forgejo Small architecture firm using Forgejo as a self-hosted git forge for open-source building design projects. We host large binary files (BIM models, PDFs, blender files) via Git LFS, some exceeding 100MB. We have a mix of technical and non-technical collaborators. ### Problem description We run Forgejo behind two domains pointing to the same instance: - `hub.openingdesign.com` — proxied through Cloudflare (web UI) - `git.openingdesign.com` — DNS only, bypasses Cloudflare (needed for large LFS file transfers >100MB which exceed Cloudflare's request body limit) We set `ROOT_URL = https://git.openingdesign.com/` in `app.ini` so that clone URLs generated by Forgejo point to `git.openingdesign.com`, bypassing Cloudflare for git operations. This works correctly — the raw HTML served by Forgejo contains the correct `git.openingdesign.com` clone URL. However, when a user browses via `hub.openingdesign.com`, Forgejo's client-side JavaScript rewrites the clone URL input field to match the current browsing hostname, overriding the `ROOT_URL` setting. The result is that users see `hub.openingdesign.com` in the clone URL box, which fails for large LFS pushes/pulls due to Cloudflare's 100MB limit. test url: https://hub.openingdesign.com/OpeningDesign/3_Season_Porch_URGC it renders out as: https://hub.openingdesign.com/OpeningDesign/3_Season_Porch_URGC.git the raw link is: https://git.openingdesign.com/OpeningDesign/3_Season_Porch_URGC.git ![20260504_2010](/attachments/1847f553-0eeb-4468-8285-214ab70174c0) ### Potential workarounds - Redirecting `hub.openingdesign.com` → `git.openingdesign.com` via Caddy, but this loses Cloudflare bot protection on the web UI - Manually updating each local repo's remote URL — works but not discoverable for new users ### Forgejo Version Forgejo version 8.0.3+gitea-1.22.0 (release name 8.0.3) built with GNU Make 4.4.1, go1.22.7 : bindata, timetzdata, sqlite, sqlite_unlock_notify ### Other details about your environment (software names and versions) - Ubuntu 22.04 - Caddy reverse proxy - MariaDB - AWS EC2 t4g.medium - Cloudflare Pro plan ### Solutions *Accepted solutions to address this problem will go here*
mfenniak commented 2026-05-05 03:47:56 +02:00 (Migrated from codeberg.org)

Hi @theoryshaw. Unfortunately I can't offer you an easy solution -- from what I can tell, the client-side rewriting of the URL is automatic and there's no current option to disable it. Unless someone else has awareness of a configuration option that I don't, development work would be required to support this.

I have to warn you that Forgejo 8.0 is an end-of-life release which is missing critical security patches, with support having ended in October 2024. Even if this was fixed, you're going to need to manage upgrading 7 major software releases before a new feature or fix would be available to you.

Hi @theoryshaw. Unfortunately I can't offer you an easy solution -- from what I can tell, the client-side rewriting of the URL is automatic and there's no current option to disable it. Unless someone else has awareness of a configuration option that I don't, development work would be required to support this. I have to warn you that Forgejo 8.0 is an end-of-life release which is missing critical security patches, with support having ended in October 2024. Even if this was fixed, you're going to need to manage upgrading 7 major software releases before a new feature or fix would be available to you.
theoryshaw commented 2026-05-05 04:36:00 +02:00 (Migrated from codeberg.org)
AI helped with a possible solution...

Suggested Fix

Problem Summary

toOriginUrl() in templates/repo/clone_script.tmpl unconditionally rewrites all clone URLs to use the browser's current hostname (window.location.hostname), ignoring the ROOT_URL set in app.ini. This breaks setups where the administrator intentionally wants clone URLs to point to a different host than the one being browsed.

Use Case

A common deployment scenario:

  • hub.example.com — proxied through Cloudflare (web UI, bot protection)
  • git.example.com — DNS only, bypasses Cloudflare (git/LFS operations)

ROOT_URL = https://git.example.com/ is set in app.ini so that clone URLs bypass Cloudflare's 100MB request body limit for large LFS file transfers. The raw HTML correctly reflects this — data-link="https://git.example.com/user/repo.git" — but toOriginUrl() overwrites it with hub.example.com when the user browses via that domain.

Current Behavior

js

// clone_script.tmpl:28-40
function toOriginUrl(urlStr) {
    const {origin, protocol, hostname, port} = window.location;
    const url = new URL(urlStr, origin);
    url.protocol = protocol;
    url.hostname = hostname;  // unconditionally overwrites with browsing hostname
    url.port     = port || (protocol === 'https:' ? '443' : '80');
    return url.toString();
}

This runs unconditionally for all absolute URLs, including ones where the server has explicitly set a different host via ROOT_URL.

Only rewrite relative URLs. If the URL is already absolute (has an explicit host), respect it and return it unchanged:

js

function toOriginUrl(urlStr) {
    // If the URL is relative, rewrite to current origin as before
    if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) {
        const {origin} = window.location;
        return new URL(urlStr, origin).toString();
    }
    // If the URL is absolute, respect the explicit host (e.g. ROOT_URL)
    // and do not rewrite to the browsing hostname
    return urlStr;
}

This preserves the original intent of the function (handling relative URLs and avoiding flicker) while respecting explicitly configured absolute URLs from ROOT_URL.

Proposed Fix — Option 2 (Opt-out attribute)

Add a data-no-origin-rewrite attribute on the server side that toOriginUrl() checks before rewriting. This gives administrators explicit control:

Server side (templates/repo/clone_script.tmpl):

html

<button id="repo-clone-https" 
    data-link="{{.CloneLink.HTTPS}}"
    data-no-origin-rewrite="{{if ne .CloneLink.HTTPS .AppUrl}}true{{end}}">

Client side:

js

function toOriginUrl(urlStr, noRewrite) {
    if (noRewrite === 'true') return urlStr;
    const {origin, protocol, hostname, port} = window.location;
    const url = new URL(urlStr, origin);
    url.protocol = protocol;
    url.hostname = hostname;
    url.port     = port || (protocol === 'https:' ? '443' : '80');
    return url.toString();
}

// Usage:
const link = toOriginUrl(
    btn.getAttribute('data-link'),
    btn.getAttribute('data-no-origin-rewrite')
);

Proposed Fix — Option 3 (Simplest)

Remove toOriginUrl() entirely and use data-link directly:

js

// Before:
const link = toOriginUrl(btn.getAttribute('data-link'));

// After:
const link = btn.getAttribute('data-link');

This is the simplest fix but removes the multi-hostname auto-adaptation feature entirely. Only appropriate if that feature is considered an edge case.

Recommendation

Option 1 is the least disruptive — it preserves the existing multi-hostname behavior for relative URLs and Forgejo instances that don't set an explicit ROOT_URL host, while respecting explicitly configured absolute URLs. It also requires changes to only one function in one file, and the same change would need to be applied to web_src/js/webcomponents/origin-url.js where toOriginUrl is duplicated (the comment there already notes "Keep this function in sync").

Files to Change

  1. templates/repo/clone_script.tmpltoOriginUrl() function
  2. web_src/js/webcomponents/origin-url.js — duplicate toOriginUrl() (keep in sync per existing comment)
<details> <summary>AI helped with a possible solution...</summary> ### Suggested Fix #### Problem Summary `toOriginUrl()` in `templates/repo/clone_script.tmpl` unconditionally rewrites all clone URLs to use the browser's current hostname (`window.location.hostname`), ignoring the `ROOT_URL` set in `app.ini`. This breaks setups where the administrator intentionally wants clone URLs to point to a different host than the one being browsed. #### Use Case A common deployment scenario: - `hub.example.com` — proxied through Cloudflare (web UI, bot protection) - `git.example.com` — DNS only, bypasses Cloudflare (git/LFS operations) `ROOT_URL = https://git.example.com/` is set in `app.ini` so that clone URLs bypass Cloudflare's 100MB request body limit for large LFS file transfers. The raw HTML correctly reflects this — `data-link="https://git.example.com/user/repo.git"` — but `toOriginUrl()` overwrites it with `hub.example.com` when the user browses via that domain. #### Current Behavior js ```js // clone_script.tmpl:28-40 function toOriginUrl(urlStr) { const {origin, protocol, hostname, port} = window.location; const url = new URL(urlStr, origin); url.protocol = protocol; url.hostname = hostname; // unconditionally overwrites with browsing hostname url.port = port || (protocol === 'https:' ? '443' : '80'); return url.toString(); } ``` This runs unconditionally for **all** absolute URLs, including ones where the server has explicitly set a different host via `ROOT_URL`. #### Proposed Fix — Option 1 (Recommended) Only rewrite **relative URLs**. If the URL is already absolute (has an explicit host), respect it and return it unchanged: js ```js function toOriginUrl(urlStr) { // If the URL is relative, rewrite to current origin as before if (!urlStr.startsWith('http://') && !urlStr.startsWith('https://')) { const {origin} = window.location; return new URL(urlStr, origin).toString(); } // If the URL is absolute, respect the explicit host (e.g. ROOT_URL) // and do not rewrite to the browsing hostname return urlStr; } ``` This preserves the original intent of the function (handling relative URLs and avoiding flicker) while respecting explicitly configured absolute URLs from `ROOT_URL`. #### Proposed Fix — Option 2 (Opt-out attribute) Add a `data-no-origin-rewrite` attribute on the server side that `toOriginUrl()` checks before rewriting. This gives administrators explicit control: **Server side** (`templates/repo/clone_script.tmpl`): html ```html <button id="repo-clone-https" data-link="{{.CloneLink.HTTPS}}" data-no-origin-rewrite="{{if ne .CloneLink.HTTPS .AppUrl}}true{{end}}"> ``` **Client side:** js ```js function toOriginUrl(urlStr, noRewrite) { if (noRewrite === 'true') return urlStr; const {origin, protocol, hostname, port} = window.location; const url = new URL(urlStr, origin); url.protocol = protocol; url.hostname = hostname; url.port = port || (protocol === 'https:' ? '443' : '80'); return url.toString(); } // Usage: const link = toOriginUrl( btn.getAttribute('data-link'), btn.getAttribute('data-no-origin-rewrite') ); ``` #### Proposed Fix — Option 3 (Simplest) Remove `toOriginUrl()` entirely and use `data-link` directly: js ```js // Before: const link = toOriginUrl(btn.getAttribute('data-link')); // After: const link = btn.getAttribute('data-link'); ``` This is the simplest fix but removes the multi-hostname auto-adaptation feature entirely. Only appropriate if that feature is considered an edge case. #### Recommendation **Option 1** is the least disruptive — it preserves the existing multi-hostname behavior for relative URLs and Forgejo instances that don't set an explicit `ROOT_URL` host, while respecting explicitly configured absolute URLs. It also requires changes to only one function in one file, and the same change would need to be applied to `web_src/js/webcomponents/origin-url.js` where `toOriginUrl` is duplicated (the comment there already notes _"Keep this function in sync"_). #### Files to Change 1. `templates/repo/clone_script.tmpl` — `toOriginUrl()` function 2. `web_src/js/webcomponents/origin-url.js` — duplicate `toOriginUrl()` (keep in sync per existing comment) </details>
Gusted commented 2026-05-06 11:30:02 +02:00 (Migrated from codeberg.org)

There's no configuration to change this, because this was the behavior highly requested from people running a setup somewhat similar to yours. The domain always point to the domain from where the request originate from to make it possible to run Forgejo under multiple domains. I think adding a setting for this would add quite some complexity after quite a lot effort (over the years) went into not relying on the configured domain when possible.

There's no configuration to change this, because this was the behavior highly requested from people running a setup somewhat similar to yours. The domain _always_ point to the domain from where the request originate from to make it possible to run Forgejo under multiple domains. I think adding a setting for this would add quite some complexity after quite a lot effort (over the years) went into not relying on the configured domain when possible.
theoryshaw commented 2026-05-06 17:01:30 +02:00 (Migrated from codeberg.org)

Thanks for the context @Gusted. I understand the multi-domain auto-adaptation is intentional and valued.

To clarify, the proposed Option 1 fix (in AI's possible solution pasted above) wouldn't change that behavior at all. It would only affect cases where ROOT_URL explicitly points to a different host than the one being browsed. When ROOT_URL and the browsing hostname match (the common case), toOriginUrl() would behave exactly as it does today.

The admin setting ROOT_URL = https://git.example.com/ while serving the UI at hub.example.com is an explicit signal that they want git operations routed differently. Respecting that seems consistent with Forgejo's philosophy of giving admins control over their deployment.

Would a small, non-breaking change like this be considered?

Thanks for the context @Gusted. I understand the multi-domain auto-adaptation is intentional and valued. To clarify, the proposed Option 1 fix _(in AI's possible solution pasted above)_ wouldn't change that behavior at all. It would only affect cases where `ROOT_URL` explicitly points to a **different** host than the one being browsed. When `ROOT_URL` and the browsing hostname match (the common case), `toOriginUrl()` would behave exactly as it does today. The admin setting `ROOT_URL = https://git.example.com/` while serving the UI at `hub.example.com` is an explicit signal that they want git operations routed differently. Respecting that seems consistent with Forgejo's philosophy of giving admins control over their deployment. Would a small, non-breaking change like this be considered?
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sleepy/forgejo#12419
No description provided.