Address observability security review findings#216
Conversation
- 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>
There was a problem hiding this comment.
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, defaultfalse) 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
expTTL.
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.
tests/observability/extension/openai/OpenAIAgentsTraceProcessor.test.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-langchain/src/tracer.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts
Outdated
Show resolved
Hide resolved
- 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>
There was a problem hiding this comment.
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.
packages/agents-a365-observability-extensions-openai/src/Utils.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-langchain/src/Utils.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts
Outdated
Show resolved
Hide resolved
tests/observability/extension/openai/OpenAIAgentsTraceProcessor.test.ts
Outdated
Show resolved
Hide resolved
- 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>
There was a problem hiding this comment.
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
processResponseSpanDatasetsgen_ai.output.messagesdirectly fromresp.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.
packages/agents-a365-observability-hosting/src/utils/ScopeUtils.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-openai/src/Utils.ts
Outdated
Show resolved
Hide resolved
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
packages/agents-a365-observability/src/configuration/ObservabilityConfigurationOptions.ts
Outdated
Show resolved
Hide resolved
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>
There was a problem hiding this comment.
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, butresp.outputis 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 toGEN_AI_OUTPUT_MESSAGES_KEYvalues produced here (both the string andbuildOutputMessagescases).
// 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.
packages/agents-a365-observability-extensions-openai/src/Utils.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-langchain/src/Utils.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-langchain/src/tracer.ts
Outdated
Show resolved
Hide resolved
…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>
There was a problem hiding this comment.
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_KEYis set fromresp.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_KEYis 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 inUtils.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.
tests/observability/extension/openai/OpenAIAgentsTraceProcessor.test.ts
Outdated
Show resolved
Hide resolved
packages/agents-a365-observability-extensions-langchain/src/LangChainTraceInstrumentor.ts
Show resolved
Hide resolved
- 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>
0e8e4dc to
3580ab6
Compare
There was a problem hiding this comment.
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
isContentRecordingEnabledis intended to suppress all content attributes, butmcp_toolsspans still go throughprocessMCPListToolsSpanData()without any content gating. That method can setGEN_AI_EVENT_CONTENT/GEN_AI_TOOL_ARGS_KEY(via keyMappings), so content can still be recorded even when content recording is disabled. PasscontentRecordingintoprocessMCPListToolsSpanData(or applyisContentKeyfiltering there) so the suppression behavior is consistent across span types; consider adding a test that coversmcp_toolsspans 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.
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>
There was a problem hiding this comment.
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.
packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts
Show resolved
Hide resolved
- 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>
There was a problem hiding this comment.
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.
packages/agents-a365-observability-extensions-openai/src/OpenAIAgentsTraceProcessor.ts
Show resolved
Hide resolved
…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>
Summary
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 tofalse; enable viaAZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED=trueenv var or programmatic override inObservabilityConfigurationOptions.truncateValueutility in core (exported as public API) truncates attribute values exceeding 8192 chars with...[truncated]suffix. Applied in OpenAIsafeJsonDumpsand LangChain content helpers.encodeURIComponent()applied totenantIdandagentIdin Agent365Exporter path construction.Map-based O(1) size check), OpenAI in-flight spans (10K) to prevent unbounded memory growth.expto max 24 hours to prevent crafted tokens from staying cached indefinitely.honomoved from direct dependencies to optionalpeerDependenciesin OpenAI extension (satisfied transitively by@openai/agents).Public API changes
@microsoft/agents-a365-observabilitytruncateValue(value: string): stringMAX_ATTRIBUTE_LENGTHconstant (8192)ObservabilityConfiguration.isContentRecordingEnabled(readsAZURE_TRACING_GEN_AI_CONTENT_RECORDING_ENABLED)ObservabilityConfigurationOptions.isContentRecordingEnabled?: () => boolean@microsoft/agents-a365-observability-extensions-openaiOpenAIAgentsInstrumentationConfig.isContentRecordingEnabled?: boolean@microsoft/agents-a365-observability-extensions-langchainLangChainTraceInstrumentor.instrument(module, options?)— new optional second parameteraddTracerToHandlers(tracer, handlers, options?)— new optional third parameterLangChainTracerconstructor — new optional second parameter{ isContentRecordingEnabled?: boolean }All additions are backwards-compatible (new optional parameters with
falsedefaults).Test plan
pnpm buildpasses across all 12 packagespnpm testpasses all 1072 tests across 57 suitesisContentRecordingEnabledconfig getter (4 tests), content suppression in OpenAI processor (1 test), token cache cap + JWT TTL cap (2 tests),truncateValueutility (6 tests)isContentRecordingEnabled: truewhere content attributes are asserted🤖 Generated with Claude Code