Skip to content

Address observability security review findings#216

Merged
fpfp100 merged 15 commits intomainfrom
users/pefan/observability-security-hardening
Mar 12, 2026
Merged

Address observability security review findings#216
fpfp100 merged 15 commits intomainfrom
users/pefan/observability-security-hardening

Conversation

@fpfp100
Copy link
Contributor

@fpfp100 fpfp100 commented Mar 9, 2026

Summary

  • Content recording gate (isContentRecordingEnabled): Sensitive content (prompts, completions, tool I/O, system instructions) is only recorded as span attributes when explicitly enabled. Gated in LangChain tracer, OpenAI trace processor, and hosting ScopeUtils. Defaults to false; enable via AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED=true env var or programmatic override in ObservabilityConfigurationOptions.
  • Attribute value truncation: Shared truncateValue utility in core (exported as public API) truncates attribute values exceeding 8192 chars with ...[truncated] suffix. Applied in OpenAI safeJsonDumps and LangChain content helpers.
  • URL injection prevention: encodeURIComponent() applied to tenantId and agentId in Agent365Exporter path construction.
  • Bounded collections: Cap token cache (10K entries), LangChain run storage (10K runs with Map-based O(1) size check), OpenAI in-flight spans (10K) to prevent unbounded memory growth.
  • JWT TTL cap: Limit decoded JWT exp to max 24 hours to prevent crafted tokens from staying cached indefinitely.
  • Retry jitter: Randomized jitter added to Agent365Exporter retry delays to prevent thundering herd.
  • Error truncation: Truncate LangChain error messages to 1024 chars before setting as span attribute.
  • Move hono to optional peer dep: hono moved from direct dependencies to optional peerDependencies in OpenAI extension (satisfied transitively by @openai/agents).

Public API changes

@microsoft/agents-a365-observability

  • New export: truncateValue(value: string): string
  • New export: MAX_ATTRIBUTE_LENGTH constant (8192)
  • New getter: ObservabilityConfiguration.isContentRecordingEnabled (reads AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED)
  • New option: ObservabilityConfigurationOptions.isContentRecordingEnabled?: () => boolean

@microsoft/agents-a365-observability-extensions-openai

  • New field: OpenAIAgentsInstrumentationConfig.isContentRecordingEnabled?: boolean

@microsoft/agents-a365-observability-extensions-langchain

  • LangChainTraceInstrumentor.instrument(module, options?) — new optional second parameter
  • addTracerToHandlers(tracer, handlers, options?) — new optional third parameter
  • LangChainTracer constructor — new optional second parameter { isContentRecordingEnabled?: boolean }

All additions are backwards-compatible (new optional parameters with false defaults).

Test plan

  • pnpm build passes across all 12 packages
  • pnpm test passes all 1072 tests across 57 suites
  • New tests: isContentRecordingEnabled config getter (4 tests), content suppression in OpenAI processor (1 test), token cache cap + JWT TTL cap (2 tests), truncateValue utility (6 tests)
  • Existing OpenAI and hosting tests updated to pass isContentRecordingEnabled: true where content attributes are asserted

🤖 Generated with Claude Code

- Add isContentRecordingEnabled config (defaults false, opt-in via AZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED)
- Gate content recording in LangChain, OpenAI, and hosting extensions
- URL-encode tenantId/agentId in exporter path to prevent injection
- Cap token cache (10K entries), LangChain runs (10K), OpenAI spans (10K)
- Cap JWT exp to 24h max in AgenticTokenCache
- Add retry jitter in Agent365Exporter
- Truncate LangChain error messages to 1024 chars
- Move hono from dependencies to optional peerDependencies in OpenAI extension

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 9, 2026 23:42
@fpfp100 fpfp100 requested review from a team as code owners March 9, 2026 23:42
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Addresses security review findings across the observability stack by reducing sensitive-data exposure, hardening request construction, and preventing unbounded memory growth.

Changes:

  • Introduces a content-recording gate (isContentRecordingEnabled, default false) and applies it to hosting, OpenAI, and LangChain span attribute recording.
  • Hardens export behavior by URL-encoding path parameters and adding retry jitter to reduce thundering herd.
  • Adds bounded in-memory limits (token cache size, LangChain run storage, OpenAI in-flight span cap) and caps JWT exp TTL.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/observability/extension/openai/OpenAIAgentsTraceProcessor.test.ts Sets env flag for content recording during span-attribute assertions.
