Skip to content

fix(swagger): point the bundled UI at a document that exists - #129

Merged
andrevanzuydam merged 1 commit into
tina4stack:v3from
MichaelC8E:fix/swagger-ui-asset-asks-for-a-url-that-never-existed
Sep 9, 2026
Merged

andrevanzuydam merged 1 commit into
tina4stack:v3from
MichaelC8E:fix/swagger-ui-asset-asks-for-a-url-that-never-existed

Conversation

@MichaelC8E

Copy link
Copy Markdown
Contributor

The defect

The bundled Swagger UI page asks the browser for {SWAGGER_ROUTE}/swagger.json. That placeholder
is never substituted — nothing in the static path templates the file, it is served
byte-for-byte off disk — so the browser requests the literal string, which 404s. The page loads,
renders the Swagger chrome, and then shows an error where the API should be.

swagger.json is not a route in this port, or in any of the others. The document is served at
/swagger/openapi.json.

Where it is reachable

core/server.py:1704 already claims both canonical forms inline ("/swagger", "/swagger/"), so
those two serve the real UI. The bundled asset is what answers:

  • GET /swagger//
  • GET /swagger/index.html

Both 200, both with a UI that can never load. With swagger disabled all forms 404
(core/server.py:3220), so there is no production exposure — only a UI that cannot work where one
is advertised.

The fix

One string in tina4_python/public/swagger/index.html:

-            url: "{SWAGGER_ROUTE}/swagger.json",
+            url: "/swagger/openapi.json",

A literal is the right shape here precisely because this file is not templated. An absolute path is
also the only correct form: a relative URL resolves differently from /swagger than from
/swagger/.

The regression test

It went into tests/test_swagger_static_gate.py, the file that already owns this surface. That
file was written for an earlier leak on these same assets and stops at status codes — which is
exactly why it never noticed this.

The new test asserts the property, not the paths: for every path form that answers with a UI
page, read the url: out of the HTML that was actually served and fetch it. Any page naming a
document that does not resolve fails, including forms nobody has thought of yet.

7 passed

With the one string reverted and the test kept:

1 failed, 6 passed
AssertionError: the page at /swagger// asks for '{SWAGGER_ROUTE}/swagger.json',
which answered 404 -- that UI can never load

Verification

Full suite against pristine v3 a380345 in the same tree: base 5247 passed / 5 failed / 664
skipped
, with this branch 5248 / 5 / 664. The five failures are identical by name either
way — the skill installer, a firebird connect timeout, and three port-takeover contract tests, all
needing services the test machine does not run. The +1 is this branch's own test.

Run under uv sync --extra test, as CI does (.github/workflows/test.yml:266) — without the
extra, tests/test_swagger_contract.py cannot import openapi-spec-validator, which is declared
TEST-ONLY in the test extra at pyproject.toml:99.

Not python-only

The same unsubstituted file ships in tina4-nodejs and tina4-ruby; both have their own pull
requests. tina4-php ships an equivalent page asking for {{baseURL}}/swagger/json.json, a token
that likewise occurs exactly once in that tree; that one has not been reproduced and has no fix yet.

The bundled Swagger UI page asks for `{SWAGGER_ROUTE}/swagger.json`. That
placeholder is never substituted -- nothing in the static path templates the
file, it is served byte-for-byte off disk -- so the browser requests the
literal string, which 404s. The page loads, renders the Swagger chrome, and
then shows an error where the API should be.

The document is served at /swagger/openapi.json, so name it directly. A
literal is the right shape here precisely because this file is not templated.

This is reachable on every path that serves the bundled asset rather than a
route: /swagger//, /swagger/index.html, and -- when the swagger routes are not
registered -- /swagger itself.

The regression asserts the property rather than the paths: for every path form
that answers with a UI page, read the `url:` out of the HTML that was actually
served and fetch it. Any page naming a document that does not resolve fails,
including forms nobody has thought of yet.
@andrevanzuydam
andrevanzuydam merged commit d3650fc into tina4stack:v3 Sep 9, 2026
4 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.

2 participants