iatoolkit 2.46.1__tar.gz → 2.47.0__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.
- {iatoolkit-2.46.1/src/iatoolkit.egg-info → iatoolkit-2.47.0}/PKG-INFO +2 -1
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/requirements.txt +1 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/__init__.py +1 -1
- iatoolkit-2.47.0/src/iatoolkit/common/url_safety.py +163 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/util.py +12 -3
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/local_file_connector.py +27 -3
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/llm_client_service.py +14 -4
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/storage_service.py +27 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_main.js +62 -2
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/prompt_api_view.py +26 -6
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/tool_api_view.py +29 -9
- {iatoolkit-2.46.1 → iatoolkit-2.47.0/src/iatoolkit.egg-info}/PKG-INFO +2 -1
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit.egg-info/SOURCES.txt +1 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit.egg-info/requires.txt +1 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/LICENSE +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/pyproject.toml +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/readme.md +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/setup.cfg +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/base_company.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/cli_commands.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/bridge_validation.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/exceptions.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/memory_lint_trigger.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/model_registry.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/routes.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/secret_resolver.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/session_manager.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/company_registry.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/core.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/call_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/google_auth_client.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/inference_handler.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/llm_response.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/locales/en.yaml +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/locales/es.yaml +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/api_key_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/bridge_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/database_manager.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/document_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/memory_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/models.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/runtime_logging.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/api_key_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/attachment_policy_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/auth_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/benchmark_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/branding_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/company_context_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/configuration_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/context_builder_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/embedding_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/excel_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/file_processor_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/history_manager_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/http_tool_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/i18n_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/inference_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/jwt_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/knowledge_base_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/language_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/license_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/mail_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/mcp_token_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/memory_compiler_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/memory_lint_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/memory_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/memory_wiki_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/noop_memory_lint_trigger.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/operator_identity_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/contracts.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/parsers/validator.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/pdf_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/profile_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/prompt_resource_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/prompt_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/query_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/sql_dataset_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/sql_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/sql_source_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/structured_output_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/system_tools.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/telemetry_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/tenant_wiki_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/tool_output_contract.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/tool_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/visual_kb_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/visual_tool_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/warmup_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/web_search/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/services/web_search_service.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/documents.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/_company_header.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/_login_widget.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/account.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/base.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/change_password.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/chat.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/chat_modals.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/error.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/forgot_password.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/home_hosted_default.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/pdf/base.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/pdf/letter.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/pdf/report.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/pdf/simple.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/templates/signup.html +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/__init__.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/account_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/api_key_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/base_login_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/categories_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/change_password_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/chat_context_preview_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/chat_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/configuration_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/connectors_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/history_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/home_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/load_document_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/login_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/logout_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/memory_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/profile_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/prompt_context_preview_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/prompt_resource_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/rag_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/signup_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/static_page_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/users_api_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/views/verify_user_view.py +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
- {iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iatoolkit
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.47.0
|
|
4
4
|
Summary: IAToolkit
|
|
5
5
|
Author: Fernando Libedinsky
|
|
6
6
|
License-Expression: MIT
|
|
@@ -51,6 +51,7 @@ Requires-Dist: langchain==0.3.19
|
|
|
51
51
|
Requires-Dist: langchain-core==0.3.35
|
|
52
52
|
Requires-Dist: langchain-text-splitters==0.3.6
|
|
53
53
|
Requires-Dist: markdown2==2.5.3
|
|
54
|
+
Requires-Dist: nh3==0.3.7
|
|
54
55
|
Requires-Dist: openai==2.8.1
|
|
55
56
|
Requires-Dist: openpyxl==3.1.5
|
|
56
57
|
Requires-Dist: pandas==2.3.1
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
Shared SSRF guard for URLs that arrive from tenants or API callers and that the
|
|
8
|
+
server will then connect to itself: task callback URLs, attachment downloads,
|
|
9
|
+
MCP image fetches, and similar.
|
|
10
|
+
|
|
11
|
+
Checking only literal IPs (``https://10.0.0.5/...``) is not enough - an
|
|
12
|
+
attacker-controlled hostname can resolve to 10.x / 169.254.169.254 / ::1 just
|
|
13
|
+
as well, and a public URL can 302 to one. This module therefore:
|
|
14
|
+
|
|
15
|
+
* requires an absolute URL with an allowed scheme and a hostname;
|
|
16
|
+
* rejects well-known local names (localhost, *.local, *.localhost, *.internal);
|
|
17
|
+
* rejects literal IPs that are private, loopback, link-local, multicast,
|
|
18
|
+
reserved or unspecified;
|
|
19
|
+
* resolves hostnames and rejects them when ANY resolved address falls in those
|
|
20
|
+
ranges (fail-closed on a private answer; fail-open only when the name cannot
|
|
21
|
+
be resolved at all, since that request could not have reached anything);
|
|
22
|
+
* offers ``fetch_with_safe_redirects`` which never lets ``requests`` follow a
|
|
23
|
+
redirect blindly: every hop is re-validated with the same rules.
|
|
24
|
+
|
|
25
|
+
Validate-then-connect still leaves a small DNS-rebinding window (the resolver
|
|
26
|
+
may return a different address at connect time); closing it fully needs
|
|
27
|
+
connection-level IP pinning, which is out of scope here. Callers should also
|
|
28
|
+
disable redirects on the actual request or use ``fetch_with_safe_redirects``.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import ipaddress
|
|
34
|
+
import socket
|
|
35
|
+
from urllib.parse import ParseResult, urljoin, urlparse
|
|
36
|
+
|
|
37
|
+
import requests
|
|
38
|
+
|
|
39
|
+
from iatoolkit.common.exceptions import IAToolkitException
|
|
40
|
+
|
|
41
|
+
_BLOCKED_HOSTNAMES = {"localhost", "localhost.localdomain"}
|
|
42
|
+
_BLOCKED_HOST_SUFFIXES = (".local", ".localhost", ".internal")
|
|
43
|
+
_REDIRECT_STATUSES = {301, 302, 303, 307, 308}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def classify_ip(ip_value: ipaddress.IPv4Address | ipaddress.IPv6Address) -> str:
|
|
47
|
+
"""'blocked' for loopback/link-local/multicast/reserved/unspecified,
|
|
48
|
+
'private' for RFC1918 & friends, 'public' otherwise."""
|
|
49
|
+
if (
|
|
50
|
+
ip_value.is_loopback
|
|
51
|
+
or ip_value.is_link_local
|
|
52
|
+
or ip_value.is_multicast
|
|
53
|
+
or ip_value.is_reserved
|
|
54
|
+
or ip_value.is_unspecified
|
|
55
|
+
):
|
|
56
|
+
return "blocked"
|
|
57
|
+
if ip_value.is_private:
|
|
58
|
+
return "private"
|
|
59
|
+
# IPv4-mapped IPv6 (::ffff:10.0.0.1) must be judged by the embedded IPv4.
|
|
60
|
+
mapped = getattr(ip_value, "ipv4_mapped", None)
|
|
61
|
+
if mapped is not None:
|
|
62
|
+
return classify_ip(mapped)
|
|
63
|
+
return "public"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _to_ip_or_none(host: str):
|
|
67
|
+
try:
|
|
68
|
+
return ipaddress.ip_address(host)
|
|
69
|
+
except ValueError:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve_host_classifications(hostname: str) -> list[str] | None:
|
|
74
|
+
"""Resolves ``hostname`` and classifies every address. Returns None when the
|
|
75
|
+
name cannot be resolved (caller decides how to treat that)."""
|
|
76
|
+
try:
|
|
77
|
+
entries = socket.getaddrinfo(hostname, None)
|
|
78
|
+
except Exception:
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
classifications: list[str] = []
|
|
82
|
+
for entry in entries:
|
|
83
|
+
sockaddr = entry[4]
|
|
84
|
+
if not sockaddr:
|
|
85
|
+
continue
|
|
86
|
+
ip_value = _to_ip_or_none(str(sockaddr[0]).split("%", 1)[0])
|
|
87
|
+
if ip_value is not None:
|
|
88
|
+
classifications.append(classify_ip(ip_value))
|
|
89
|
+
return classifications
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def assert_public_http_url(
|
|
93
|
+
url: str,
|
|
94
|
+
*,
|
|
95
|
+
allowed_schemes: tuple[str, ...] = ("https",),
|
|
96
|
+
error_type: IAToolkitException.ErrorType = IAToolkitException.ErrorType.INVALID_PARAMETER,
|
|
97
|
+
label: str = "URL",
|
|
98
|
+
) -> ParseResult:
|
|
99
|
+
"""
|
|
100
|
+
Raises IAToolkitException(error_type) unless ``url`` is an absolute URL with
|
|
101
|
+
an allowed scheme whose host is a public internet address (literal or via
|
|
102
|
+
DNS). Returns the parsed URL on success.
|
|
103
|
+
"""
|
|
104
|
+
normalized = str(url or "").strip()
|
|
105
|
+
parsed = urlparse(normalized)
|
|
106
|
+
scheme = (parsed.scheme or "").strip().lower()
|
|
107
|
+
hostname = (parsed.hostname or "").strip().lower().rstrip(".")
|
|
108
|
+
|
|
109
|
+
if scheme not in allowed_schemes:
|
|
110
|
+
raise IAToolkitException(
|
|
111
|
+
error_type,
|
|
112
|
+
f"{label} must use {'/'.join(s.upper() for s in allowed_schemes)}.",
|
|
113
|
+
)
|
|
114
|
+
if not hostname:
|
|
115
|
+
raise IAToolkitException(error_type, f"{label} host is required.")
|
|
116
|
+
if hostname in _BLOCKED_HOSTNAMES or hostname.endswith(_BLOCKED_HOST_SUFFIXES):
|
|
117
|
+
raise IAToolkitException(error_type, f"{label} host is not allowed.")
|
|
118
|
+
|
|
119
|
+
ip_value = _to_ip_or_none(hostname)
|
|
120
|
+
if ip_value is not None:
|
|
121
|
+
if classify_ip(ip_value) != "public":
|
|
122
|
+
raise IAToolkitException(error_type, f"{label} host is not allowed.")
|
|
123
|
+
return parsed
|
|
124
|
+
|
|
125
|
+
classifications = resolve_host_classifications(hostname)
|
|
126
|
+
if classifications and any(kind != "public" for kind in classifications):
|
|
127
|
+
raise IAToolkitException(
|
|
128
|
+
error_type,
|
|
129
|
+
f"{label} host is not allowed (resolves to a private or reserved address).",
|
|
130
|
+
)
|
|
131
|
+
return parsed
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def fetch_with_safe_redirects(
|
|
135
|
+
url: str,
|
|
136
|
+
*,
|
|
137
|
+
allowed_schemes: tuple[str, ...] = ("https",),
|
|
138
|
+
error_type: IAToolkitException.ErrorType = IAToolkitException.ErrorType.INVALID_PARAMETER,
|
|
139
|
+
label: str = "URL",
|
|
140
|
+
max_redirects: int = 3,
|
|
141
|
+
**requests_kwargs,
|
|
142
|
+
) -> requests.Response:
|
|
143
|
+
"""
|
|
144
|
+
``requests.get`` that validates the initial URL and every redirect hop with
|
|
145
|
+
``assert_public_http_url`` instead of letting ``requests`` follow them
|
|
146
|
+
blindly (a public URL that 302s to http://169.254.169.254/ would otherwise
|
|
147
|
+
sail through). Extra kwargs (timeout, stream, headers, ...) are passed to
|
|
148
|
+
``requests.get``; ``allow_redirects`` is always forced to False.
|
|
149
|
+
"""
|
|
150
|
+
requests_kwargs.pop("allow_redirects", None)
|
|
151
|
+
current_url = str(url or "").strip()
|
|
152
|
+
for _ in range(max_redirects + 1):
|
|
153
|
+
assert_public_http_url(
|
|
154
|
+
current_url, allowed_schemes=allowed_schemes, error_type=error_type, label=label
|
|
155
|
+
)
|
|
156
|
+
response = requests.get(current_url, allow_redirects=False, **requests_kwargs)
|
|
157
|
+
location = response.headers.get("Location") if response.status_code in _REDIRECT_STATUSES else None
|
|
158
|
+
if not location:
|
|
159
|
+
return response
|
|
160
|
+
response.close()
|
|
161
|
+
current_url = urljoin(current_url, location)
|
|
162
|
+
|
|
163
|
+
raise IAToolkitException(error_type, f"{label} redirected too many times.")
|
|
@@ -9,7 +9,16 @@ from iatoolkit.common.exceptions import IAToolkitException
|
|
|
9
9
|
from flask import request
|
|
10
10
|
from injector import inject
|
|
11
11
|
import os
|
|
12
|
-
from jinja2 import
|
|
12
|
+
from jinja2 import FileSystemLoader
|
|
13
|
+
# Prompt templates are authored by tenant users through the GUI/API, not by the
|
|
14
|
+
# operator running this server, so they must render in a sandbox: a plain
|
|
15
|
+
# jinja2.Environment lets a template walk `cycler.__init__.__globals__.os` (or
|
|
16
|
+
# any object's __class__/__mro__/__subclasses__) straight into the interpreter
|
|
17
|
+
# and execute commands on the host. ImmutableSandboxedEnvironment blocks
|
|
18
|
+
# underscore-prefixed attribute access, unsafe callables, and state-mutating
|
|
19
|
+
# methods while leaving everything a prompt legitimately needs (variables,
|
|
20
|
+
# loops, conditionals, filters, includes) intact.
|
|
21
|
+
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
|
13
22
|
from datetime import datetime, date
|
|
14
23
|
from decimal import Decimal
|
|
15
24
|
import yaml
|
|
@@ -33,7 +42,7 @@ class Utility:
|
|
|
33
42
|
template_dir = os.path.dirname(template_pathname)
|
|
34
43
|
template_file = os.path.basename(template_pathname)
|
|
35
44
|
|
|
36
|
-
env =
|
|
45
|
+
env = ImmutableSandboxedEnvironment(loader=FileSystemLoader(template_dir))
|
|
37
46
|
template = env.get_template(template_file)
|
|
38
47
|
|
|
39
48
|
# Explicit kwargs should win over client_data to preserve rich objects
|
|
@@ -71,7 +80,7 @@ class Utility:
|
|
|
71
80
|
else:
|
|
72
81
|
loader = None # Sin loader, no se pueden incluir plantillas desde archivos.
|
|
73
82
|
|
|
74
|
-
env =
|
|
83
|
+
env = ImmutableSandboxedEnvironment(loader=loader)
|
|
75
84
|
template = env.from_string(template_string)
|
|
76
85
|
|
|
77
86
|
# Explicit kwargs should win over client_data to preserve rich objects
|
{iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/infra/connectors/local_file_connector.py
RENAMED
|
@@ -37,9 +37,33 @@ class LocalFileConnector(FileConnector):
|
|
|
37
37
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
38
38
|
f"Error procesando el directorio {self.directory}: {e}")
|
|
39
39
|
|
|
40
|
+
def _resolve_within_root(self, file_path: str) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Maps a storage key / path onto the filesystem and refuses anything that
|
|
43
|
+
escapes the connector's root directory. `file_path` may be relative (a
|
|
44
|
+
storage key) or absolute (the `path` values list_files() hands back);
|
|
45
|
+
either way the real, symlink-resolved target must stay under root -
|
|
46
|
+
otherwise a caller-controlled key like `../../etc/passwd` or
|
|
47
|
+
`/proc/self/environ` turns this connector into an arbitrary file
|
|
48
|
+
read/write/delete on the host.
|
|
49
|
+
"""
|
|
50
|
+
candidate = str(file_path or "").strip()
|
|
51
|
+
if not candidate or "\x00" in candidate:
|
|
52
|
+
raise IAToolkitException(IAToolkitException.ErrorType.INVALID_PARAMETER,
|
|
53
|
+
"Invalid storage path.")
|
|
54
|
+
|
|
55
|
+
root = os.path.realpath(self.directory)
|
|
56
|
+
target = candidate if os.path.isabs(candidate) else os.path.join(root, candidate)
|
|
57
|
+
resolved = os.path.realpath(target)
|
|
58
|
+
if resolved != root and not resolved.startswith(root + os.sep):
|
|
59
|
+
raise IAToolkitException(IAToolkitException.ErrorType.PERMISSION,
|
|
60
|
+
f"Storage path escapes the configured root directory: {file_path}")
|
|
61
|
+
return resolved
|
|
62
|
+
|
|
40
63
|
def get_file_content(self, file_path: str) -> bytes:
|
|
64
|
+
full_path = self._resolve_within_root(file_path)
|
|
41
65
|
try:
|
|
42
|
-
with open(
|
|
66
|
+
with open(full_path, 'rb') as f:
|
|
43
67
|
return f.read()
|
|
44
68
|
except Exception as e:
|
|
45
69
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR,
|
|
@@ -47,7 +71,7 @@ class LocalFileConnector(FileConnector):
|
|
|
47
71
|
|
|
48
72
|
def upload_file(self, file_path: str, content: bytes, content_type: str = None) -> None:
|
|
49
73
|
# Nota: file_path debe ser relativo al directorio raíz configurado
|
|
50
|
-
full_path =
|
|
74
|
+
full_path = self._resolve_within_root(file_path)
|
|
51
75
|
|
|
52
76
|
# Asegurar que el directorio destino existe
|
|
53
77
|
try:
|
|
@@ -59,7 +83,7 @@ class LocalFileConnector(FileConnector):
|
|
|
59
83
|
f"Error escribiendo el archivo {file_path}: {e}")
|
|
60
84
|
|
|
61
85
|
def delete_file(self, file_path: str) -> None:
|
|
62
|
-
full_path =
|
|
86
|
+
full_path = self._resolve_within_root(file_path)
|
|
63
87
|
try:
|
|
64
88
|
if os.path.exists(full_path):
|
|
65
89
|
os.remove(full_path)
|
|
@@ -12,6 +12,7 @@ from iatoolkit.common.model_registry import ModelRegistry
|
|
|
12
12
|
from injector import inject
|
|
13
13
|
import time
|
|
14
14
|
import markdown2
|
|
15
|
+
import nh3
|
|
15
16
|
import os
|
|
16
17
|
import logging
|
|
17
18
|
import json
|
|
@@ -1393,10 +1394,19 @@ class llmClient:
|
|
|
1393
1394
|
|
|
1394
1395
|
# Heurística simple: si contiene tags, lo tratamos como HTML ya renderizable
|
|
1395
1396
|
if re.search(r"</?[a-zA-Z][\s\S]*>", answer):
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1397
|
+
html_answer = answer.replace("\n", "")
|
|
1398
|
+
else:
|
|
1399
|
+
html_answer = markdown2.markdown(answer).replace("\n", "")
|
|
1400
|
+
|
|
1401
|
+
# The result is injected into the chat DOM as HTML (and stored, then shown
|
|
1402
|
+
# again in the admin monitoring inspector). The model echoes whatever it
|
|
1403
|
+
# was fed - SQL rows, RAG documents, tool results - so an <img onerror>,
|
|
1404
|
+
# <svg onload> or javascript: link planted in tenant data would otherwise
|
|
1405
|
+
# run in every viewer's browser, including admins'. nh3 is an allowlist
|
|
1406
|
+
# sanitizer: it keeps the formatting markdown2 produces (headings, lists,
|
|
1407
|
+
# tables, code, links, images) and strips scripts, event handlers,
|
|
1408
|
+
# iframes/objects and javascript:/data: URLs.
|
|
1409
|
+
return nh3.clean(html_answer)
|
|
1400
1410
|
|
|
1401
1411
|
def format_plaintext(self, answer: str):
|
|
1402
1412
|
if not answer:
|
|
@@ -234,8 +234,34 @@ class StorageService:
|
|
|
234
234
|
logging.error(error_msg)
|
|
235
235
|
raise IAToolkitException(IAToolkitException.ErrorType.FILE_IO_ERROR, error_msg)
|
|
236
236
|
|
|
237
|
+
@staticmethod
|
|
238
|
+
def _validate_storage_key(storage_key: str) -> str:
|
|
239
|
+
"""
|
|
240
|
+
Defensive check shared by every read/delete: a storage key must never
|
|
241
|
+
walk up with `..` (or carry NUL bytes). Absolute paths are deliberately
|
|
242
|
+
left to the connector - LocalFileConnector confines them to its root,
|
|
243
|
+
and its list_files() legitimately hands back absolute paths that flow
|
|
244
|
+
into here. Callers that accept keys from untrusted input additionally
|
|
245
|
+
enforce the `companies/<tenant>/` prefix (see
|
|
246
|
+
AttachmentResolutionService._validate_existing_storage_key); this guard
|
|
247
|
+
is the floor that applies even to internally sourced keys.
|
|
248
|
+
"""
|
|
249
|
+
normalized = str(storage_key or "").strip()
|
|
250
|
+
segments = normalized.replace("\\", "/").split("/")
|
|
251
|
+
if (
|
|
252
|
+
not normalized
|
|
253
|
+
or "\x00" in normalized
|
|
254
|
+
or any(segment in {".", ".."} for segment in segments)
|
|
255
|
+
):
|
|
256
|
+
raise IAToolkitException(
|
|
257
|
+
IAToolkitException.ErrorType.INVALID_PARAMETER,
|
|
258
|
+
"Invalid storage key.",
|
|
259
|
+
)
|
|
260
|
+
return normalized
|
|
261
|
+
|
|
237
262
|
def get_document_content(self, company_short_name: str, storage_key: str) -> bytes:
|
|
238
263
|
"""Retrieves raw content via the configured connector."""
|
|
264
|
+
storage_key = self._validate_storage_key(storage_key)
|
|
239
265
|
try:
|
|
240
266
|
connector = self._get_connector(company_short_name)
|
|
241
267
|
return connector.get_file_content(storage_key)
|
|
@@ -247,6 +273,7 @@ class StorageService:
|
|
|
247
273
|
"""
|
|
248
274
|
Deletes a document from the configured storage.
|
|
249
275
|
"""
|
|
276
|
+
storage_key = self._validate_storage_key(storage_key)
|
|
250
277
|
try:
|
|
251
278
|
connector = self._get_connector(company_short_name)
|
|
252
279
|
connector.delete_file(storage_key)
|
|
@@ -3,6 +3,65 @@ let isRequestInProgress = false;
|
|
|
3
3
|
let abortController = null;
|
|
4
4
|
let selectedPrompt = null; // Will hold a lightweight prompt object
|
|
5
5
|
|
|
6
|
+
// Allowlist-style sanitizer for the HTML answer returned by the backend. The
|
|
7
|
+
// server already sanitizes it (LLMClientService.format_html -> nh3), but this is
|
|
8
|
+
// the last line before .append()/.html() parse it into the live DOM, and the
|
|
9
|
+
// model echoes tenant data (SQL rows, RAG documents, tool results) where an
|
|
10
|
+
// <img onerror> or javascript: link could be planted. Parses into an inert
|
|
11
|
+
// document, drops executable elements, event-handler attributes and
|
|
12
|
+
// script-bearing URLs, and returns the surviving markup.
|
|
13
|
+
function sanitizeHtmlFragment(html) {
|
|
14
|
+
const source = String(html || '');
|
|
15
|
+
if (!source) return '';
|
|
16
|
+
if (typeof DOMParser === 'undefined') {
|
|
17
|
+
return $('<div>').text(source).html();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const BLOCKED_TAGS = new Set([
|
|
21
|
+
'script', 'style', 'iframe', 'frame', 'frameset', 'object', 'embed', 'applet',
|
|
22
|
+
'link', 'meta', 'base', 'form', 'input', 'button', 'textarea', 'select', 'option',
|
|
23
|
+
'svg', 'math', 'template', 'noscript',
|
|
24
|
+
]);
|
|
25
|
+
const URL_ATTRS = new Set(['href', 'src', 'xlink:href', 'action', 'formaction', 'srcset', 'poster', 'background', 'data']);
|
|
26
|
+
|
|
27
|
+
const isUnsafeUrl = (value, attrName, tagName) => {
|
|
28
|
+
// Strip whitespace/control chars browsers ignore ("java\tscript:").
|
|
29
|
+
const normalized = String(value || '').replace(/[\u0000-\u0020\u007f-\u00a0]/g, '').toLowerCase();
|
|
30
|
+
if (normalized.startsWith('javascript:') || normalized.startsWith('vbscript:')) return true;
|
|
31
|
+
if (normalized.startsWith('data:')) {
|
|
32
|
+
return !(tagName === 'img' && attrName === 'src' && normalized.startsWith('data:image/'));
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const doc = new DOMParser().parseFromString(source, 'text/html');
|
|
38
|
+
const walk = (node) => {
|
|
39
|
+
Array.from(node.children).forEach((el) => {
|
|
40
|
+
const tag = el.tagName.toLowerCase();
|
|
41
|
+
if (BLOCKED_TAGS.has(tag)) {
|
|
42
|
+
el.remove();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
Array.from(el.attributes).forEach((attr) => {
|
|
46
|
+
const name = attr.name.toLowerCase();
|
|
47
|
+
if (name.startsWith('on') || name === 'style' || name === 'srcdoc') {
|
|
48
|
+
el.removeAttribute(attr.name);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (URL_ATTRS.has(name) && isUnsafeUrl(attr.value, name, tag)) {
|
|
52
|
+
el.removeAttribute(attr.name);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
if (tag === 'a') {
|
|
56
|
+
el.setAttribute('rel', 'noopener noreferrer');
|
|
57
|
+
}
|
|
58
|
+
walk(el);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
walk(doc.body);
|
|
62
|
+
return doc.body.innerHTML;
|
|
63
|
+
}
|
|
64
|
+
|
|
6
65
|
$(document).ready(function () {
|
|
7
66
|
// Si viene un Token retornado por login con APY-KEY se gatilla el redeem a una sesion de flask
|
|
8
67
|
if (window.redeemToken) {
|
|
@@ -177,7 +236,7 @@ function processBotResponse(responseData) {
|
|
|
177
236
|
// A. Texto: Usamos 'answer' porque contiene el HTML procesado y limpio del backend.
|
|
178
237
|
// Evitamos usar content_parts[type=text] porque contiene el JSON crudo del LLM.
|
|
179
238
|
if (responseData.answer) {
|
|
180
|
-
answerSection.append(responseData.answer);
|
|
239
|
+
answerSection.append(sanitizeHtmlFragment(responseData.answer));
|
|
181
240
|
}
|
|
182
241
|
|
|
183
242
|
// B. Imágenes: Iteramos content_parts buscando SOLO imágenes para adjuntarlas.
|
|
@@ -207,7 +266,8 @@ function processBotResponse(responseData) {
|
|
|
207
266
|
toastr.error('Memoria no está disponible.');
|
|
208
267
|
return;
|
|
209
268
|
}
|
|
210
|
-
|
|
269
|
+
// Even a detached element parses <img src onerror> and fires it - sanitize first.
|
|
270
|
+
const plainText = $('<div>').html(sanitizeHtmlFragment(responseData.answer || '')).text().trim();
|
|
211
271
|
await window.saveItemToMemory({
|
|
212
272
|
item_type: 'chat_assistant_message',
|
|
213
273
|
content_text: plainText,
|
|
@@ -74,11 +74,31 @@ class PromptApiView(MethodView):
|
|
|
74
74
|
f"unexpected error getting company prompts: {e}")
|
|
75
75
|
return jsonify({"error_message": str(e)}), 500
|
|
76
76
|
|
|
77
|
+
def _require_admin_auth(self, company_short_name: str) -> dict | tuple:
|
|
78
|
+
"""
|
|
79
|
+
Prompt content is a Jinja template rendered server-side for every user
|
|
80
|
+
of the tenant, so writing it is an admin capability: even sandboxed, a
|
|
81
|
+
template author can shape what every agent says and does. Regular chat
|
|
82
|
+
users / plain API keys pass verify_for_company but must not get here.
|
|
83
|
+
"""
|
|
84
|
+
auth_result = self.auth_service.verify_for_company(company_short_name)
|
|
85
|
+
if not auth_result.get("success"):
|
|
86
|
+
status_code = auth_result.get("status_code", 401)
|
|
87
|
+
if status_code == 403:
|
|
88
|
+
return jsonify({"error": "Forbidden"}), 403
|
|
89
|
+
return jsonify(auth_result), status_code
|
|
90
|
+
|
|
91
|
+
role = str(auth_result.get("user_role") or "").strip().lower()
|
|
92
|
+
if role not in {"admin", "owner"}:
|
|
93
|
+
return jsonify({"error": "Forbidden"}), 403
|
|
94
|
+
|
|
95
|
+
return auth_result
|
|
96
|
+
|
|
77
97
|
def put(self, company_short_name, prompt_name):
|
|
78
98
|
try:
|
|
79
|
-
auth_result = self.
|
|
80
|
-
if
|
|
81
|
-
return
|
|
99
|
+
auth_result = self._require_admin_auth(company_short_name)
|
|
100
|
+
if isinstance(auth_result, tuple):
|
|
101
|
+
return auth_result
|
|
82
102
|
|
|
83
103
|
data = request.get_json()
|
|
84
104
|
|
|
@@ -93,9 +113,9 @@ class PromptApiView(MethodView):
|
|
|
93
113
|
def post(self, company_short_name, prompt_name=None):
|
|
94
114
|
"""Creates a new prompt."""
|
|
95
115
|
try:
|
|
96
|
-
auth_result = self.
|
|
97
|
-
if
|
|
98
|
-
return
|
|
116
|
+
auth_result = self._require_admin_auth(company_short_name)
|
|
117
|
+
if isinstance(auth_result, tuple):
|
|
118
|
+
return auth_result
|
|
99
119
|
|
|
100
120
|
data = request.get_json() or {}
|
|
101
121
|
|
|
@@ -67,14 +67,34 @@ class ToolApiView(MethodView):
|
|
|
67
67
|
logging.exception(f"Tool API Error (GET): {e}")
|
|
68
68
|
return jsonify({"error": "Internal server error"}), 500
|
|
69
69
|
|
|
70
|
+
def _require_admin_auth(self, company_short_name: str) -> dict | tuple:
|
|
71
|
+
"""
|
|
72
|
+
Creating or changing a tool defines code the agent will execute for every
|
|
73
|
+
user of the tenant (HTTP targets, inference endpoints, execution_config),
|
|
74
|
+
so it's an admin capability. Regular chat users and plain API keys pass
|
|
75
|
+
verify_for_company but must not be able to write tool definitions.
|
|
76
|
+
"""
|
|
77
|
+
auth_result = self.auth_service.verify_for_company(company_short_name)
|
|
78
|
+
if not auth_result.get("success"):
|
|
79
|
+
status_code = auth_result.get("status_code", 401)
|
|
80
|
+
if status_code == 403:
|
|
81
|
+
return jsonify({"error": "Forbidden"}), 403
|
|
82
|
+
return jsonify(auth_result), status_code
|
|
83
|
+
|
|
84
|
+
role = str(auth_result.get("user_role") or "").strip().lower()
|
|
85
|
+
if role not in {"admin", "owner"}:
|
|
86
|
+
return jsonify({"error": "Forbidden"}), 403
|
|
87
|
+
|
|
88
|
+
return auth_result
|
|
89
|
+
|
|
70
90
|
def post(self, company_short_name: str):
|
|
71
91
|
"""
|
|
72
92
|
POST /<company>/api/tools -> Create a new tool
|
|
73
93
|
Body: { "name": "...", "description": "...", "tool_type": "...", ... }
|
|
74
94
|
"""
|
|
75
|
-
auth_result = self.
|
|
76
|
-
if
|
|
77
|
-
return
|
|
95
|
+
auth_result = self._require_admin_auth(company_short_name)
|
|
96
|
+
if isinstance(auth_result, tuple):
|
|
97
|
+
return auth_result
|
|
78
98
|
|
|
79
99
|
try:
|
|
80
100
|
data = request.get_json() or {}
|
|
@@ -96,9 +116,9 @@ class ToolApiView(MethodView):
|
|
|
96
116
|
"""
|
|
97
117
|
PUT /<company>/api/tools/<id> -> Update an existing tool
|
|
98
118
|
"""
|
|
99
|
-
auth_result = self.
|
|
100
|
-
if
|
|
101
|
-
return
|
|
119
|
+
auth_result = self._require_admin_auth(company_short_name)
|
|
120
|
+
if isinstance(auth_result, tuple):
|
|
121
|
+
return auth_result
|
|
102
122
|
|
|
103
123
|
try:
|
|
104
124
|
data = request.get_json() or {}
|
|
@@ -123,9 +143,9 @@ class ToolApiView(MethodView):
|
|
|
123
143
|
"""
|
|
124
144
|
DELETE /<company>/api/tools/<id> -> Delete a tool
|
|
125
145
|
"""
|
|
126
|
-
auth_result = self.
|
|
127
|
-
if
|
|
128
|
-
return
|
|
146
|
+
auth_result = self._require_admin_auth(company_short_name)
|
|
147
|
+
if isinstance(auth_result, tuple):
|
|
148
|
+
return auth_result
|
|
129
149
|
|
|
130
150
|
try:
|
|
131
151
|
actor = auth_result.get("user_identifier")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iatoolkit
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.47.0
|
|
4
4
|
Summary: IAToolkit
|
|
5
5
|
Author: Fernando Libedinsky
|
|
6
6
|
License-Expression: MIT
|
|
@@ -51,6 +51,7 @@ Requires-Dist: langchain==0.3.19
|
|
|
51
51
|
Requires-Dist: langchain-core==0.3.35
|
|
52
52
|
Requires-Dist: langchain-text-splitters==0.3.6
|
|
53
53
|
Requires-Dist: markdown2==2.5.3
|
|
54
|
+
Requires-Dist: nh3==0.3.7
|
|
54
55
|
Requires-Dist: openai==2.8.1
|
|
55
56
|
Requires-Dist: openpyxl==3.1.5
|
|
56
57
|
Requires-Dist: pandas==2.3.1
|
|
@@ -21,6 +21,7 @@ src/iatoolkit/common/model_registry.py
|
|
|
21
21
|
src/iatoolkit/common/routes.py
|
|
22
22
|
src/iatoolkit/common/secret_resolver.py
|
|
23
23
|
src/iatoolkit/common/session_manager.py
|
|
24
|
+
src/iatoolkit/common/url_safety.py
|
|
24
25
|
src/iatoolkit/common/util.py
|
|
25
26
|
src/iatoolkit/common/interfaces/__init__.py
|
|
26
27
|
src/iatoolkit/common/interfaces/asset_storage.py
|
|
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
|
|
File without changes
|
|
File without changes
|
{iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py
RENAMED
|
File without changes
|
{iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/memory_lint_trigger.py
RENAMED
|
File without changes
|
|
File without changes
|
{iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py
RENAMED
|
File without changes
|
{iatoolkit-2.46.1 → iatoolkit-2.47.0}/src/iatoolkit/common/interfaces/web_search_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|