mirror of
https://github.com/unslothai/unsloth.git
synced 2026-08-25 08:42:25 +00:00
* Studio: serve Swagger UI and ReDoc from this origin, not a CDN FastAPI's built-in /docs and /redoc load ~2.3 MB of JavaScript from cdn.jsdelivr.net and start Swagger with an inline script, so #8042 had to add 'unsafe-inline' and that CDN to script-src for those paths. Those pages share an origin with the frontend, and localStorage is origin-scoped rather than path-scoped, so anything executing there can read the access and refresh tokens session.ts stores and call the API as that user. Vendor the pinned bundles under backend/assets/docs_ui and re-register both pages on FastAPI's own paths against them. Swagger's inline init runs off the per-response nonce main.py already plumbs for the bootstrap script, so script-src stays 'self'. The docs CSP branch now only relaxes style-src, font-src and worker-src, none of which execute script. The pages look and behave exactly as before, and now work with no network. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Match the Swagger init tag by what follows it, not by surrounding whitespace fastapi is unpinned, so a release that reflows the docs template or drops the comment above the init script would miss an exact-string marker and 500 the page. Anchor on the SwaggerUIBundle call instead. * Docs pages: honor root_path, and ship Swagger UI's NOTICE FastAPI's built-in docs routes prefix every URL they emit with the ASGI root_path; the replacements did not, so behind a path-stripping proxy the schema, bundles and OAuth redirect escaped the mapping. Apache-2.0 section 4(d) also requires the NOTICE file to travel with a redistributed Swagger UI, along with the extracted third-party banners its bundle names. * Keep the vendored docs bundles out of git's CRLF conversion The Windows cross-platform run failed the digest check on every text file in assets/docs_ui (the PNG was fine), which is core.autocrlf=true rewriting them on checkout. That both breaks the manifest and means a Windows install serves bytes that are not the reviewed release. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
77 lines
3.2 KiB
Python
77 lines
3.2 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""The vendored Swagger UI and ReDoc bundles stay byte-identical to the releases they came from.
|
|
|
|
These files execute on the Studio origin, which is where session.ts keeps the access and
|
|
refresh tokens, so the point of shipping them rather than loading them from a CDN is that
|
|
their bytes are fixed at review time. A silent edit here is a script change nobody read.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
_BACKEND = Path(__file__).resolve().parent.parent
|
|
_DOCS_UI = _BACKEND / "assets" / "docs_ui"
|
|
_MANIFEST = json.loads((_DOCS_UI / "docs_ui_manifest.json").read_text(encoding = "utf-8"))
|
|
|
|
# Ours, not upstream's: prose we may reword, and the manifest cannot hash itself.
|
|
_UNPINNED = {"README.md", "docs_ui_manifest.json"}
|
|
|
|
|
|
def _tracked_files() -> dict[str, Path]:
|
|
"""Enumerate the real tree, so an *added* file is caught and not just an edit."""
|
|
return {
|
|
path.relative_to(_DOCS_UI).as_posix(): path
|
|
for path in sorted(_DOCS_UI.rglob("*"))
|
|
if path.is_file() and path.relative_to(_DOCS_UI).as_posix() not in _UNPINNED
|
|
}
|
|
|
|
|
|
def test_tree_matches_the_manifest():
|
|
found = _tracked_files()
|
|
recorded = _MANIFEST["files"]
|
|
assert set(found) == set(recorded), (
|
|
"assets/docs_ui gained or lost a file; it is a static copy of the pinned releases, "
|
|
"so update docs_ui_manifest.json in the same commit"
|
|
)
|
|
drifted = [
|
|
name
|
|
for name, path in found.items()
|
|
if hashlib.sha256(path.read_bytes()).hexdigest() != recorded[name]
|
|
]
|
|
assert not drifted, (
|
|
f"vendored docs assets no longer match their pinned releases: {', '.join(drifted)}. "
|
|
"A formatter or minifier most likely rewrote them"
|
|
)
|
|
|
|
|
|
def test_no_symlinks():
|
|
"""A symlink would let the digest check pass while the served bytes differ."""
|
|
offenders = [
|
|
str(path.relative_to(_DOCS_UI)) for path in _DOCS_UI.rglob("*") if path.is_symlink()
|
|
]
|
|
assert not offenders, f"assets/docs_ui must be plain files: {offenders}"
|
|
|
|
|
|
def test_every_package_ships_its_licence():
|
|
names = {entry["package"] for entry in _MANIFEST["packages"]}
|
|
assert names == {"swagger-ui-dist", "redoc"}
|
|
assert (_DOCS_UI / "LICENSE.swagger-ui").exists()
|
|
assert (_DOCS_UI / "LICENSE.redoc").exists()
|
|
# Apache-2.0 section 4(d): redistributing a work that carries a NOTICE means shipping it.
|
|
# The bundle also names its own extracted third-party banners; ship those with it.
|
|
assert (_DOCS_UI / "NOTICE.swagger-ui").exists()
|
|
assert (_DOCS_UI / "swagger-ui-bundle.js.LICENSE.txt").exists()
|
|
for entry in _MANIFEST["packages"]:
|
|
assert entry["version"] and entry["license"] and entry["source"]
|
|
|
|
|
|
def test_bundles_reference_no_remote_script_host():
|
|
"""The whole point is that nothing on the docs pages phones out for code."""
|
|
for name in ("swagger-ui-bundle.js", "redoc.standalone.js"):
|
|
text = (_DOCS_UI / name).read_text(encoding = "utf-8", errors = "ignore")
|
|
assert "cdn.jsdelivr.net" not in text, f"{name} pulls from jsDelivr at runtime"
|