Skip to content

Swagger UI static assets are served in production, bypassing TINA4_SWAGGER_ENABLED (all 4 frameworks) #97

Description

@andrevanzuydam

Summary

All four frameworks ship the Swagger UI as STATIC assets in their bundled public
directory (swagger/index.html + swagger/oauth2-redirect.html). Static files are
resolved independently of the gated /swagger routes, so with swagger DISABLED
(TINA4_SWAGGER_ENABLED=false, or TINA4_DEBUG falsy, or tina4 serve --production)
the Swagger UI page is still served.

Affected: 3.13.82 and earlier (the assets date back to March).

Impact (deliberately not overstated)

The gated route /swagger/openapi.json IS correctly blocked (404), so the OpenAPI
spec is NOT disclosed. What leaks is the dev UI shell: a Swagger-branded page that
then fails to fetch its own spec. No API structure and no data disclosure. It is
still a dev surface reachable on a production deployment, and it defeats the
documented TINA4_SWAGGER_ENABLED production switch.

Reproduction

tina4 init python demo && cd demo
TINA4_OVERRIDE_CLIENT=true tina4 serve --production --no-browser
curl -o /dev/null -w '%{http_code}\n' http://127.0.0.1:7146/swagger/index.html

Measured before the fix (production, swagger disabled)

framework /swagger /swagger/ /swagger/index.html /swagger/oauth2-redirect.html
PHP 200 200 200 200
Node 200 200 200 200
Python 404 200 200 200
Ruby 404 404 200 200

Why it stayed hidden: a bare /swagger already 404s on Python (index resolution only
fires for an empty path or a trailing slash) and on Ruby (the gated handler
intercepts), so testing only the obvious URL looked clean. PHP and Node DO
index-resolve a bare /swagger.

Root cause

Static serving runs BEFORE route matching and applies directory-index resolution, so
public/swagger/index.html answers /swagger regardless of the swagger gate. The
gate logic itself was correct in every framework; it was simply never consulted on
the static path.

Fix

Gate the /swagger* static path on the same predicate that gates the routes, checked
BEFORE index resolution, failing closed when the swagger module cannot load:

  • python: tina4_python/core/server.py (_try_static)
  • ruby: lib/tina4/rack_app.rb (try_static)
  • php: Tina4/StaticFiles.php (tryServe)
  • node: packages/core/src/server.ts (boot-time flag set from swaggerEnabled())

After the fix all four return 404 on every URL above in production, and 200 on all of
them in dev, so the feature itself is unchanged.

Lock-in tests

One per framework, each including a guard that asserts the assets exist on disk (so
the negative test cannot pass vacuously), plus positive, negative, TINA4_DEBUG
fallback, explicit-flag-beats-debug, and a non-swagger control asset. Each was
verified to FAIL against the unfixed code (python 3, ruby 3, php 3, node 5 failures)
and pass with the fix.

Fixed in the working tree, pending release.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions