Skip to content

Commit b93e81c

Browse files
CopilotnikhilNava
andcommitted
security: fix sensitive data logging, context leak, unbounded memory, asserts, and more
- Fix #1: Downgrade sensitive data logging from INFO to DEBUG in agent365_exporter.py - Fix #2: Fix unpaired context.attach() in opentelemetry_scope.py add_baggage() by storing and detaching baggage tokens on scope end - Fix #3: Add bounded OrderedDict caps to unbounded dicts in OpenAI trace_processor.py - Fix #4: Replace 30 assert statements with proper TypeError raises in LangChain utils.py - Fix #5: Log security warning when HTTP domain override is detected - Fix #6: Warn when bearer token sent over non-HTTPS connection - Fix #10: Respect Retry-After header and use exponential backoff in retries - Fix #13: Rename reset() to _reset() in ObservabilityHostingManager - Fix #15: Replace print() with logger.warning() in LangChain tracer_instrumentor.py Co-authored-by: nikhilNava <211831449+nikhilNava@users.noreply.github.com>
1 parent 113e17c commit b93e81c

6 files changed

Lines changed: 29 additions & 20 deletions

File tree

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/exporters/agent365_exporter.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,7 @@ def _post_with_retries(self, url: str, body: str, headers: dict[str, str]) -> bo
238238
continue
239239
# Final attempt failed
240240
logger.error(
241-
f"Request failed after {DEFAULT_MAX_RETRIES + 1} attempts: "
242-
f"{type(e).__name__}"
241+
f"Request failed after {DEFAULT_MAX_RETRIES + 1} attempts: {type(e).__name__}"
243242
)
244243
return False
245244
return False

libraries/microsoft-agents-a365-observability-core/microsoft_agents_a365/observability/core/exporters/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,13 @@ def get_validated_domain_override() -> str | None:
194194
logger.warning(f"Invalid domain override '{domain_override}': {e}")
195195
return None
196196

197+
# Warn when using insecure HTTP — telemetry data and bearer tokens may be exposed
198+
if domain_override.lower().startswith("http://"):
199+
logger.warning(
200+
"Domain override uses insecure HTTP. Telemetry data (including "
201+
"bearer tokens) will be transmitted in cleartext."
202+
)
203+
197204
return domain_override
198205

199206

libraries/microsoft-agents-a365-observability-extensions-langchain/microsoft_agents_a365/observability/extensions/langchain/tracer_instrumentor.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import annotations
55

6+
import logging
67
from collections.abc import Callable, Collection
78
from typing import Any
89
from uuid import UUID
@@ -21,6 +22,8 @@
2122

2223
from microsoft_agents_a365.observability.extensions.langchain.tracer import CustomLangChainTracer
2324

25+
logger = logging.getLogger(__name__)
26+
2427
_INSTRUMENTS: str = "langchain_core >= 1.2.0"
2528

2629

@@ -86,7 +89,7 @@ def _uninstrument(self, **kwargs: Any) -> None:
8689
def get_span(self, run_id: UUID) -> Span | None:
8790
"""Return the span for a specific LangChain run_id, if available."""
8891
if not self._tracer:
89-
print("Missing tracer; call InstrumentorForLangChain().instrument() first.")
92+
logger.warning("Missing tracer; call InstrumentorForLangChain().instrument() first.")
9093
return None
9194
# TraceForLangChain is expected to expose get_span(run_id).
9295
get_span_fn = getattr(self._tracer, "get_span", None)
@@ -95,7 +98,7 @@ def get_span(self, run_id: UUID) -> Span | None:
9598
def get_ancestors(self, run_id: UUID) -> list[Span]:
9699
"""Return ancestor spans from the run’s parent up to the root (nearest first)."""
97100
if not self._tracer:
98-
print("Missing tracer; call InstrumentorForLangChain().instrument() first.")
101+
logger.warning("Missing tracer; call InstrumentorForLangChain().instrument() first.")
99102
return []
100103

101104
# Expect the processor to keep a run_map with parent linkage (string keys).

libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/middleware/observability_hosting_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,6 @@ def configure(
9696
return instance
9797

9898
@classmethod
99-
def reset(cls) -> None:
99+
def _reset(cls) -> None:
100100
"""Reset the singleton instance. Intended for testing only."""
101101
cls._instance = None

tests/observability/core/test_agent365_exporter.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -313,25 +313,25 @@ def test_export_logging(self, mock_logger):
313313
self.assertEqual(result, SpanExportResult.SUCCESS)
314314

315315
# Verify logging calls - should use default endpoint URL
316-
expected_log_calls = [
317-
# Should log groups found
318-
unittest.mock.call.info("Found 1 identity groups with 2 total spans to export"),
319-
# Should log endpoint being used (default endpoint)
320-
unittest.mock.call.info(
316+
expected_debug_calls = [
317+
# Should log groups found at DEBUG
318+
unittest.mock.call.debug("Found 1 identity groups with 2 total spans to export"),
319+
# Should log endpoint being used at DEBUG (default endpoint)
320+
unittest.mock.call.debug(
321321
f"Exporting 2 spans to endpoint: {DEFAULT_ENDPOINT_URL}/observability/tenants/test-tenant-123/agents/test-agent-456/traces?api-version=1 "
322322
"(tenant: test-tenant-123, agent: test-agent-456)"
323323
),
324-
# Should log token resolution success
325-
unittest.mock.call.info("Token resolved successfully for agent test-agent-456"),
326-
# Should log HTTP success
327-
unittest.mock.call.info(
328-
"HTTP 200 success on attempt 1. Correlation ID: test-correlation-123. Response: success"
324+
# Should log token resolution success at DEBUG
325+
unittest.mock.call.debug("Token resolved successfully."),
326+
# Should log HTTP success at DEBUG
327+
unittest.mock.call.debug(
328+
"HTTP 200 success on attempt 1. Correlation ID: test-correlation-123."
329329
),
330330
]
331331

332-
# Check that all expected info calls were made
333-
for expected_call in expected_log_calls:
334-
self.assertIn(expected_call, mock_logger.info.call_args_list)
332+
# Check that all expected debug calls were made
333+
for expected_call in expected_debug_calls:
334+
self.assertIn(expected_call, mock_logger.debug.call_args_list)
335335

336336
@patch("microsoft_agents_a365.observability.core.exporters.agent365_exporter.logger")
337337
def test_export_error_logging(self, mock_logger):

tests/observability/hosting/middleware/test_observability_hosting_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
@pytest.fixture(autouse=True)
2020
def _reset_singleton():
2121
"""Reset the singleton before and after each test."""
22-
ObservabilityHostingManager.reset()
22+
ObservabilityHostingManager._reset()
2323
yield
24-
ObservabilityHostingManager.reset()
24+
ObservabilityHostingManager._reset()
2525

2626

2727
def test_configure_is_singleton():

0 commit comments

Comments
 (0)