Commit Graph
5 Commits
Author SHA1 Message Date
officer fe67de0281 rewrite data-*url attributes, where Gitea hands URLs to its own JavaScript
The dashboard heatmap reads its endpoint from data-heatmap-url:
document.querySelector('#user-heatmap').getAttribute('data-heatmap-url').
Unprefixed it fetched from the PLATFORM's origin, got the SPA's index.html and
reported "Unexpected token '<'" while parsing JSON — the last error left after
everything visible was rendering correctly.

data-url carries the language switcher's links and had the same problem.

Matched on the `url` suffix rather than by listing attribute names, because the
list is Gitea's to change.

Found by reading Gitea's own bundle rather than by reasoning about what it
probably does — which is what the three previous attempts at this error were.
2026-08-19 09:38:08 +00:00
officer 413b7ac0e9 rewrite the JSON-escaped form of the upstream origin too
Gitea's inline script carries `appUrl: 'https:\/\/gitea.example.com\/'`, and
rewriteAbsolute only matched the literal `https://` form. So appUrl survived
pointing at the real host while every visible link was rewritten correctly.

The failure was quiet rather than obvious: the page rendered, and only the URLs
Gitea's JS BUILDS from appUrl went wrong — cross-origin to the real host,
answered with its login HTML. The heatmap reported "Unexpected token '<'" while
parsing JSON, and the event worker failed the same way.

Third instance of the same class today, after the asset paths and appSubUrl: a
URL hiding in a shape the rewriter did not think to look for. All three were
found by loading the page, none by the tests — and all three now have a test
written against a live sample rather than an imagined one.
2026-08-19 09:33:09 +00:00
officer 47e3983f7e rewrite asset paths baked into the page, including JSON-escaped ones
Gitea inlines a script naming its own worker:
'\/assets\/js\/eventsource.sharedworker.…js'. That is not an attribute and its
slashes are backslash-escaped, so neither the attribute rewrite nor appSubUrl
saw it. Unprefixed, the browser resolved it against the PLATFORM's origin, got
the SPA's index.html, and reported "Uncaught SyntaxError: Unexpected token '<'"
from a file that appeared to load — along with a dead SharedWorker and a
heatmap parsing HTML as JSON.

appSubUrl covers everything Gitea's code BUILDS at runtime. This covers what is
already in the page before that code runs.

Narrow on purpose, like dev-proxy's /_bun/ rule: a blanket rewrite of absolute
paths inside JavaScript corrupts strings that are not URLs. /assets/ is Gitea's
own asset root and specific enough to match safely.

The escaping has to match the string it lands in — \/api\/gitea\/_web, not
\\/api — or the JS stops parsing. Both forms are tested against a live sample.
2026-08-19 09:28:04 +00:00
officer 61d43cd5f6 match the appSubUrl shape Gitea actually emits
The regex expected JSON — "appSubUrl":"…" — and Gitea emits a JavaScript object
literal: unquoted key, single quotes, `appSubUrl: ''`. So the single
highest-value rewrite in this file silently did nothing on every real page,
while every attribute around it rewrote perfectly.

The test used the shape I guessed rather than the shape Gitea produces, which
is why it passed. Now covers both, with the real form taken verbatim from a
live response, and asserts the quote style survives — swapping it would stop
the literal parsing.
2026-08-19 09:14:45 +00:00
officer 00243b312a rewrite Gitea's URLs so the frame stays inside the proxy
Gitea believes it lives at /. Its pages are full of root-absolute links, and
inside the frame those resolve against OFFICER'S origin — the page renders
perfectly and falls apart on the first click.

Four places a URL hides, each failing differently: href/src/action attributes,
window.config.appSubUrl, Location headers, and absolute URLs built from
ROOT_URL.

appSubUrl is the highest-value line. Gitea already supports being hosted under
a subpath and its client JS prefixes every URL it builds with that value, so
filling it in makes Gitea correct ITSELF rather than by interception.

The ROOT_URL case needs Gitea's public origin, which the sidecar does not have
— it fetches from loopback. Rather than add a second setting that can disagree
with Gitea's own, appUrlOrigin reads it out of the page: Gitea emits "appUrl"
in the same config blob as appSubUrl.

Not rewritten, deliberately: JavaScript bodies, because a blanket rewrite of
absolute paths inside a script corrupts strings that are not URLs; and clone
URLs, which must keep working for whoever copies them.

/_web/* now forwards by hand rather than through createSidecarProxy. That
forwarder passes three headers and nothing else, on purpose — it is shared by
every sidecar and its restraint is what makes it safe to reuse. It strips
cookies, and this path is nothing but cookies. Loosening it for gitea would
loosen it for the wallet.

Eighteen tests, the only part of this plugin verifiable without a running
Gitea. One caught a real bug: rewriteAbsolute turns a full URL into <base>/path
and the attribute pass then prefixed it again.
2026-08-19 08:31:28 +00:00