unique-search-proxy 2026.42.0.dev0__tar.gz → 2026.42.0.dev2__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.
Files changed (114) hide show
  1. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/PKG-INFO +11 -3
  2. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/README.md +8 -0
  3. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/pyproject.toml +3 -3
  4. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/service.py +19 -9
  5. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/monitoring/metrics.py +12 -0
  6. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/monitoring/setup.py +6 -1
  7. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/__init__.py +0 -0
  8. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/__init__.py +0 -0
  9. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/__init__.py +0 -0
  10. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/health.py +0 -0
  11. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/__init__.py +0 -0
  12. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/agent_search.py +0 -0
  13. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/configuration.py +0 -0
  14. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/context_headers.py +0 -0
  15. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/crawl.py +0 -0
  16. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/openapi_examples.py +0 -0
  17. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/api/v1/search.py +0 -0
  18. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/app.py +0 -0
  19. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/context.py +0 -0
  20. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/__init__.py +0 -0
  21. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/__init__.py +0 -0
  22. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/bing/cleanup.py +0 -0
  23. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/bing/client.py +0 -0
  24. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/bing/runner.py +0 -0
  25. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/bing/service.py +0 -0
  26. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/factory.py +0 -0
  27. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/service_base.py +0 -0
  28. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/structured_output.py +0 -0
  29. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/vertexai/client.py +0 -0
  30. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/vertexai/gemini.py +0 -0
  31. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/agent_engines/vertexai/service.py +0 -0
  32. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/client/__init__.py +0 -0
  33. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/client/credentials.py +0 -0
  34. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/client/service.py +0 -0
  35. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/__init__.py +0 -0
  36. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/__init__.py +0 -0
  37. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/__init__.py +0 -0
  38. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/errors.py +0 -0
  39. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/html_markdown.py +0 -0
  40. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/__init__.py +0 -0
  41. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/html.py +0 -0
  42. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/pdf.py +0 -0
  43. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/processors/plain_text.py +0 -0
  44. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/processing/registry.py +0 -0
  45. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/settings.py +0 -0
  46. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/basic/user_agent.py +0 -0
  47. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/factory.py +0 -0
  48. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/firecrawl/polling.py +0 -0
  49. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/firecrawl/request_body.py +0 -0
  50. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/firecrawl/service.py +0 -0
  51. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/jina/request_body.py +0 -0
  52. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/jina/service.py +0 -0
  53. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/pinned_egress.py +0 -0
  54. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/tavily/request_body.py +0 -0
  55. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/crawlers/tavily/service.py +0 -0
  56. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/provider_response.py +0 -0
  57. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/providers.py +0 -0
  58. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/registry.py +0 -0
  59. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/__init__.py +0 -0
  60. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/brave/__init__.py +0 -0
  61. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/brave/pagination.py +0 -0
  62. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/brave/query_params.py +0 -0
  63. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/brave/service.py +0 -0
  64. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/descriptor.py +0 -0
  65. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/factory.py +0 -0
  66. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/google/__init__.py +0 -0
  67. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/google/pagination.py +0 -0
  68. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/google/query_params.py +0 -0
  69. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/google/service.py +0 -0
  70. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/pagination.py +0 -0
  71. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/perplexity/__init__.py +0 -0
  72. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/perplexity/request_body.py +0 -0
  73. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/perplexity/service.py +0 -0
  74. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/search_engines/service_base.py +0 -0
  75. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/url_safety/__init__.py +0 -0
  76. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/core/url_safety/gate.py +0 -0
  77. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/error_handlers.py +0 -0
  78. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/__init__.py +0 -0
  79. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/generator/__init__.py +0 -0
  80. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/generator/introspect.py +0 -0
  81. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/generator/schema.py +0 -0
  82. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/generator/template.py +0 -0
  83. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/generator/templates/provider_values_yaml.j2 +0 -0
  84. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/generator/values_yaml.py +0 -0
  85. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/metadata.py +0 -0
  86. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/registry.py +0 -0
  87. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/helm/url_safety_registration.py +0 -0
  88. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/logging_config.py +0 -0
  89. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/middleware/context.py +0 -0
  90. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/monitoring/__init__.py +0 -0
  91. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/presets/__init__.py +0 -0
  92. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/presets/common.py +0 -0
  93. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/presets/crawl.py +0 -0
  94. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/presets/search.py +0 -0
  95. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/presets/types.py +0 -0
  96. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/__init__.py +0 -0
  97. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/app.py +0 -0
  98. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/base.py +0 -0
  99. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/client.py +0 -0
  100. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/monitoring.py +0 -0
  101. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/__init__.py +0 -0
  102. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/base.py +0 -0
  103. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/bing_agent.py +0 -0
  104. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/brave.py +0 -0
  105. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/firecrawl.py +0 -0
  106. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/google.py +0 -0
  107. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/jina.py +0 -0
  108. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/perplexity.py +0 -0
  109. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/tavily.py +0 -0
  110. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/providers/vertexai_agent.py +0 -0
  111. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/settings/secret_str.py +0 -0
  112. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/startup_report.py +0 -0
  113. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/utils/__init__.py +0 -0
  114. {unique_search_proxy-2026.42.0.dev0 → unique_search_proxy-2026.42.0.dev2}/unique_search_proxy_client/web/utils/url.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: unique-search-proxy
