Repository navigation
🎨 Palette: Swagger UI 인증 정보 유지 기능 추가 #775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 12 commits
9355b96
0a4b4da
340272e
31bf14c
6c4a584
623b14f
bbaed06
281ace5
282d5d9
31d441f
d919954
ad06fc5
db02668
69d9f69
d6085a2
cc918a8
7dea6f6
b581cbb
e8a8375
ad0616d
af7386e
75f895e
b26d998
4d701f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Product and technical gap baseline | ||
|
|
||
| This document records gaps that are observable from the current NewsDOM code, delivery policy, releases, and open integration work. It is not a roadmap commitment; a gap leaves this list only after the corresponding code and evidence are current on the protected integration path. | ||
|
|
||
| ## Product boundary | ||
|
|
||
| NewsDOM API owns the PDF-to-canonical-NewsDOM parsing boundary around MinerU. It accepts an authenticated PDF, validates and bounds the upload, invokes the local MinerU runtime, and returns the repository's NewsDOM response schema. Authentication, parsing and readiness truth stay in this service; consumers such as Naruon use the released API contract rather than source-copying NewsDOM internals. | ||
|
|
||
| The latest immutable GitHub release observed while updating this baseline is `v0.2.0` (published 2026-04-24). `CHANGELOG.md` currently describes an unreleased `0.3.0` migration in which parser authentication becomes default-required. | ||
|
|
||
| ## Current gaps | ||
|
|
||
| | Gap | Current evidence | Acceptance | | ||
| | --- | --- | --- | | ||
| | Interactive API documentation security and behavior | PR #775 repairs unconditional Swagger authorization persistence and the global CSP that blocked FastAPI's generated documentation assets. Authorization persistence is development-only; Swagger and ReDoc now receive route-scoped CSP policies in both runtime profiles, the external Swagger validator is disabled, and non-document API responses retain the locked-down CSP. Repository policy still requires a live localhost `/docs` and `/redoc` smoke for documentation changes. | Current-head unit/coverage/docs gates pass; a real browser/local smoke proves `/docs` and `/redoc` render without CSP errors in production and development; a refresh in development preserves Swagger authorization while production does not persist it; screenshots or equivalent browser evidence correspond to the exact tested head. | | ||
| | `0.3.0` release readiness | The changelog still uses an unreleased image placeholder for the authentication migration. The last immutable release is `v0.2.0`. | Protected `develop` evidence is GREEN; package, OpenAPI, container image, SBOM/provenance, rollback instructions and release manifest identify the same version and source; the release tag is immutable and reproducible. | | ||
| | Runtime configuration boundary | `AGENTS.md` records `NEWSDOM_MINERU_BIN` as the remaining raw-environment deployment knob and requires future secrets/credentials/external endpoints to use the canonical KV/credential-registry pattern. | The executable-path override has an explicit deployment/configuration ADR or is moved behind the adopted configuration boundary; no new runtime secret is read directly from process environment. | | ||
| | Real-data parser acceptance | Synthetic fixtures remain suitable for unit tests, but commercial parsing acceptance requires right-cleared representative PDFs and must not publish private/copyrighted inputs. | A private/right-cleared acceptance corpus exercises representative layouts, languages, page counts and failure cases; results are reproducible and published only as non-sensitive metrics/evidence, not source documents. | | ||
|
|
||
| ## Current PR #775 traceability | ||
|
|
||
| The repair sequence on branch `jules-11760207665579123715-6bc329bb` is test-first: | ||
|
|
||
| - Swagger RED regression: `31bf14cd6d71e03b0b31320c940c3b79f3d984ca` | ||
| - development-only authorization persistence and initial Swagger CSP fix: `6c4a5846761e4c1eee2d968c690589811de875a7` | ||
| - corrected CSP test oracle: `623b14fc62262fddb8e0d810e72317908cf76ae5` | ||
| - documentation alignment: `bbaed060f1243dc30653c40b2e66c9d93df46474` | ||
| - baseline introduction: `281ace5a3df9f23ef9f43d9984b55d3353c768b5` | ||
| - ReDoc CSP RED: `282d5d9259b578913c7fbe29fce9415bad3c106f` | ||
| - production-route CSP RED expansion: `31d441f67f8f91f072a04bcd5e30b3481256ec0f` | ||
| - route-scoped Swagger/ReDoc causal fix: `d9199541d00a1da1cc732af42f6f59a6e2e59cbe` | ||
|
|
||
| GitHub-hosted verification must be evaluated on the final exact head, not on any predecessor listed above. Queued jobs or CodeQL `startup_failure` before source execution are incomplete evidence, not passing gates and not a reason for no-op source churn. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,6 +27,7 @@ | |
| from .config import ( | ||
| AuthenticationMode, | ||
| MAX_BEARER_HEADER_BYTES, | ||
| RuntimeProfile, | ||
| RuntimeSettings, | ||
| load_runtime_settings, | ||
| ) | ||
|
|
@@ -50,6 +51,25 @@ | |
| SERVICE_UNAVAILABLE_DETAIL = "Service Unavailable" | ||
| LOGGER = logging.getLogger("newsdom_api") | ||
| BEARER_SCHEME = HTTPBearer(auto_error=False, scheme_name="BearerAuth") | ||
| LOCKED_DOWN_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'" | ||
| SWAGGER_DOCS_CSP = ( | ||
| "default-src 'none'; " | ||
| "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " | ||
| "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; " | ||
| "img-src 'self' data: https://fastapi.tiangolo.com; " | ||
| "font-src 'self' data: https://cdn.jsdelivr.net; " | ||
| "connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; " | ||
| "form-action 'self'" | ||
| ) | ||
| REDOC_DOCS_CSP = ( | ||
| "default-src 'none'; " | ||
| "script-src 'self' https://cdn.jsdelivr.net; " | ||
| "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; " | ||
| "font-src 'self' https://fonts.gstatic.com; " | ||
| "img-src 'self' data: https://fastapi.tiangolo.com; " | ||
| "connect-src 'self'; frame-ancestors 'none'; base-uri 'none'; " | ||
| "form-action 'self'" | ||
| ) | ||
|
|
||
| tags_metadata = [ | ||
| {"name": "Parser", "description": "Core PDF parsing endpoints."}, | ||
|
|
@@ -65,9 +85,14 @@ def _apply_security_headers(response: Response, request: Request) -> Response: | |
|
|
||
| response.headers["X-Content-Type-Options"] = "nosniff" | ||
| response.headers["X-Frame-Options"] = "DENY" | ||
| response.headers["Content-Security-Policy"] = ( | ||
| "default-src 'none'; frame-ancestors 'none'; base-uri 'none'" | ||
| ) | ||
| path = request.scope.get("path") | ||
| if path in {"/docs", "/docs/oauth2-redirect"}: | ||
| csp = SWAGGER_DOCS_CSP | ||
| elif path == "/redoc": | ||
| csp = REDOC_DOCS_CSP | ||
| else: | ||
| csp = LOCKED_DOWN_CSP | ||
|
Comment on lines
+88
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| response.headers["Content-Security-Policy"] = csp | ||
| response.headers["Referrer-Policy"] = "no-referrer" | ||
| response.headers["Cache-Control"] = "no-store, no-cache, max-age=0" | ||
| forwarded_proto = request.headers.get("x-forwarded-proto", "") | ||
|
|
@@ -321,6 +346,10 @@ def create_app( | |
| "displayRequestDuration": True, | ||
| "syntaxHighlight.theme": "monokai", | ||
| "tryItOutEnabled": True, | ||
| "persistAuthorization": ( | ||
| application_settings.runtime_profile is RuntimeProfile.DEVELOPMENT | ||
| ), | ||
| "validatorUrl": None, | ||
| }, | ||
| ) | ||
| application.state.runtime_settings = application_settings | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| """Regression tests for the interactive API documentation security boundary.""" | ||
|
|
||
| from fastapi.testclient import TestClient | ||
|
|
||
| from newsdom_api.config import AuthenticationMode, RuntimeProfile, RuntimeSettings | ||
| from newsdom_api.main import create_app | ||
|
|
||
| LOCKED_DOWN_CSP = "default-src 'none'; frame-ancestors 'none'; base-uri 'none'" | ||
|
|
||
|
|
||
| def _settings(profile: RuntimeProfile) -> RuntimeSettings: | ||
| """Build an authenticated runtime configuration for one profile.""" | ||
|
|
||
| return RuntimeSettings( | ||
| authentication_mode=AuthenticationMode.REQUIRED, | ||
| runtime_profile=profile, | ||
| api_token="swagger-test-token", | ||
| ) | ||
|
|
||
|
|
||
| def test_swagger_authorization_persistence_is_development_only() -> None: | ||
| """Persist bearer authorization only in the explicit development profile.""" | ||
|
|
||
| production = TestClient(create_app(_settings(RuntimeProfile.PRODUCTION))) | ||
| development = TestClient(create_app(_settings(RuntimeProfile.DEVELOPMENT))) | ||
|
|
||
| production_docs = production.get("/docs") | ||
| development_docs = development.get("/docs") | ||
|
|
||
| assert production_docs.status_code == 200 | ||
| assert development_docs.status_code == 200 | ||
| assert '"persistAuthorization": false' in production_docs.text | ||
| assert '"persistAuthorization": true' in development_docs.text | ||
| assert '"validatorUrl": null' in development_docs.text | ||
|
Comment on lines
+30
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- test file outline ---'
ast-grep outline tests/test_swagger_docs_security.py
printf '%s\n' '--- targeted test sections ---'
sed -n '1,140p' tests/test_swagger_docs_security.py
printf '%s\n' '--- documentation/CSP definitions and routes ---'
rg -n -C 8 'Content-Security-Policy|LOCKED_DOWN_CSP|oauth2-redirect|docs' src/newsdom_api tests/test_swagger_docs_security.pyRepository: ContextualWisdomLab/newsdom-api Length of output: 11220 Security Misconfiguration (CWE-693) CSP 정책 전체와 모든 변경 경로를 검증하세요. 개발 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
|
|
||
| def test_swagger_csp_allows_only_required_origins_in_each_profile() -> None: | ||
| """Swagger UI can execute in each runtime profile with a route-scoped CSP.""" | ||
|
|
||
| for profile in (RuntimeProfile.PRODUCTION, RuntimeProfile.DEVELOPMENT): | ||
| client = TestClient(create_app(_settings(profile))) | ||
| response = client.get("/docs") | ||
|
|
||
| assert response.status_code == 200 | ||
| csp = response.headers["Content-Security-Policy"] | ||
| assert "default-src 'none'" in csp | ||
| assert "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net" in csp | ||
| assert "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net" in csp | ||
| assert "img-src 'self' data: https://fastapi.tiangolo.com" in csp | ||
| assert "connect-src 'self'" in csp | ||
| assert "frame-ancestors 'none'" in csp | ||
| assert "base-uri 'none'" in csp | ||
| assert "form-action 'self'" in csp | ||
|
|
||
|
|
||
| def test_redoc_csp_allows_only_required_origins_in_each_profile() -> None: | ||
| """ReDoc can load its script, fonts, schema and favicon in each profile.""" | ||
|
|
||
| for profile in (RuntimeProfile.PRODUCTION, RuntimeProfile.DEVELOPMENT): | ||
| client = TestClient(create_app(_settings(profile))) | ||
| response = client.get("/redoc") | ||
|
|
||
| assert response.status_code == 200 | ||
| csp = response.headers["Content-Security-Policy"] | ||
| assert "default-src 'none'" in csp | ||
| assert "script-src 'self' https://cdn.jsdelivr.net" in csp | ||
| assert "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com" in csp | ||
| assert "font-src 'self' https://fonts.gstatic.com" in csp | ||
| assert "img-src 'self' data: https://fastapi.tiangolo.com" in csp | ||
| assert "connect-src 'self'" in csp | ||
| assert "frame-ancestors 'none'" in csp | ||
| assert "base-uri 'none'" in csp | ||
| assert "form-action 'self'" in csp | ||
|
|
||
|
|
||
| def test_non_docs_responses_keep_the_locked_down_csp() -> None: | ||
| """The docs exception must not weaken the API response security boundary.""" | ||
|
|
||
| for profile in (RuntimeProfile.PRODUCTION, RuntimeProfile.DEVELOPMENT): | ||
| client = TestClient(create_app(_settings(profile)), base_url="https://testserver") | ||
| response = client.get("/health") | ||
|
|
||
| assert response.status_code == 200 | ||
| assert response.headers["Content-Security-Policy"] == LOCKED_DOWN_CSP | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 무관한 제품 기준 문서 혼입
Swagger 수정 PR에 릴리스, 설정, 실제 데이터까지 다루는 제품 기준이 추가되었습니다. 이는 범위 정책이 요구하는 단일 원인 범위를 벗어납니다.
Was this helpful? React with 👍 or 👎 to provide feedback.