7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
552 lines
18 KiB
Python
552 lines
18 KiB
Python
"""
|
|
Tests to verify session["username"] hardening across all route files.
|
|
|
|
All @login_required route handlers should use session["username"] (direct access)
|
|
instead of session.get("username"). The @login_required decorator guarantees
|
|
the key exists; direct access fails fast if the decorator is ever removed.
|
|
"""
|
|
|
|
import inspect
|
|
import textwrap
|
|
|
|
|
|
def _get_function_source(func):
|
|
"""Get dedented source of a function for AST analysis."""
|
|
return textwrap.dedent(inspect.getsource(func))
|
|
|
|
|
|
def _uses_unsafe_session_get(source: str) -> bool:
|
|
"""Check if source contains session.get("username") or flask_session.get("username")."""
|
|
return (
|
|
'session.get("username")' in source
|
|
or "session.get('username')" in source
|
|
)
|
|
|
|
|
|
def _uses_safe_session_access(source: str) -> bool:
|
|
"""Check if source contains session["username"] or flask_session["username"]."""
|
|
return 'session["username"]' in source or "session['username']" in source
|
|
|
|
|
|
class TestNewsRoutesSessionAccess:
|
|
"""Verify news routes use session['username'] not session.get('username')."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.web.routes.news_routes import (
|
|
create_subscription,
|
|
get_news_feed,
|
|
get_subscriptions,
|
|
save_preferences,
|
|
submit_feedback,
|
|
)
|
|
|
|
return [
|
|
get_news_feed,
|
|
get_subscriptions,
|
|
create_subscription,
|
|
submit_feedback,
|
|
save_preferences,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
"""No news route should use session.get('username')."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username') "
|
|
f"instead of session['username']"
|
|
)
|
|
|
|
def test_uses_session_bracket_access(self):
|
|
"""Each news route that accesses username should use bracket notation."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert _uses_safe_session_access(source), (
|
|
f"{func.__name__} should use session['username']"
|
|
)
|
|
|
|
|
|
class TestHistoryRoutesSessionAccess:
|
|
"""Verify history routes use session['username'] not session.get('username')."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.web.routes.history_routes import (
|
|
get_history,
|
|
get_research_details,
|
|
get_research_logs,
|
|
get_research_status,
|
|
)
|
|
|
|
return [
|
|
get_history,
|
|
get_research_status,
|
|
get_research_details,
|
|
get_research_logs,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestResearchRoutesSessionAccess:
|
|
"""Verify research routes use session['username'] not session.get('username')."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.web.routes.research_routes import (
|
|
clear_history,
|
|
delete_research,
|
|
export_research_report,
|
|
get_history,
|
|
get_queue_position,
|
|
get_queue_status,
|
|
get_research_details,
|
|
get_research_logs,
|
|
get_research_report,
|
|
get_research_status,
|
|
start_research,
|
|
terminate_research,
|
|
upload_pdf,
|
|
)
|
|
|
|
return [
|
|
start_research,
|
|
terminate_research,
|
|
delete_research,
|
|
clear_history,
|
|
get_history,
|
|
get_research_details,
|
|
get_research_logs,
|
|
get_research_report,
|
|
export_research_report,
|
|
get_research_status,
|
|
get_queue_status,
|
|
get_queue_position,
|
|
upload_pdf,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestContextOverflowSessionAccess:
|
|
"""Verify context overflow routes use flask_session['username']."""
|
|
|
|
def test_no_session_get_username(self):
|
|
from local_deep_research.web.routes.context_overflow_api import (
|
|
get_context_overflow_metrics,
|
|
)
|
|
|
|
source = _get_function_source(get_context_overflow_metrics)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
"get_context_overflow_metrics uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestApiRoutesSessionAccess:
|
|
"""Verify API routes use session['username']."""
|
|
|
|
def test_no_session_get_username(self):
|
|
from local_deep_research.web.routes.api_routes import (
|
|
api_terminate_research,
|
|
)
|
|
|
|
source = _get_function_source(api_terminate_research)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
"api_terminate_research uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestSettingsRoutesSessionAccess:
|
|
"""Verify settings routes use session['username'] (except helper functions)."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.web.routes.settings_routes import (
|
|
api_delete_setting,
|
|
api_get_all_settings,
|
|
api_get_available_models,
|
|
api_get_available_search_engines,
|
|
api_get_categories,
|
|
api_get_db_setting,
|
|
api_get_search_favorites,
|
|
api_import_settings,
|
|
api_toggle_search_favorite,
|
|
api_update_search_favorites,
|
|
api_update_setting,
|
|
fix_corrupted_settings,
|
|
reset_to_defaults,
|
|
save_all_settings,
|
|
save_settings,
|
|
)
|
|
|
|
return [
|
|
save_all_settings,
|
|
reset_to_defaults,
|
|
save_settings,
|
|
api_get_all_settings,
|
|
api_get_db_setting,
|
|
api_update_setting,
|
|
api_delete_setting,
|
|
api_import_settings,
|
|
api_get_categories,
|
|
api_get_available_models,
|
|
api_get_available_search_engines,
|
|
api_get_search_favorites,
|
|
api_update_search_favorites,
|
|
api_toggle_search_favorite,
|
|
fix_corrupted_settings,
|
|
]
|
|
|
|
def test_no_session_get_username_in_routes(self):
|
|
"""Decorated route functions should not use session.get('username')."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
def test_helper_functions_use_safe_get(self):
|
|
"""Helper functions (not decorated) should still use session.get('username')."""
|
|
from local_deep_research.web.routes.settings_routes import (
|
|
_get_setting_from_session,
|
|
)
|
|
from local_deep_research.web.warning_checks import calculate_warnings
|
|
|
|
for func in [_get_setting_from_session, calculate_warnings]:
|
|
source = _get_function_source(func)
|
|
assert _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} should use session.get('username') "
|
|
f"because it is not behind @login_required"
|
|
)
|
|
|
|
|
|
class TestLibraryRoutesSessionAccess:
|
|
"""Verify library routes use session['username']."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.research_library.routes.library_routes import (
|
|
check_downloads,
|
|
delete_document,
|
|
document_details_page,
|
|
download_all_text,
|
|
download_bulk,
|
|
download_research_pdfs,
|
|
download_single_resource,
|
|
download_source,
|
|
download_text_single,
|
|
get_collections_list,
|
|
get_documents,
|
|
get_library_stats,
|
|
get_research_list,
|
|
get_research_sources,
|
|
library_page,
|
|
mark_for_redownload,
|
|
download_manager_page,
|
|
queue_all_undownloaded,
|
|
serve_text_api,
|
|
sync_library,
|
|
toggle_favorite,
|
|
view_pdf_page,
|
|
view_text_page,
|
|
)
|
|
|
|
return [
|
|
library_page,
|
|
document_details_page,
|
|
download_manager_page,
|
|
get_library_stats,
|
|
get_collections_list,
|
|
get_documents,
|
|
toggle_favorite,
|
|
delete_document,
|
|
view_pdf_page,
|
|
view_text_page,
|
|
serve_text_api,
|
|
download_single_resource,
|
|
download_text_single,
|
|
download_all_text,
|
|
download_research_pdfs,
|
|
download_bulk,
|
|
get_research_list,
|
|
sync_library,
|
|
mark_for_redownload,
|
|
queue_all_undownloaded,
|
|
get_research_sources,
|
|
check_downloads,
|
|
download_source,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestFollowupRoutesSessionAccess:
|
|
"""Verify followup routes use session['username']."""
|
|
|
|
def test_no_session_get_username(self):
|
|
from local_deep_research.followup_research.routes import (
|
|
prepare_followup,
|
|
start_followup,
|
|
)
|
|
|
|
for func in [prepare_followup, start_followup]:
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestSchedulerRoutesSessionAccess:
|
|
"""Verify scheduler helper uses session.get('username') (not decorated)."""
|
|
|
|
def test_helper_uses_safe_get(self):
|
|
"""get_current_username is a helper, not a route — it should use .get()."""
|
|
from local_deep_research.research_scheduler.routes import (
|
|
get_current_username,
|
|
)
|
|
|
|
source = _get_function_source(get_current_username)
|
|
assert _uses_unsafe_session_get(source), (
|
|
"get_current_username should use session.get('username') "
|
|
"because it is not behind @login_required"
|
|
)
|
|
|
|
|
|
class TestBenchmarkRoutesSessionAccess:
|
|
"""Verify benchmark routes use flask_session['username']."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.benchmarks.web_api.benchmark_routes import (
|
|
cancel_benchmark,
|
|
delete_benchmark_run,
|
|
export_benchmark_results,
|
|
get_benchmark_history,
|
|
get_benchmark_results,
|
|
get_benchmark_status,
|
|
get_running_benchmark,
|
|
index,
|
|
start_benchmark,
|
|
start_benchmark_simple,
|
|
)
|
|
|
|
return [
|
|
index,
|
|
start_benchmark,
|
|
get_running_benchmark,
|
|
get_benchmark_status,
|
|
cancel_benchmark,
|
|
get_benchmark_history,
|
|
get_benchmark_results,
|
|
start_benchmark_simple,
|
|
delete_benchmark_run,
|
|
export_benchmark_results,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
|
|
class TestMetricsRoutesSessionAccess:
|
|
"""Verify metrics routes use flask_session['username'] (except helper functions)."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.web.routes.metrics_routes import (
|
|
api_classify_domains,
|
|
api_classification_progress,
|
|
api_cost_analytics,
|
|
api_enhanced_metrics,
|
|
api_get_classifications_summary,
|
|
api_get_domain_classifications,
|
|
api_get_research_rating,
|
|
api_link_analytics,
|
|
api_metrics,
|
|
api_rate_limiting_metrics,
|
|
api_research_costs,
|
|
api_research_link_metrics,
|
|
api_save_research_rating,
|
|
api_star_reviews,
|
|
)
|
|
|
|
return [
|
|
api_metrics,
|
|
api_rate_limiting_metrics,
|
|
api_research_link_metrics,
|
|
api_enhanced_metrics,
|
|
api_get_research_rating,
|
|
api_save_research_rating,
|
|
api_star_reviews,
|
|
api_research_costs,
|
|
api_cost_analytics,
|
|
api_link_analytics,
|
|
api_get_domain_classifications,
|
|
api_get_classifications_summary,
|
|
api_classify_domains,
|
|
api_classification_progress,
|
|
]
|
|
|
|
def test_no_session_get_username_in_routes(self):
|
|
"""Decorated route functions should not use flask_session.get('username')."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username')"
|
|
)
|
|
|
|
def test_helper_functions_use_safe_get(self):
|
|
"""Helper functions (not decorated) should still use flask_session.get('username')."""
|
|
from local_deep_research.web.routes.metrics_routes import (
|
|
get_link_analytics,
|
|
get_rate_limiting_analytics,
|
|
get_rating_analytics,
|
|
get_strategy_analytics,
|
|
)
|
|
|
|
for func in [
|
|
get_rating_analytics,
|
|
get_link_analytics,
|
|
get_strategy_analytics,
|
|
get_rate_limiting_analytics,
|
|
]:
|
|
source = _get_function_source(func)
|
|
assert _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} should use flask_session.get('username') "
|
|
f"because it is not behind @login_required"
|
|
)
|
|
|
|
|
|
class TestRAGRoutesSessionAccess:
|
|
"""Verify RAG routes use session['username'] not session.get('username')."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.research_library.routes.rag_routes import (
|
|
cancel_indexing,
|
|
configure_rag,
|
|
create_collection,
|
|
get_collection_documents,
|
|
get_collections,
|
|
get_documents,
|
|
get_index_info,
|
|
get_index_status,
|
|
get_rag_stats,
|
|
index_all,
|
|
index_collection,
|
|
index_document,
|
|
remove_document,
|
|
start_background_index,
|
|
update_collection,
|
|
upload_to_collection,
|
|
view_document_chunks,
|
|
)
|
|
|
|
return [
|
|
view_document_chunks,
|
|
get_index_info,
|
|
get_rag_stats,
|
|
index_document,
|
|
remove_document,
|
|
index_all,
|
|
configure_rag,
|
|
get_documents,
|
|
get_collections,
|
|
create_collection,
|
|
update_collection,
|
|
upload_to_collection,
|
|
get_collection_documents,
|
|
index_collection,
|
|
start_background_index,
|
|
get_index_status,
|
|
cancel_indexing,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
"""No RAG route should use session.get('username')."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username') "
|
|
f"instead of session['username']"
|
|
)
|
|
|
|
|
|
class TestDeleteRoutesSessionAccess:
|
|
"""Verify delete routes use session['username'] not session.get('username')."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.research_library.deletion.routes.delete_routes import (
|
|
delete_collection,
|
|
delete_collection_index,
|
|
delete_document,
|
|
delete_document_blob,
|
|
delete_documents_blobs_bulk,
|
|
delete_documents_bulk,
|
|
get_bulk_deletion_preview,
|
|
get_collection_deletion_preview,
|
|
get_document_deletion_preview,
|
|
remove_document_from_collection,
|
|
remove_documents_from_collection_bulk,
|
|
)
|
|
|
|
return [
|
|
delete_document,
|
|
delete_document_blob,
|
|
get_document_deletion_preview,
|
|
remove_document_from_collection,
|
|
delete_collection,
|
|
delete_collection_index,
|
|
get_collection_deletion_preview,
|
|
delete_documents_bulk,
|
|
delete_documents_blobs_bulk,
|
|
remove_documents_from_collection_bulk,
|
|
get_bulk_deletion_preview,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
"""No delete route should use session.get('username')."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username') "
|
|
f"instead of session['username']"
|
|
)
|
|
|
|
|
|
class TestNewsFlaskApiSessionAccess:
|
|
"""Verify news flask_api routes use session['username'] not session.get('username')."""
|
|
|
|
def _get_route_functions(self):
|
|
from local_deep_research.news.flask_api import (
|
|
check_overdue_subscriptions,
|
|
run_subscription_now,
|
|
scheduler_stats,
|
|
)
|
|
|
|
return [
|
|
run_subscription_now,
|
|
scheduler_stats,
|
|
check_overdue_subscriptions,
|
|
]
|
|
|
|
def test_no_session_get_username(self):
|
|
"""No news flask_api route should use session.get('username')."""
|
|
for func in self._get_route_functions():
|
|
source = _get_function_source(func)
|
|
assert not _uses_unsafe_session_get(source), (
|
|
f"{func.__name__} uses session.get('username') "
|
|
f"instead of session['username']"
|
|
)
|