From c93062ed04ff273412b689e549c02b9e3179063f Mon Sep 17 00:00:00 2001 From: Jen Hamon Date: Sat, 11 Jul 2026 16:14:00 -0400 Subject: [PATCH] security(http_client): stop logging raw request body in PINECONE_DEBUG_CURL Fixes CodeQL py/clear-text-logging-sensitive-data alert #83 at pinecone/_internal/http_client.py _log_curl. The debug curl logger previously emitted the raw request body via `-d ''`. Pinecone request bodies carry user vector and metadata payloads that may contain sensitive data (e.g. PII in metadata), so the body is no longer logged verbatim even under the PINECONE_DEBUG_CURL opt-in. It is replaced with a `--data-binary @body.json # N byte body omitted` placeholder that records the size and shows where to supply the payload manually. Sensitive headers remain redacted as before. This removes the CodeQL finding at the source (no clear-text path to the logger) rather than dismissing it. Co-Authored-By: Claude Opus 4.8 (1M context) --- pinecone/_internal/http_client.py | 11 +++++++++-- tests/unit/test_debug_logging.py | 12 ++++++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/pinecone/_internal/http_client.py b/pinecone/_internal/http_client.py index a756b3fc..3fa65f84 100644 --- a/pinecone/_internal/http_client.py +++ b/pinecone/_internal/http_client.py @@ -104,7 +104,14 @@ def _log_curl( headers: dict[str, str], body: bytes | None = None, ) -> None: - """Log a curl-equivalent command for debugging when PINECONE_DEBUG_CURL is set.""" + """Log a curl-equivalent command for debugging when PINECONE_DEBUG_CURL is set. + + Sensitive header values (see ``_SENSITIVE_HEADERS``) are redacted. The + request body is intentionally *not* logged verbatim: Pinecone request bodies + carry user vector and metadata payloads that may contain sensitive data + (e.g. PII embedded in metadata). Only the body size is emitted, and a + ``--data-binary @body.json`` placeholder shows where to supply it manually. + """ if not os.environ.get("PINECONE_DEBUG_CURL"): return safe_headers = _redact_headers(headers) @@ -112,7 +119,7 @@ def _log_curl( for key, value in safe_headers.items(): parts.append(f"-H '{key}: {value}'") if body is not None: - parts.append(f"-d '{body.decode('utf-8', errors='replace')}'") + parts.append(f"--data-binary @body.json # {len(body)} byte body omitted") curl_cmd = " ".join(parts) logger.debug("curl equivalent:\n%s", curl_cmd) diff --git a/tests/unit/test_debug_logging.py b/tests/unit/test_debug_logging.py index 923506fe..f0d9927e 100644 --- a/tests/unit/test_debug_logging.py +++ b/tests/unit/test_debug_logging.py @@ -82,9 +82,11 @@ def test_curl_logging_redacts_api_key( assert "my-secret-key-12345" not in caplog.text assert "Api-Key: ***" in caplog.text - def test_curl_logging_includes_body( + def test_curl_logging_omits_body_contents( self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture ) -> None: + # The raw request body must never be logged: it can carry user vector + # and metadata payloads (potentially PII). Only the byte count is shown. monkeypatch.setenv("PINECONE_DEBUG_CURL", "1") body = b'{"vectors": [{"id": "v1"}]}' with caplog.at_level(logging.DEBUG, logger="pinecone._internal.http_client"): @@ -94,8 +96,8 @@ def test_curl_logging_includes_body( {"Api-Key": "test-key", "Content-Type": "application/json"}, body=body, ) - assert "-d " in caplog.text - assert '{"vectors": [{"id": "v1"}]}' in caplog.text + assert '{"vectors": [{"id": "v1"}]}' not in caplog.text + assert f"{len(body)} byte body omitted" in caplog.text def test_curl_logging_includes_all_headers( self, monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture @@ -144,7 +146,9 @@ def test_post_logs_curl_with_body( finally: client.close() assert "curl -X POST" in caplog.text - assert "-d " in caplog.text + assert "byte body omitted" in caplog.text + # Raw request payload must not appear in logs. + assert '"id": "v1"' not in caplog.text @respx.mock def test_no_curl_output_when_disabled(