From dc31f466b3a279ce45b07b8d252ed033296e97de Mon Sep 17 00:00:00 2001 From: nuclide-research Date: Sun, 7 Jun 2026 18:40:53 -0500 Subject: [PATCH] presidio guardrail: require explicit URL scheme for PRESIDIO_*_API_BASE The previous behavior silently prepended `http://` to PRESIDIO_ANALYZER_API_BASE and PRESIDIO_ANONYMIZER_API_BASE when the operator-supplied value lacked a URL scheme. The maintainer comment said: "assume communicating over private network". This default sent the full unredacted prompt to the analyzer in plaintext over any non-loopback transit. Realized deployment shapes that fail: - Presidio in a separate Kubernetes namespace, no service mesh - Presidio in a separate VPC or VNet - Presidio behind a managed service (Azure Container Instances, AWS App Runner, etc.) reached over the public internet - Any case where the customer relies on Presidio for HIPAA / GDPR / PCI scoping but the analyzer hop crosses an untrusted network The redaction has not happened yet at the moment of transmission. Presidio is the entity that performs it. So plaintext on the wire IS the unredacted PII. This change refuses to guess. Both PRESIDIO_ANALYZER_API_BASE and PRESIDIO_ANONYMIZER_API_BASE must now include an explicit `https://` or `http://` scheme. The startup-time ValueError tells the operator how to set it: - `https://...` for direct TLS to a public or routable endpoint - `http://...` if encryption is handled at a lower layer (loopback, service mesh sidecar like Istio or Linkerd, Tailscale, etc.) Existing deployments that relied on the implicit `http://` default will hit this error at startup and must add a 7-character prefix to silence it. Loud breakage is preferable to silent PII leakage. No new dependencies, no test changes. The patch is to one branch each in the analyzer and anonymizer validation paths. --- .../guardrails/guardrail_hooks/presidio.py | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/presidio.py b/litellm/proxy/guardrails/guardrail_hooks/presidio.py index fc414ab7b54..e339cf45c80 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/presidio.py +++ b/litellm/proxy/guardrails/guardrail_hooks/presidio.py @@ -183,9 +183,20 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): self.presidio_analyzer_api_base.startswith("http://") or self.presidio_analyzer_api_base.startswith("https://") ): - # add http:// if unset, assume communicating over private network - e.g. render - self.presidio_analyzer_api_base = ( - "http://" + self.presidio_analyzer_api_base + # PRESIDIO_ANALYZER_API_BASE must include an explicit scheme. + # Previous behavior silently prepended http://, which sent the + # unredacted prompt to the analyzer in plaintext over any non- + # loopback transit (separate pod, namespace, VPC, or managed + # service). Refuse to guess so the operator makes a conscious + # choice between https:// (direct TLS) and http:// (encryption + # handled below by loopback, service mesh, Tailscale, etc.). + raise ValueError( + "PRESIDIO_ANALYZER_API_BASE must include a URL scheme " + "(`https://...` for direct TLS, or `http://...` if your " + "deployment encrypts at a lower layer such as loopback, " + "a service mesh, or Tailscale). The previous default of " + "silently prepending `http://` could send PII in plaintext " + "over any non-loopback transit." ) if self.presidio_anonymizer_api_base is None: @@ -196,9 +207,14 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): self.presidio_anonymizer_api_base.startswith("http://") or self.presidio_anonymizer_api_base.startswith("https://") ): - # add http:// if unset, assume communicating over private network - e.g. render - self.presidio_anonymizer_api_base = ( - "http://" + self.presidio_anonymizer_api_base + # See PRESIDIO_ANALYZER_API_BASE note above. Refuse to guess. + raise ValueError( + "PRESIDIO_ANONYMIZER_API_BASE must include a URL scheme " + "(`https://...` for direct TLS, or `http://...` if your " + "deployment encrypts at a lower layer such as loopback, " + "a service mesh, or Tailscale). The previous default of " + "silently prepending `http://` could send PII in plaintext " + "over any non-loopback transit." ) @asynccontextmanager