3
- Version: 2026.42.0.dev0
3
+ Version: 2026.42.0.dev2
4
4
  Summary: Web Search Proxy implementation
5
5
  Author: ThePhilAz
6
6
  Author-email: ThePhilAz <rami.azouz@philico.com>
@@ -19,8 +19,8 @@ Requires-Dist: certifi>=2025.11.12,<2027
19
19
  Requires-Dist: google-genai>=1.73.0,<2
20
20
  Requires-Dist: google-auth>=2.43.0,<3
21
21
  Requires-Dist: jinja2>=3.1.6,<4
22
- Requires-Dist: unique-toolkit[monitoring]>=2026.42.0.dev0,<2026.42.0rc0
23
- Requires-Dist: unique-search-proxy-core>=2026.42.0.dev0,<2026.42.0rc0
22
+ Requires-Dist: unique-toolkit[monitoring]>=2026.42.0.dev13,<2026.42.0rc0
23
+ Requires-Dist: unique-search-proxy-core>=2026.42.0.dev2,<2026.42.0rc0
24
24
  Requires-Dist: openai>=1.105.0,<3
25
25
  Requires-Python: >=3.12
26
26
  Description-Content-Type: text/markdown
@@ -237,8 +237,16 @@ Settings use pydantic-settings with per-provider env vars. Copy `.env.example` t
237
237
  | VertexAI agent | `VERTEXAI_AGENT_` | `VERTEXAI_AGENT_SERVICE_ACCOUNT_CREDENTIALS` (optional) |
238
238
  | HTTP client | `HTTP_CLIENT_` | `HTTP_CLIENT_PROXY_HOST`, `HTTP_CLIENT_POOL_TIMEOUT_SECONDS` |
239
239
  | Prometheus | `PROMETHEUS_` | `PROMETHEUS_ENABLED` |
240
+ | URL safety | `URL_SAFETY_` | `URL_SAFETY_ENABLED` |
240
241
  | Container | (shell) | `HOST`, `PORT`, `WORKERS`, `LOG_LEVEL` |
241
242
 
243
+ `URL_SAFETY_ENABLED` defaults to `true` when unset. Setting it to `false`
244
+ disables application-level SSRF checks and is accepted only when every request
245
+ uses a configured authenticated corporate proxy; incompatible direct or partial
246
+ proxy routing fails during startup. The application cannot verify that the
247
+ proxy implements equivalent URL-safety controls, so this state is highlighted
248
+ in the Prometheus metric and Grafana dashboard.
249
+
242
250
  With `WORKERS > 1`, the entrypoint sets `PROMETHEUS_MULTIPROC_DIR` for correct metric aggregation.