tests/observability/extension/hosting/scope-utils.test.ts Sets env flag for content recording during scope tag population tests.
pnpm-lock.yaml Updates lock entries to align with dependency specifier changes.
packages/agents-a365-observability/src/tracing/exporter/Agent365Exporter.ts URL-encodes tenant/agent IDs in paths; adds retry jitter.
packages/agents-a365-observability/src/configuration/ObservabilityConfigurationOptions.ts Adds isContentRecordingEnabled override option and docs.
packages/agents-a365-observability/src/configuration/ObservabilityConfiguration.ts Implements isContentRecordingEnabled resolution via override/env var.
packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts Gates recording of input message content based on config.
packages/agents-a365-observability-hosting/src/caching/AgenticTokenCache.ts Adds cache size cap and JWT exp TTL cap.
packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts Adds content-attribute gating and caps in-flight spans.
packages/agents-a365-observability-extensions-openai/package.json Moves hono to optional peer dependency.
packages/agents-a365-observability-extensions-langchain/src/tracer.ts Adds content-attribute gating, run cap, and error-message truncation.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

You can also share your feedback on Copilot code review. Take the survey.

jsl517 and others added 2 commits March 9, 2026 16:59
- Use process.env snapshot/restore pattern in tests (matches ObservabilityConfiguration.test.ts)
- Convert LangChainTracer runs/parentByRunId from Record to Map for O(1) .size check
- Account for suffix length in error truncation (1010 + 14 = 1024)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sions

Truncate span attribute values exceeding 32,768 characters to prevent
oversized telemetry payloads. Applied to safeJsonDumps (OpenAI) and
content attributes (LangChain: tool args/results, messages, system instructions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 10, 2026 17:31
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

You can also share your feedback on Copilot code review. Take the survey.

jsl517 and others added 2 commits March 10, 2026 10:47
- Increase error message truncation from 1024 to 8192 chars (8178 + suffix = 8192)
- Add attribute value truncation at 8KB for OpenAI and LangChain extensions
- Accept optional IConfigurationProvider in ScopeUtils.setInputMessageTags
  so consumers using custom providers can override content recording

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 10, 2026 20:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)

packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts:257

  • processResponseSpanData sets gen_ai.output.messages directly from resp.output / buildOutputMessages() without applying the new truncation logic. Large completions could still exceed attribute limits; consider running these through the same truncation helper used elsewhere (and ensuring the final value stays within the limit).
      if (resp.output && contentRecording) {
        if (typeof resp.output === 'string') {
          otelSpan.setAttribute(OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY, resp.output);
        } else {
          otelSpan.setAttribute(
            OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY,
            this.buildOutputMessages(resp.output  as Array<{ role: string; content: Array<{ type: string; text: string }> }>)
          );

packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts:295

  • Input message attributes are written from inputObj / buildInputMessages() without truncation. This bypasses the new attribute-length safety guard and can still produce oversized span attributes for long prompts.
    if (inputObj && !this.suppressInvokeAgentInput && contentRecording) {
      if (typeof inputObj === 'string') {
        try {
          const parsed = JSON.parse(inputObj as string);
          if (Array.isArray(parsed)) {
            otelSpan.setAttribute(
              OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY,
              this.buildInputMessages(parsed)
            );
            return;
          }
        } catch {
          // If parsing fails, fall back to raw string behavior
        }
        otelSpan.setAttribute(OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, inputObj);
      } else if (Array.isArray(inputObj)) {
        // build the input messages from array
        otelSpan.setAttribute(
          OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY,
          this.buildInputMessages(inputObj)
        );
      }

You can also share your feedback on Copilot code review. Take the survey.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Content recording gate is only needed in extensions (OpenAI/LangChain),
not in the core config. Hosting ScopeUtils always records input messages
since input/output are required for agent validation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 17:32
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts:257

  • Content attributes are gated by contentRecording, but resp.output is still recorded without any truncation/size limiting. Given this PR introduces an 8192-char truncation utility elsewhere, it would be more consistent (and safer for exporter/backend limits) to apply the same truncation to GEN_AI_OUTPUT_MESSAGES_KEY values produced here (both the string and buildOutputMessages cases).
      // Store the output field for GEN_AI_RESPONSE_CONTENT_KEY
      if (resp.output && contentRecording) {
        if (typeof resp.output === 'string') {
          otelSpan.setAttribute(OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY, resp.output);
        } else {
          otelSpan.setAttribute(
            OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY,
            this.buildOutputMessages(resp.output  as Array<{ role: string; content: Array<{ type: string; text: string }> }>)
          );

You can also share your feedback on Copilot code review. Take the survey.

jsl517 and others added 2 commits March 11, 2026 11:01
…nstrumentation

Replace isContentRecordingEnabled option with direct env var read in
OpenAI and LangChain extensions. This aligns with the standard
OpenTelemetry convention and removes unnecessary config surface.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Content recording is controlled via boolean option on extension
instrumentor configs (OpenAI/LangChain), not via env var or core
ObservabilityConfiguration. Fixes lint no-restricted-properties error.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 18:38
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (2)

packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts:260

  • When content recording is enabled, GEN_AI_OUTPUT_MESSAGES_KEY is set from resp.output / buildOutputMessages(...) without any length cap. Large responses can exceed common OpenTelemetry attribute limits and may be dropped/failed by exporters. Consider truncating these attribute values to the same MAX_ATTRIBUTE_LENGTH used elsewhere.
      // Store the output field for GEN_AI_RESPONSE_CONTENT_KEY
      if (resp.output && contentRecording) {
        if (typeof resp.output === 'string') {
          otelSpan.setAttribute(OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY, resp.output);
        } else {
          otelSpan.setAttribute(
            OpenTelemetryConstants.GEN_AI_OUTPUT_MESSAGES_KEY,
            this.buildOutputMessages(resp.output  as Array<{ role: string; content: Array<{ type: string; text: string }> }>)
          );
        }

packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts:291

  • When content recording is enabled, GEN_AI_INPUT_MESSAGES_KEY is set directly from _input (including raw strings) without truncation. For large prompts this can exceed attribute size limits. Consider truncating the recorded input value consistently with the truncation behavior added in Utils.safeJsonDumps.
    if (inputObj && !this.suppressInvokeAgentInput && contentRecording) {
      if (typeof inputObj === 'string') {
        try {
          const parsed = JSON.parse(inputObj as string);
          if (Array.isArray(parsed)) {
            otelSpan.setAttribute(
              OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY,
              this.buildInputMessages(parsed)
            );
            return;
          }
        } catch {
          // If parsing fails, fall back to raw string behavior
        }
        otelSpan.setAttribute(OpenTelemetryConstants.GEN_AI_INPUT_MESSAGES_KEY, inputObj);
      } else if (Array.isArray(inputObj)) {

You can also share your feedback on Copilot code review. Take the survey.

- Export truncateValue and MAX_ATTRIBUTE_LENGTH from core index.ts
- Add shared truncateValue with suffix-aware truncation to tracing/util.ts
- Remove local truncateValue copies from OpenAI and LangChain Utils.ts
- Warn when LangChain singleton getInstance() receives options after init
- Fix misleading test name for content recording
- Add truncation tests, token cache cap tests, JWT TTL cap tests

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 19:08
@fpfp100 fpfp100 force-pushed the users/pefan/observability-security-hardening branch from 0e8e4dc to 3580ab6 Compare March 11, 2026 19:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts:228

  • isContentRecordingEnabled is intended to suppress all content attributes, but mcp_tools spans still go through processMCPListToolsSpanData() without any content gating. That method can set GEN_AI_EVENT_CONTENT / GEN_AI_TOOL_ARGS_KEY (via keyMappings), so content can still be recorded even when content recording is disabled. Pass contentRecording into processMCPListToolsSpanData (or apply isContentKey filtering there) so the suppression behavior is consistent across span types; consider adding a test that covers mcp_tools spans with content recording disabled.
    case 'mcp_tools':
      this.processMCPListToolsSpanData(otelSpan, data);
      break;

You can also share your feedback on Copilot code review. Take the survey.

fpfp100 and others added 2 commits March 11, 2026 12:13
The integration test asserts content attributes are present, so it
needs isContentRecordingEnabled: true on the instrumentor config.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 19:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

You can also share your feedback on Copilot code review. Take the survey.

jsl517 and others added 2 commits March 11, 2026 12:49
- LangChain tracer: clean up runs and parentByRunId when tracing is
  suppressed to prevent memory leaks
- OpenAI processor: apply truncateValue to raw input/output message
  setAttribute calls in processResponseSpanData for consistency
- Integration test: enable content recording in instrumentor config

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…unId cleanup

When tracing becomes suppressed mid-flight, properly end any span that was
already started to avoid abandoned spans. Remove parentByRunId.delete() from
skip-internal-runs and no-entry paths since these entries are lightweight
string mappings needed for parent chain traversal.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 11, 2026 20:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

You can also share your feedback on Copilot code review. Take the survey.

…panData

When funcData.input is already a string or funcData.output is a non-object,
they bypassed safeJsonDumps and its truncation. Apply truncateValue directly
to these paths for consistent attribute size limits.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@fpfp100 fpfp100 merged commit dcdb39c into main Mar 12, 2026
7 checks passed
@fpfp100 fpfp100 deleted the users/pefan/observability-security-hardening branch March 12, 2026 18:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants