From a599086c1163a8d2fa1b67b883bdf70e07dcd1e8 Mon Sep 17 00:00:00 2001 From: nearology Date: Mon, 9 Nov 2026 12:05:03 +0330 Subject: [PATCH] =?UTF-8?q?=D8=A7=D8=B5=D9=84=D8=A7=D8=AD=20=D9=87=D8=AF?= =?UTF-8?q?=D8=B1=20=D9=87=D8=A7=20=D8=A8=D8=B1=D8=A7=DB=8C=20=D9=86=D9=85?= =?UTF-8?q?=D8=A7=DB=8C=D8=B4=20=D8=A7=DB=8C=D9=81=D8=B1=DB=8C=D9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/main.py | 2 +- backend/tests/test_platform.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index d224b25..fcce0d5 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -75,7 +75,7 @@ async def security(request, call_next): if settings().api_key and not authenticated(request.headers.get('x-api-key')): return JSONResponse({'success': False, 'error': {'code': 'unauthorized', 'message': 'Provide a valid X-API-Key header'}}, 401) response = await call_next(request) - is_viewer = request.url.path.startswith('/view/') + is_viewer = request.url.path.startswith(('/view/', '/compare/')) frame_ancestors = "'self' https://git.sadentel.net" if is_viewer else "'none'" response.headers.update({ 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', diff --git a/backend/tests/test_platform.py b/backend/tests/test_platform.py index dbefa52..ed7e6e8 100644 --- a/backend/tests/test_platform.py +++ b/backend/tests/test_platform.py @@ -235,6 +235,10 @@ VIEWER_CSP = DEFAULT_CSP.replace("frame-ancestors 'none'", "frame-ancestors 'sel ('GET', '/view/invalid-id', 404), ('GET', '/view/nested/missing', 404), ('POST', '/view/' + '0'*48, 405), + ('GET', '/compare/' + '0'*48, 200), + ('GET', '/compare/invalid-id', 404), + ('GET', '/compare/nested/missing', 404), + ('POST', '/compare/' + '0'*48, 405), ]) def test_viewer_only_allows_self_and_gitea_frames(client, method, path, expected_status): response = client.request(method, path) @@ -244,14 +248,15 @@ def test_viewer_only_allows_self_and_gitea_frames(client, method, path, expected assert response.headers['x-content-type-options'] == 'nosniff' -@pytest.mark.parametrize('path', ['/', '/docs', '/healthz', '/assets/viewer.js', '/view', '/viewer/invalid', '/api/v1/schematics/'+'0'*48+'/status']) +@pytest.mark.parametrize('path', ['/', '/docs', '/healthz', '/assets/viewer.js', '/view', '/compare', '/api/compare/'+'0'*48, '/viewer/invalid', '/api/v1/schematics/'+'0'*48+'/status']) def test_non_viewer_keeps_original_csp(client, path): response = client.get(path) assert response.headers.get_list('content-security-policy') == [DEFAULT_CSP] @pytest.mark.parametrize('frame_option', ['DENY', 'SAMEORIGIN']) -def test_conflicting_headers_removed_only_for_viewer(client, monkeypatch, frame_option): +@pytest.mark.parametrize('prefix', ['/view/', '/compare/']) +def test_conflicting_headers_removed_only_for_viewer(client, monkeypatch, frame_option, prefix): from fastapi.responses import Response def page(*args, **kwargs): response = Response('page', media_type='text/html', headers={ @@ -260,7 +265,8 @@ def test_conflicting_headers_removed_only_for_viewer(client, monkeypatch, frame_ response.headers.append('Content-Security-Policy', "frame-ancestors 'none'") return response monkeypatch.setattr('app.main.FileResponse', page) - viewer = client.get('/view/'+'0'*48) + monkeypatch.setattr('app.compare_api.FileResponse', page) + viewer = client.get(prefix+'0'*48) assert 'x-frame-options' not in viewer.headers assert viewer.headers.get_list('content-security-policy') == [VIEWER_CSP] other = client.get('/docs')