243
251
 
244
252
  ---
@@ -210,8 +210,16 @@ Settings use pydantic-settings with per-provider env vars. Copy `.env.example` t
210
210
  | VertexAI agent | `VERTEXAI_AGENT_` | `VERTEXAI_AGENT_SERVICE_ACCOUNT_CREDENTIALS` (optional) |
211
211
  | HTTP client | `HTTP_CLIENT_` | `HTTP_CLIENT_PROXY_HOST`, `HTTP_CLIENT_POOL_TIMEOUT_SECONDS` |
212
212
  | Prometheus | `PROMETHEUS_` | `PROMETHEUS_ENABLED` |
213
+ | URL safety | `URL_SAFETY_` | `URL_SAFETY_ENABLED` |
213
214
  | Container | (shell) | `HOST`, `PORT`, `WORKERS`, `LOG_LEVEL` |
214
215
 
216
+ `URL_SAFETY_ENABLED` defaults to `true` when unset. Setting it to `false`
217
+ disables application-level SSRF checks and is accepted only when every request
218
+ uses a configured authenticated corporate proxy; incompatible direct or partial
219
+ proxy routing fails during startup. The application cannot verify that the
220
+ proxy implements equivalent URL-safety controls, so this state is highlighted
221
+ in the Prometheus metric and Grafana dashboard.
222
+
215
223
  With `WORKERS > 1`, the entrypoint sets `PROMETHEUS_MULTIPROC_DIR` for correct metric aggregation.
216
224
 
217
225
  ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "unique-search-proxy"
3
- version = "2026.42.0.dev0"
3
+ version = "2026.42.0.dev2"
4
4
  description = "Web Search Proxy implementation"
5
5
  authors = [{ name = "ThePhilAz", email = "rami.azouz@philico.com" }]
6
6
  readme = "README.md"
@@ -21,8 +21,8 @@ dependencies = [
21
21
  "google-genai>=1.73.0,<2",
22
22
  "google-auth>=2.43.0,<3",
23
23
  "jinja2>=3.1.6,<4",
24
- "unique-toolkit[monitoring]>=2026.42.0.dev0,<2026.42.0rc0",
25
- "unique-search-proxy-core>=2026.42.0.dev0,<2026.42.0rc0",
24
+ "unique-toolkit[monitoring]>=2026.42.0.dev13,<2026.42.0rc0",
25
+ "unique-search-proxy-core>=2026.42.0.dev2,<2026.42.0rc0",
26
26
  "openai>=1.105.0,<3",
27
27
  ]
28
28
 
@@ -15,9 +15,11 @@ from unique_search_proxy_core.schema import (
15
15
  ProxyErrorCode,
16
16
  )
17
17
  from unique_search_proxy_core.url_safety import (
18
+ CrawlTargetValidationError,
18
19
  ResolvedCrawlTarget,
19
20
  bypass_crawl_target,
20
- pinned_httpx_get_args,
21
+ safe_pinned_httpx_get,
22
+ url_safety_settings,
21
23
  )
22
24
 
23
25
  from unique_search_proxy_client.web.core.crawlers.basic.processing import (
@@ -29,10 +31,12 @@ from unique_search_proxy_client.web.core.crawlers.basic.user_agent import (
29
31
  random_user_agent,
30
32
  )
31
33
  from unique_search_proxy_client.web.core.provider_response import (
34
+ crawl_forbidden_target,
32
35
  crawl_upstream_error,
33
36
  transport_error_raw,
34
37
  )
35
38
  from unique_search_proxy_client.web.core.url_safety.gate import AllowedCrawlTarget
39
+ from unique_search_proxy_client.web.monitoring.metrics import record_crawl_blocked
36
40
 
37
41
  _LOGGER = logging.getLogger(__name__)
38
42
 
@@ -101,18 +105,24 @@ class BasicCrawlerService(BaseCrawler[BasicCrawlRequest]):
101
105
  semaphore: asyncio.Semaphore,
102
106
  content_type_handlers: dict[str, ContentTypeHandlerPolicy],
103
107
  ) -> CrawlUrlResult:
