databricks-tellr-app 0.4.2.dev12__tar.gz → 0.4.2.dev13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/PKG-INFO +1 -1
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/pyproject.toml +1 -1
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/design_system_service.py +307 -53
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/README.md +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app/run.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app.egg-info/SOURCES.txt +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app.egg-info/requires.txt +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app.egg-info/top_level.txt +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/setup.cfg +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/setup.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/_otel_bootstrap.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/fixtures/tour_demo_deck.json +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/main.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/mcp_auth.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/mcp_server.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/middleware/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/middleware/csrf.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/middleware/request_logging.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/middleware/security_headers.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/_authz.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/admin.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/admin_usage.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/agent_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/chat.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/deck_contributors.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/export.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/feedback.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/google_slides.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/images.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/local_version.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/profiles.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/sessions.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/settings/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/settings/contributors.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/settings/deck_prompts.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/settings/design_systems.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/settings/identities.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/settings/slide_styles.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/setup.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/slides.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/tools.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/tour.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/verification.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/version.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/agent_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/feedback.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/requests.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/responses.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/settings/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/settings/requests.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/settings/responses.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/streaming.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/chat_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/export_job_queue.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/feedback_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/job_queue.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/session_manager.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/session_naming.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/usage_events.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/usage_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/utils/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/utils/validation.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/config_loader.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/context_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/database.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/databricks_client.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/defaults.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/encryption.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/init_default_profile.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/lakebase.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/migrate_profiles_to_agent_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/mlflow_agent_spans.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/mlflow_tracing.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/otel_logging.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/permission_context.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/prompt_modules.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/settings_db.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/user_context.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/deck_contributor.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/design_system.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/encryption_key.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/feedback.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/genie_space.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/google_global_credentials.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/google_oauth_token.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/identity.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/image.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/profile.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/profile_contributor.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/prompts.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/request_log.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/session.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/slide_deck_prompt.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/slide_style_library.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/usage_event.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/user_preference.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/types.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/domain/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/domain/slide.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/domain/slide_deck.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/agent.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/agent_factory.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/config_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/config_validator.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/ast_guard.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/codeprep.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/gslides_runner.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/jail.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/pptx_runner.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/converter_jail/protocol.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/design_system_compiler.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/design_system_templates.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/drive_uploader.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/evaluation/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/evaluation/llm_judge.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/genie_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/google_slides_auth.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/google_slides_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/html_to_google_slides.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/html_to_pptx.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/identity_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/identity_providers/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/identity_providers/account_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/identity_providers/local_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/identity_providers/workspace_provider.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/image_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/image_tools.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/permission_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/pptx_from_html_huashu.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/pptx_from_records.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/pptx_prompts_defaults.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/profile_service.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/streaming_callback.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/agent_bricks_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/ds_asset_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/genie_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/mcp_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/model_endpoint_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/vector_tool.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/validator.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/__init__.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/css_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/ds_asset_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/error_handling.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/html_safety.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/html_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/image_utils.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/js_validator.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/logging_config.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/pi_filter.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/slide_hash.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/spotlight.py +0 -0
- {databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/text_caps.py +0 -0
|
@@ -1083,6 +1083,22 @@ _REASON_CORRUPT_EXTRA = (
|
|
|
1083
1083
|
"its Info-ZIP Unicode Path extra field (0x7075) is malformed, so the name the "
|
|
1084
1084
|
"archive means to declare for this entry cannot be read at all"
|
|
1085
1085
|
)
|
|
1086
|
+
_REASON_LOCAL_HEADER = (
|
|
1087
|
+
"its local file header — the second place a .zip records an entry's name — cannot "
|
|
1088
|
+
"be read, so whether the archive states one name for this entry or two cannot be "
|
|
1089
|
+
"established"
|
|
1090
|
+
)
|
|
1091
|
+
# Distinct from :data:`_REASON_EMPTY`, which is about a name that IS empty everywhere.
|
|
1092
|
+
# This one is about a name the ARCHIVE leaves empty while the reader resolves a usable
|
|
1093
|
+
# one from an extra field — so the message has to name both halves, or it reads as a
|
|
1094
|
+
# complaint about a name the user can see is not empty.
|
|
1095
|
+
_REASON_EMPTY_RECORDED_NAME = (
|
|
1096
|
+
"the archive records NO name for this entry at all. An empty central-directory "
|
|
1097
|
+
"name is not a legitimate archive member, and it is not harmless either: an "
|
|
1098
|
+
"Info-ZIP Unicode Path extra field can supply a replacement (its checksum matches "
|
|
1099
|
+
"an empty name trivially), so the entry ends up with a name that was never "
|
|
1100
|
+
"recorded for it"
|
|
1101
|
+
)
|
|
1086
1102
|
_REASON_ABSOLUTE = (
|
|
1087
1103
|
"it is an absolute path, so it names a location outside the bundle rather than "
|
|
1088
1104
|
"a file inside it"
|
|
@@ -1398,8 +1414,26 @@ def _raw_entry_name(info: zipfile.ZipInfo) -> str:
|
|
|
1398
1414
|
``ZipInfo.__init__`` truncates ``filename`` at the first NUL byte and keeps the
|
|
1399
1415
|
real central-directory name in ``orig_filename``. Every gate here reads the name
|
|
1400
1416
|
through this function so validation judges what the archive actually says.
|
|
1417
|
+
|
|
1418
|
+
ABSENT and EMPTY are different answers, and only an explicit ``None`` test tells
|
|
1419
|
+
them apart. ``or`` did not: it fell back to ``filename`` for an ``orig_filename``
|
|
1420
|
+
of ``""`` as readily as for a missing attribute — which is this module's own
|
|
1421
|
+
laundering, performed at the one function whose entire job is to report the raw
|
|
1422
|
+
name. An empty central name is attacker-controlled data with meaning, and CPython
|
|
1423
|
+
will happily REPLACE it: a ``0x7075`` record needs its checksum to match the
|
|
1424
|
+
central name, and the CRC-32 of ``b""`` is 0, so the record is honoured and
|
|
1425
|
+
``filename`` becomes whatever it declares. The fallback then handed every gate
|
|
1426
|
+
that name, so ``orig_filename != filename`` compared the replacement against
|
|
1427
|
+
itself and the local-header comparison compared against it too. Measured: an entry
|
|
1428
|
+
with an empty central name, a local name of ``slides/benign.bin`` and a record
|
|
1429
|
+
declaring the same passed both and was yielded for storage.
|
|
1430
|
+
|
|
1431
|
+
The empty name is now REPORTED rather than replaced; refusing it is
|
|
1432
|
+
:func:`_entry_identity_refusal`'s call, on :data:`_REASON_EMPTY_RECORDED_NAME`.
|
|
1433
|
+
The ``getattr`` default stays for a ``ZipInfo`` that genuinely lacks the attribute.
|
|
1401
1434
|
"""
|
|
1402
|
-
|
|
1435
|
+
raw = getattr(info, "orig_filename", None)
|
|
1436
|
+
return info.filename if raw is None else raw
|
|
1403
1437
|
|
|
1404
1438
|
|
|
1405
1439
|
# Info-ZIP Unicode Path Extra Field. Its whole purpose is to declare a name for an
|
|
@@ -1411,11 +1445,20 @@ _UNICODE_PATH_EXTRA_TAG = 0x7075
|
|
|
1411
1445
|
# CRC-32 of the central-directory name.
|
|
1412
1446
|
_UNICODE_PATH_HEADER_LEN = 5
|
|
1413
1447
|
|
|
1448
|
+
# Every extra-field record is a 2-byte tag followed by a 2-byte body size. Anything
|
|
1449
|
+
# shorter than that cannot be a record at all.
|
|
1450
|
+
_EXTRA_RECORD_HEADER_LEN = 4
|
|
1414
1451
|
|
|
1415
|
-
|
|
1452
|
+
|
|
1453
|
+
def _extra_field_name_refusal(extra: bytes, central: str) -> Optional[str]:
|
|
1416
1454
|
"""Refuse an entry whose EXTRA FIELD declares a different name, else ``None``.
|
|
1417
1455
|
|
|
1418
|
-
|
|
1456
|
+
Takes the extra-field BYTES rather than a ``ZipInfo`` because an entry has TWO
|
|
1457
|
+
extra fields — one in the central directory, one in its local file header — and
|
|
1458
|
+
both are walked, by :func:`_entry_identity_refusal`. ``ZipInfo.extra`` is only the
|
|
1459
|
+
central copy.
|
|
1460
|
+
|
|
1461
|
+
Parsed from raw bytes on purpose. No stdlib-processed attribute
|
|
1419
1462
|
can be trusted as an entry's identity, and this record is the proof: in CPython's
|
|
1420
1463
|
``ZipInfo._decodeExtra`` a ``0x7075`` record assigns
|
|
1421
1464
|
``self.filename = _sanitize_filename(declared_name)`` and never touches
|
|
@@ -1443,12 +1486,21 @@ def _extra_field_name_refusal(info: zipfile.ZipInfo) -> Optional[str]:
|
|
|
1443
1486
|
and never the filename. The Unicode COMMENT field (``0x6375``) carries a comment,
|
|
1444
1487
|
not a path. Unknown tags are skipped by both the stdlib and the loop below.
|
|
1445
1488
|
"""
|
|
1446
|
-
central = _raw_entry_name(info)
|
|
1447
|
-
extra = info.extra or b""
|
|
1448
1489
|
offset = 0
|
|
1449
|
-
while len(extra)
|
|
1490
|
+
while offset < len(extra):
|
|
1491
|
+
# A LEFTOVER too short to be a record header is malformed, not padding. The
|
|
1492
|
+
# extra field is a sequence of records that tile it exactly; the format
|
|
1493
|
+
# defines no filler between or after them. Stopping quietly on a trailing
|
|
1494
|
+
# fragment skipped the shortest possible spelling of the very record this
|
|
1495
|
+
# function exists to read: an extra of exactly b"\x75\x70" is the 0x7075 tag
|
|
1496
|
+
# with its size field and body cut off, and CPython — whose own walk has the
|
|
1497
|
+
# same four-byte floor — opens such an archive without complaint. Whatever
|
|
1498
|
+
# name that record meant to declare cannot be read, which is the condition
|
|
1499
|
+
# the overrun and short-body branches below already refuse.
|
|
1500
|
+
if len(extra) - offset < _EXTRA_RECORD_HEADER_LEN:
|
|
1501
|
+
return _REASON_CORRUPT_EXTRA
|
|
1450
1502
|
tag, size = struct.unpack_from("<HH", extra, offset)
|
|
1451
|
-
body_start = offset +
|
|
1503
|
+
body_start = offset + _EXTRA_RECORD_HEADER_LEN
|
|
1452
1504
|
body_end = body_start + size
|
|
1453
1505
|
if body_end > len(extra):
|
|
1454
1506
|
return _REASON_CORRUPT_EXTRA
|
|
@@ -1466,42 +1518,196 @@ def _extra_field_name_refusal(info: zipfile.ZipInfo) -> Optional[str]:
|
|
|
1466
1518
|
return None
|
|
1467
1519
|
|
|
1468
1520
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1521
|
+
# Local file header layout (APPNOTE 4.3.7), the fixed part of which is 30 bytes:
|
|
1522
|
+
# signature, version needed, general-purpose bit flag, ... then the two lengths that
|
|
1523
|
+
# say how much variable-length data follows, then the name and the extra field.
|
|
1524
|
+
_LOCAL_HEADER_SIGNATURE = b"PK\x03\x04"
|
|
1525
|
+
_LOCAL_HEADER_LEN = 30
|
|
1526
|
+
_LOCAL_HEADER_FLAGS_OFFSET = 6
|
|
1527
|
+
_LOCAL_HEADER_LENGTHS_OFFSET = 26 # file name length u16, then extra field length u16
|
|
1528
|
+
# General-purpose bit 11: the name and comment are UTF-8 rather than the historical
|
|
1529
|
+
# CP437. Read from the LOCAL header's own flags, because that is the byte a reader
|
|
1530
|
+
# consulting this header would use to decode the name beside it.
|
|
1531
|
+
_UTF8_NAME_FLAG = 0x800
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
def _local_header_identity(
|
|
1535
|
+
zf: zipfile.ZipFile, info: zipfile.ZipInfo
|
|
1536
|
+
) -> "Optional[tuple[str, bytes]]":
|
|
1537
|
+
"""The ``(name, extra)`` the entry's LOCAL FILE HEADER records, or ``None``.
|
|
1538
|
+
|
|
1539
|
+
A .zip states every entry's identity TWICE — once in the local file header that
|
|
1540
|
+
precedes its bytes, once in the central directory at the end — and NOTHING in the
|
|
1541
|
+
format makes the two agree. CPython builds ``ZipInfo`` purely from the central
|
|
1542
|
+
copy: ``ZipInfo.extra`` is the central extra and the local one is never parsed at
|
|
1543
|
+
all. So a guard that reads ``info.extra`` alone inspects one of the two names an
|
|
1544
|
+
archive can present, and an attacker simply puts the rewrite in the other:
|
|
1545
|
+
measured, an entry with a clean central name, an EMPTY central extra and a local
|
|
1546
|
+
``0x7075`` record declaring ``templates/x/.thumbnail\\x00.exe`` (version 1,
|
|
1547
|
+
matching CRC — the form CPython would honour had it looked) was accepted and
|
|
1548
|
+
stored as a live template thumbnail, which is precisely the outcome the central
|
|
1549
|
+
check exists to prevent.
|
|
1550
|
+
|
|
1551
|
+
Read straight from the archive rather than through any ``ZipInfo`` attribute, for
|
|
1552
|
+
the same reason the extra field is: the point is what the bytes say. The header is
|
|
1553
|
+
located by ``info.header_offset`` — an absolute file offset, adjusted by CPython
|
|
1554
|
+
for any prepended data — and parsed at the documented offsets rather than guessed
|
|
1555
|
+
at.
|
|
1556
|
+
|
|
1557
|
+
THE OFFSET IS THE ARCHIVE'S, NOT THE READER'S. ``header_offset`` looks like a value
|
|
1558
|
+
the zip reader vouched for and is nothing of the kind: a central directory record
|
|
1559
|
+
may carry the legacy sentinel ``0xffffffff`` in its 32-bit offset field plus a ZIP64
|
|
1560
|
+
extra field holding a 64-bit replacement, and ``ZipInfo._decodeExtra`` substitutes
|
|
1561
|
+
that verbatim without ever comparing it to the size of the file. A 146-byte archive
|
|
1562
|
+
can therefore claim its entry's local header begins at ``2**64 - 1``. So the offset
|
|
1563
|
+
is bound-checked against the archive's real size BEFORE the seek, and an offset that
|
|
1564
|
+
cannot hold a header is unreadable like any other — which also makes "past the end
|
|
1565
|
+
of the file" and "too large to seek to at all" one case instead of two.
|
|
1566
|
+
|
|
1567
|
+
PRECONDITION on sharing ``zf.fp``, stated rather than assumed. This seeks a file
|
|
1568
|
+
pointer it does not own, and it does so WITHOUT taking ``ZipFile._lock``, so it is
|
|
1569
|
+
not safe in general — a truly concurrent read, or a file object shared with anything
|
|
1570
|
+
outside this ``ZipFile``, could be moved out from under mid-read. It is safe for
|
|
1571
|
+
THIS importer's usage, and only because of four properties that hold together:
|
|
1572
|
+
:func:`import_bundle` wraps the upload in a private :class:`io.BytesIO` that nothing
|
|
1573
|
+
else holds a reference to; the whole-bundle scan runs to completion before any entry
|
|
1574
|
+
is read; ``ZipFile.open``'s reader re-seeks to its own position before every read, so
|
|
1575
|
+
a moved pointer cannot corrupt an open stream; and the asset reads are synchronous
|
|
1576
|
+
``zf.read`` calls with no interleaving. The position is restored afterwards anyway —
|
|
1577
|
+
it costs one seek and removes the need for a reader of this function to know any of
|
|
1578
|
+
the above.
|
|
1579
|
+
|
|
1580
|
+
``None`` means the header could not be read as a local file header at all, which
|
|
1581
|
+
callers treat as a refusal (:data:`_REASON_LOCAL_HEADER`) rather than as
|
|
1582
|
+
permission: an entry whose second name is unknown is not an entry with one name.
|
|
1583
|
+
"""
|
|
1584
|
+
fp = getattr(zf, "fp", None)
|
|
1585
|
+
if fp is None:
|
|
1586
|
+
return None
|
|
1587
|
+
try:
|
|
1588
|
+
resume_at = fp.tell()
|
|
1589
|
+
try:
|
|
1590
|
+
fp.seek(0, io.SEEK_END)
|
|
1591
|
+
archive_size = fp.tell()
|
|
1592
|
+
offset = info.header_offset
|
|
1593
|
+
# Negative is nonsense and at/after EOF means the header is not there. Both
|
|
1594
|
+
# are checked BEFORE the seek, because a large enough offset does not fail
|
|
1595
|
+
# the seek gracefully — it cannot be converted to a C ssize_t and raises
|
|
1596
|
+
# OverflowError, which is not an OSError and is not a ValueError.
|
|
1597
|
+
if offset < 0 or offset + _LOCAL_HEADER_LEN > archive_size:
|
|
1598
|
+
return None
|
|
1599
|
+
fp.seek(offset)
|
|
1600
|
+
header = fp.read(_LOCAL_HEADER_LEN)
|
|
1601
|
+
if (
|
|
1602
|
+
len(header) != _LOCAL_HEADER_LEN
|
|
1603
|
+
or header[:4] != _LOCAL_HEADER_SIGNATURE
|
|
1604
|
+
):
|
|
1605
|
+
return None
|
|
1606
|
+
name_len, extra_len = struct.unpack_from(
|
|
1607
|
+
"<HH", header, _LOCAL_HEADER_LENGTHS_OFFSET
|
|
1608
|
+
)
|
|
1609
|
+
name_bytes = fp.read(name_len)
|
|
1610
|
+
extra = fp.read(extra_len)
|
|
1611
|
+
finally:
|
|
1612
|
+
fp.seek(resume_at)
|
|
1613
|
+
except (OSError, ValueError, OverflowError):
|
|
1614
|
+
# A seek on a detached/closed stream raises, and the bound check above cannot
|
|
1615
|
+
# cover an arithmetic surprise nobody has thought of yet — OverflowError is
|
|
1616
|
+
# here as the backstop for the next one, since the whole point of this helper
|
|
1617
|
+
# is that its input is hostile. Unreadable, like any other unparseable header.
|
|
1618
|
+
#
|
|
1619
|
+
# NOT bare ``Exception``: a genuine bug in the parsing this guards must still
|
|
1620
|
+
# crash loudly rather than be laundered into a path refusal.
|
|
1621
|
+
return None
|
|
1622
|
+
if len(name_bytes) != name_len or len(extra) != extra_len:
|
|
1623
|
+
return None
|
|
1624
|
+
(flags,) = struct.unpack_from("<H", header, _LOCAL_HEADER_FLAGS_OFFSET)
|
|
1625
|
+
encoding = "utf-8" if flags & _UTF8_NAME_FLAG else "cp437"
|
|
1626
|
+
try:
|
|
1627
|
+
return name_bytes.decode(encoding), extra
|
|
1628
|
+
except UnicodeDecodeError:
|
|
1629
|
+
return None
|
|
1473
1630
|
|
|
1474
|
-
``rel_path`` is the name to CLASSIFY when it differs from the archive's — the
|
|
1475
|
-
root-prefix-stripped path, for the in-scope iterator. It has to be a separate
|
|
1476
|
-
argument because the template-thumbnail allowlist is anchored at ``templates/``:
|
|
1477
|
-
a wrapped bundle's ``safe/templates/x/.thumbnail`` only matches once ``safe/`` is
|
|
1478
|
-
off. The identity checks below always apply to the FULL recorded name, which is
|
|
1479
|
-
the string a rewrite would have altered.
|
|
1480
1631
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1632
|
+
def _entry_identity_refusal(
|
|
1633
|
+
zf: zipfile.ZipFile, info: zipfile.ZipInfo
|
|
1634
|
+
) -> Optional[str]:
|
|
1635
|
+
"""Refuse an entry that the archive gives more than ONE name, else ``None``.
|
|
1636
|
+
|
|
1637
|
+
THE place every name-rewriting route is collected, so closing one cannot leave
|
|
1638
|
+
another open. There are four, and each is invisible to the others:
|
|
1483
1639
|
|
|
1484
1640
|
1. ``orig_filename != filename`` — the reader rewrote the central name. A NUL
|
|
1485
1641
|
byte does this: ``templates/x/.thumbnail\\x00.exe`` is truncated to
|
|
1486
1642
|
``templates/x/.thumbnail``, which matches the thumbnail allowlist.
|
|
1487
|
-
2.
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1643
|
+
2. A ``0x7075`` record in the CENTRAL extra declaring another name. This defeats
|
|
1644
|
+
route 1 entirely, because the stdlib sanitizes the declared name before
|
|
1645
|
+
assigning it, leaving the two attributes equal.
|
|
1646
|
+
3. The LOCAL file header's name disagreeing with the central directory's.
|
|
1647
|
+
CPython does compare these, but only inside ``ZipFile.open`` — i.e. only for
|
|
1648
|
+
entries whose bytes are actually read, and only as a ``BadZipFile`` rather
|
|
1649
|
+
than as something the user can act on. Refusing here covers every entry and
|
|
1650
|
+
says what is wrong.
|
|
1651
|
+
4. A ``0x7075`` record in the LOCAL extra. This defeats routes 1-3 together: the
|
|
1652
|
+
central directory is entirely clean, so nothing CPython parses is out of
|
|
1653
|
+
place, while a reader that trusts local headers sees the other name.
|
|
1654
|
+
|
|
1655
|
+
Checked in that order, so the reported reason names the shallowest defect the
|
|
1656
|
+
entry has rather than the deepest.
|
|
1657
|
+
|
|
1658
|
+
All four presuppose that the archive records a name at all, which is why the EMPTY
|
|
1659
|
+
recorded name is refused ahead of them: an entry with no central-directory name has
|
|
1660
|
+
no identity for this function to establish, and every comparison below would be
|
|
1661
|
+
against ``""``. It is reported as the empty name it is rather than as a mismatch
|
|
1662
|
+
with whatever CPython resolved, because that is the fact the user can act on.
|
|
1663
|
+
"""
|
|
1664
|
+
central = _raw_entry_name(info)
|
|
1665
|
+
if not central:
|
|
1666
|
+
return _REASON_EMPTY_RECORDED_NAME
|
|
1667
|
+
if central != info.filename:
|
|
1668
|
+
return _REASON_NAME_MISMATCH
|
|
1669
|
+
reason = _extra_field_name_refusal(info.extra or b"", central)
|
|
1670
|
+
if reason is not None:
|
|
1671
|
+
return reason
|
|
1672
|
+
local = _local_header_identity(zf, info)
|
|
1673
|
+
if local is None:
|
|
1674
|
+
return _REASON_LOCAL_HEADER
|
|
1675
|
+
local_name, local_extra = local
|
|
1676
|
+
if local_name != central:
|
|
1677
|
+
return _REASON_NAME_MISMATCH
|
|
1678
|
+
return _extra_field_name_refusal(local_extra, central)
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
def _entry_verdict_for_info(
|
|
1682
|
+
zf: zipfile.ZipFile, info: zipfile.ZipInfo
|
|
1683
|
+
) -> _EntryVerdict:
|
|
1684
|
+
"""The verdict for one ZipInfo, judged on the name the archive really records.
|
|
1685
|
+
|
|
1686
|
+
The whole-bundle scan's composition of the two shared steps, over the FULL recorded
|
|
1687
|
+
name. :func:`_iter_safe_entries` applies the very same two steps in the very same
|
|
1688
|
+
order but cannot call this, because it has to interleave its root-prefix scoping
|
|
1689
|
+
between them: identity is judged for every member, scoping then decides which
|
|
1690
|
+
entries this import stores, and only those are classified — on the STRIPPED path,
|
|
1691
|
+
because the template-thumbnail allowlist is anchored at ``templates/``. Identity is
|
|
1692
|
+
judged on the full recorded name in both gates, that being the string a rewrite
|
|
1693
|
+
would have altered.
|
|
1694
|
+
|
|
1695
|
+
IDENTITY FIRST, then the path rules. :func:`_entry_identity_refusal` establishes
|
|
1696
|
+
that the entry has exactly ONE name — across both of the archive's copies of it —
|
|
1697
|
+
and only then is that name classified. The order is the point: validating a path
|
|
1698
|
+
that the archive states twice validates whichever copy this reader happened to
|
|
1699
|
+
resolve, while ``zf.read``/``namelist`` and the rest of the importer key off
|
|
1700
|
+
``filename``. Refusing any divergence keeps one name per entry everywhere, which
|
|
1701
|
+
is the same property the backslash refusal buys (see
|
|
1496
1702
|
:func:`_entry_path_verdict`).
|
|
1703
|
+
|
|
1704
|
+
``zf`` is needed because half of what an archive says about an entry lives in the
|
|
1705
|
+
local file header, which no ``ZipInfo`` attribute exposes.
|
|
1497
1706
|
"""
|
|
1498
|
-
|
|
1499
|
-
if
|
|
1500
|
-
return _EntryVerdict(_ENTRY_REFUSE, reason=
|
|
1501
|
-
|
|
1502
|
-
if extra_reason is not None:
|
|
1503
|
-
return _EntryVerdict(_ENTRY_REFUSE, reason=extra_reason)
|
|
1504
|
-
return _classify_bundle_entry(raw if rel_path is None else rel_path)
|
|
1707
|
+
identity_reason = _entry_identity_refusal(zf, info)
|
|
1708
|
+
if identity_reason is not None:
|
|
1709
|
+
return _EntryVerdict(_ENTRY_REFUSE, reason=identity_reason)
|
|
1710
|
+
return _classify_bundle_entry(_raw_entry_name(info))
|
|
1505
1711
|
|
|
1506
1712
|
|
|
1507
1713
|
def _is_symlink(info: zipfile.ZipInfo) -> bool:
|
|
@@ -1558,15 +1764,22 @@ def _assert_bundle_paths_safe(zf: zipfile.ZipFile) -> None:
|
|
|
1558
1764
|
|
|
1559
1765
|
THIS GATE ESTABLISHES THE INVARIANT THE REST OF THE IMPORTER RELIES ON. It runs
|
|
1560
1766
|
before root discovery and before any read (see :func:`import_bundle`), and it
|
|
1561
|
-
refuses any entry
|
|
1562
|
-
|
|
1563
|
-
the
|
|
1564
|
-
:func:`
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1767
|
+
refuses any entry the archive gives more than one name — whether the divergence is
|
|
1768
|
+
between ``orig_filename`` and ``filename``, between the two headers that record
|
|
1769
|
+
the entry, or in a name-rewriting extra field in either of them
|
|
1770
|
+
(:func:`_entry_identity_refusal`) — as well as any name containing a ``\\``.
|
|
1771
|
+
Everything downstream — :func:`_locate_root_prefix`, the
|
|
1772
|
+
``startswith(root_prefix)`` scoping and slicing in :func:`_iter_safe_entries`, and
|
|
1773
|
+
``zf.read``-by-name — therefore works on names that are exactly what validation
|
|
1774
|
+
judged. Weakening any of those refusals reintroduces two path identities in one
|
|
1775
|
+
importer.
|
|
1776
|
+
|
|
1777
|
+
The verdict comes from :func:`_entry_verdict_for_info`, which composes the SAME two
|
|
1778
|
+
shared steps :func:`_iter_safe_entries` applies —
|
|
1779
|
+
:func:`_entry_identity_refusal` then :func:`_classify_bundle_entry` — so the two
|
|
1780
|
+
gates cannot disagree about an entry. The iterator runs them either side of its own
|
|
1781
|
+
root-prefix scoping, which is why it composes them itself rather than calling this
|
|
1782
|
+
gate's helper; the classifiers are shared, so there is still exactly one of each.
|
|
1570
1783
|
"""
|
|
1571
1784
|
claimed: dict[str, str] = {}
|
|
1572
1785
|
for info in zf.infolist():
|
|
@@ -1579,7 +1792,7 @@ def _assert_bundle_paths_safe(zf: zipfile.ZipFile) -> None:
|
|
|
1579
1792
|
f"Bundle entry {name!r} is a symlink; refusing to import."
|
|
1580
1793
|
)
|
|
1581
1794
|
# No ``if name`` guard: an empty name is refused, not skipped.
|
|
1582
|
-
verdict = _entry_verdict_for_info(info)
|
|
1795
|
+
verdict = _entry_verdict_for_info(zf, info)
|
|
1583
1796
|
if verdict.kind == _ENTRY_REFUSE:
|
|
1584
1797
|
raise DesignSystemImportError(_path_refusal_message(name, verdict.reason))
|
|
1585
1798
|
if verdict.kind == _ENTRY_FILE:
|
|
@@ -1594,7 +1807,8 @@ def _iter_safe_entries(
|
|
|
1594
1807
|
"""Yield ``(ZipInfo, safe_rel_path)`` for every in-scope bundle FILE entry.
|
|
1595
1808
|
|
|
1596
1809
|
Skips directories, OS junk (``__MACOSX``, ``.DS_Store``) and dotfiles. Raises
|
|
1597
|
-
:class:`DesignSystemImportError` on
|
|
1810
|
+
:class:`DesignSystemImportError` on an in-scope zip-slip path (absolute or ``..``),
|
|
1811
|
+
and on ANY entry the archive gives more than one name wherever it sits, so a
|
|
1598
1812
|
malicious bundle is rejected rather than silently stored.
|
|
1599
1813
|
|
|
1600
1814
|
This is the ACTUAL first gate every entry passes, so the ONE dot-prefixed shape
|
|
@@ -1603,12 +1817,19 @@ def _iter_safe_entries(
|
|
|
1603
1817
|
the recognizer alone reads as working in a unit test and still drops every real
|
|
1604
1818
|
thumbnail, because the entry never reaches the recognizer.
|
|
1605
1819
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
is
|
|
1820
|
+
Judged in TWO STEPS, in the same order :func:`_entry_verdict_for_info` applies them
|
|
1821
|
+
for the up-front scan, with the scoping filter interleaved between the two:
|
|
1822
|
+
:func:`_entry_identity_refusal` for every member, then
|
|
1823
|
+
:func:`_classify_bundle_entry` for the ones this import is scoped to. Both halves
|
|
1824
|
+
are the SHARED helpers, so neither gate can be patched into disagreeing with the
|
|
1825
|
+
other. Interleaving is what keeps the two halves' different natures straight: an
|
|
1826
|
+
entry outside the root is legitimately none of this import's business, while an
|
|
1827
|
+
entry the archive names twice is hostile wherever it sits — see the comments at the
|
|
1828
|
+
two steps.
|
|
1829
|
+
|
|
1830
|
+
Stripping ``root_prefix`` is the only work done here, and it is bookkeeping rather
|
|
1831
|
+
than a path rule: it is a raw slice, which is sound precisely because no validated
|
|
1832
|
+
name is ever a rewritten one (no ``\\`` fold, no stdlib truncation).
|
|
1612
1833
|
|
|
1613
1834
|
Two in-scope entries claiming ONE path are refused here as well as by the up-front
|
|
1614
1835
|
scan, so every path this yields is DISTINCT and callers do not need a first-wins
|
|
@@ -1625,12 +1846,45 @@ def _iter_safe_entries(
|
|
|
1625
1846
|
claimed: dict[str, str] = {}
|
|
1626
1847
|
for info in zf.infolist():
|
|
1627
1848
|
name = _raw_entry_name(info)
|
|
1849
|
+
# IDENTITY BEFORE SCOPE, for EVERY member. Whether the archive gives this entry
|
|
1850
|
+
# ONE name is a fact about the entry, true wherever it sits, so it is settled
|
|
1851
|
+
# before the scoping below decides whether this import has any interest in it.
|
|
1852
|
+
# Ordering these the other way round left the iterator judging only what the
|
|
1853
|
+
# scope filter let through, and an entry it dropped was never judged at all: an
|
|
1854
|
+
# EMPTY recorded name is "in scope" of every prefix and strips to nothing, so
|
|
1855
|
+
# ``not rel_raw`` SKIPPED it — the one shape that is hostile precisely because
|
|
1856
|
+
# the archive records no name for it.
|
|
1857
|
+
#
|
|
1858
|
+
# Nothing was exploitable: :func:`_assert_bundle_paths_safe` runs first on every
|
|
1859
|
+
# production path and refuses such an entry before root discovery or any read.
|
|
1860
|
+
# What was lost is that this gate no longer refused INDEPENDENTLY, which is the
|
|
1861
|
+
# entire value of having two.
|
|
1862
|
+
identity_reason = _entry_identity_refusal(zf, info)
|
|
1863
|
+
if identity_reason is not None:
|
|
1864
|
+
raise DesignSystemImportError(
|
|
1865
|
+
_path_refusal_message(name, identity_reason)
|
|
1866
|
+
)
|
|
1867
|
+
# SCOPE, unchanged and still SILENT. An entry outside the discovered root is
|
|
1868
|
+
# not one of the entries this import stores, so it is passed over rather than
|
|
1869
|
+
# refused — including one whose PATH is unsafe, which the whole-bundle scan
|
|
1870
|
+
# already refuses GLOBALLY (see :func:`_assert_bundle_paths_safe`). Raising here
|
|
1871
|
+
# on an out-of-scope path would widen this generator from "the entries this
|
|
1872
|
+
# import stores" to "every entry in the archive", which is a different contract
|
|
1873
|
+
# from the one its caller has. Identity is not that: it is judged above because
|
|
1874
|
+
# no root prefix makes a second name acceptable.
|
|
1628
1875
|
if not name.startswith(root_prefix):
|
|
1629
1876
|
continue
|
|
1630
1877
|
rel_raw = name[len(root_prefix):]
|
|
1631
1878
|
if not rel_raw:
|
|
1632
1879
|
continue # the root-prefix directory entry itself
|
|
1633
|
-
|
|
1880
|
+
# The STRIPPED path is what gets classified — the reason this is
|
|
1881
|
+
# :func:`_classify_bundle_entry` directly rather than
|
|
1882
|
+
# :func:`_entry_verdict_for_info`, whose identity half has already run above.
|
|
1883
|
+
# The template-thumbnail allowlist is anchored at ``templates/``, so a wrapped
|
|
1884
|
+
# bundle's ``safe/templates/x/.thumbnail`` only matches once ``safe/`` is off;
|
|
1885
|
+
# the identity check above deliberately used the FULL recorded name instead,
|
|
1886
|
+
# which is the string a rewrite would have altered.
|
|
1887
|
+
verdict = _classify_bundle_entry(rel_raw)
|
|
1634
1888
|
if verdict.kind == _ENTRY_REFUSE:
|
|
1635
1889
|
raise DesignSystemImportError(
|
|
1636
1890
|
_path_refusal_message(rel_raw, verdict.reason)
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/databricks_tellr_app/run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/_otel_bootstrap.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/middleware/__init__.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/middleware/csrf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/__init__.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/_authz.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/admin.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/admin_usage.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/agent_config.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/chat.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/export.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/feedback.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/images.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/profiles.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/sessions.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/setup.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/slides.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/tools.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/tour.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/verification.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/routes/version.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/feedback.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/requests.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/responses.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/schemas/streaming.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/services/job_queue.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/utils/__init__.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/api/utils/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/config_loader.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/context_utils.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/databricks_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/encryption.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/mlflow_agent_spans.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/mlflow_tracing.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/otel_logging.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/permission_context.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/prompt_modules.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/settings_db.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/core/user_context.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/image.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/profile.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/prompts.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/database/models/session.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/domain/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/domain/slide_deck.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/agent_factory.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/config_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/drive_uploader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/genie_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/html_to_pptx.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/image_service.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/image_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/tools/mcp_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/services/validator.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/css_utils.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/ds_asset_utils.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/error_handling.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/html_safety.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/html_utils.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/image_utils.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/js_validator.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/logging_config.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/pi_filter.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/slide_hash.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/spotlight.py
RENAMED
|
File without changes
|
{databricks_tellr_app-0.4.2.dev12 → databricks_tellr_app-0.4.2.dev13}/src/utils/text_caps.py
RENAMED
|
File without changes
|