104
- request_url, pin_headers, extensions = pinned_httpx_get_args(resolved_target)
105
108
  async with semaphore:
106
- headers = {"User-Agent": random_user_agent(), **pin_headers}
107
-
108
109
  try:
109
- response = await client.get(
110
- request_url,
111
- headers=headers,
112
- extensions=extensions or None,
110
+ response = await safe_pinned_httpx_get(
111
+ client,
112
+ resolved_target,
113
+ headers={"User-Agent": random_user_agent()},
113
114
  timeout=Timeout(timeout),
114
- follow_redirects=True,
115
+ max_redirect_hops=url_safety_settings.max_redirect_hops,
116
+ enforce_url_safety=url_safety_settings.enabled,
117
+ )
118
+ except CrawlTargetValidationError as exc:
119
+ blocked = exc.blocked_targets[0]
120
+ record_crawl_blocked(blocked.category)
121
+ _LOGGER.warning(
122
+ "Basic crawl blocked redirect: %s",
123
+ blocked.category,
115
124
  )
125
+ return crawl_forbidden_target(display_url, blocked.reason)
116
126
  except httpx.TimeoutException as exc:
117
127
  _LOGGER.warning("Basic crawl timed out for %s: %s", display_url, exc)
118
128
  return crawl_upstream_error(
@@ -93,6 +93,12 @@ crawl_blocked_total = m.counter(
93
93
  "Crawl targets blocked by URL safety policy",
94
94
  ["reason_category"],
95
95
  )
96
+ url_safety_application_checks_enabled = m.gauge(
97
+ "url_safety_application_checks_enabled",
98
+ "Whether application-level URL safety checks are enabled (1) or delegated "
99
+ "to a configured corporate proxy (0)",
100
+ [],
101
+ )
96
102
 
97
103
  agent_search_duration_seconds = m.histogram(
98
104
  "agent_search_duration_seconds",
@@ -182,6 +188,12 @@ def record_crawl_blocked(reason_category: str, count: int = 1) -> None:
182
188
  crawl_blocked_total.labels(reason_category=reason_category).inc(count)
183
189
 
184
190
 
191
+ def set_url_safety_application_checks_enabled(enabled: bool) -> None:
192
+ if not _metrics_enabled():
193
+ return
194
+ url_safety_application_checks_enabled.set(int(enabled))
195
+
196
+
185
197
  def record_agent_search_success(engine: str, duration_seconds: float) -> None:
186
198
  if not _metrics_enabled():
187
199
  return
@@ -4,9 +4,13 @@ import logging
4
4
 
5
5
  from fastapi import FastAPI
6
6
  from fastapi.responses import PlainTextResponse
7
+ from unique_search_proxy_core.url_safety import url_safety_settings
7
8
 
8
9
  import unique_search_proxy_client.web.settings.monitoring as monitoring_settings
9
- from unique_search_proxy_client.web.monitoring.metrics import HTTP_LATENCY_BUCKETS
10
+ from unique_search_proxy_client.web.monitoring.metrics import (
11
+ HTTP_LATENCY_BUCKETS,
12
+ set_url_safety_application_checks_enabled,
13
+ )
10
14
 
11
15
  _LOGGER = logging.getLogger(__name__)
12
16
 
@@ -50,5 +54,6 @@ def setup_prometheus(app: FastAPI) -> bool:
50
54
  media_type="text/plain; version=0.0.4; charset=utf-8",
51
55
  )
52
56
 
57
+ set_url_safety_application_checks_enabled(url_safety_settings.enabled)
53
58
  _LOGGER.info("Prometheus metrics enabled at /metrics")
54
59
  return True