Skip to content

Commit 83355af

Browse files
legendecasrauno56
andauthored
fix: sanitize attributes inputs (#2881)
Co-authored-by: Rauno Viskus <[email protected]>
1 parent 14bd6f9 commit 83355af

File tree

14 files changed

+183
-104
lines changed

14 files changed

+183
-104
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ All notable changes to this project will be documented in this file.
1010

1111
### :bug: (Bug Fix)
1212

13+
* fix: sanitize attributes inputs #2881 @legendecas
14+
1315
### :books: (Refine Doc)
1416

1517
* docs(sdk): update earliest support node version #2860 @svetlanabrennan

packages/opentelemetry-core/src/common/attributes.ts

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,40 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { SpanAttributeValue, SpanAttributes } from '@opentelemetry/api';
1716

18-
export function sanitizeAttributes(attributes: unknown): SpanAttributes {
19-
const out: SpanAttributes = {};
17+
import { diag, AttributeValue, Attributes } from '@opentelemetry/api';
2018

21-
if (attributes == null || typeof attributes !== 'object') {
19+
export function sanitizeAttributes(attributes: unknown): Attributes {
20+
const out: Attributes = {};
21+
22+
if (typeof attributes !== 'object' || attributes == null) {
2223
return out;
2324
}
2425

25-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
26-
for (const [k, v] of Object.entries(attributes!)) {
27-
if (isAttributeValue(v)) {
28-
if (Array.isArray(v)) {
29-
out[k] = v.slice();
30-
} else {
31-
out[k] = v;
32-
}
26+
for (const [key, val] of Object.entries(attributes)) {
27+
if (!isAttributeKey(key)) {
28+
diag.warn(`Invalid attribute key: ${key}`);
29+
continue;
30+
}
31+
if (!isAttributeValue(val)) {
32+
diag.warn(`Invalid attribute value set for key: ${key}`);
33+
continue;
34+
}
35+
if (Array.isArray(val)) {
36+
out[key] = val.slice();
37+
} else {
38+
out[key] = val;
3339
}
3440
}
3541

3642
return out;
3743
}
3844

39-
export function isAttributeValue(val: unknown): val is SpanAttributeValue {
45+
export function isAttributeKey(key: unknown): key is string {
46+
return typeof key === 'string' && key.length > 0;
47+
}
48+
49+
export function isAttributeValue(val: unknown): val is AttributeValue {
4050
if (val == null) {
4151
return true;
4252
}

packages/opentelemetry-core/src/trace/sampler/ParentBasedSampler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616

1717
import {
18-
SpanAttributes,
18+
Attributes,
1919
Context,
2020
isSpanContextValid,
2121
Link,
@@ -64,7 +64,7 @@ export class ParentBasedSampler implements Sampler {
6464
traceId: string,
6565
spanName: string,
6666
spanKind: SpanKind,
67-
attributes: SpanAttributes,
67+
attributes: Attributes,
6868
links: Link[]
6969
): SamplingResult {
7070
const parentContext = trace.getSpanContext(context);

packages/opentelemetry-exporter-zipkin/src/transform.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ export function toZipkinSpan(
6666
return zipkinSpan;
6767
}
6868

69-
/** Converts OpenTelemetry SpanAttributes and SpanStatus to Zipkin Tags format. */
69+
/** Converts OpenTelemetry Attributes and SpanStatus to Zipkin Tags format. */
7070
export function _toZipkinTags(
71-
attributes: api.SpanAttributes,
71+
attributes: api.Attributes,
7272
status: api.SpanStatus,
7373
statusCodeTagName: string,
7474
statusErrorTagName: string,

packages/opentelemetry-resources/src/Resource.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class Resource {
6868
merge(other: Resource | null): Resource {
6969
if (!other || !Object.keys(other.attributes).length) return this;
7070

71-
// SpanAttributes from resource overwrite attributes from other resource.
71+
// Attributes from resource overwrite attributes from other resource.
7272
const mergedAttributes = Object.assign(
7373
{},
7474
this.attributes,

packages/opentelemetry-sdk-trace-base/src/Span.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
InstrumentationLibrary,
2323
isTimeInput,
2424
timeInputToHrTime,
25+
sanitizeAttributes,
2526
} from '@opentelemetry/core';
2627
import { Resource } from '@opentelemetry/resources';
2728
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
@@ -30,7 +31,7 @@ import { TimedEvent } from './TimedEvent';
3031
import { Tracer } from './Tracer';
3132
import { SpanProcessor } from './SpanProcessor';
3233
import { SpanLimits } from './types';
33-
import { SpanAttributeValue, Context } from '@opentelemetry/api';
34+
import { AttributeValue, Context } from '@opentelemetry/api';
3435
import { ExceptionEventName } from './enums';
3536

3637
/**
@@ -42,7 +43,7 @@ export class Span implements api.Span, ReadableSpan {
4243
private readonly _spanContext: api.SpanContext;
4344
readonly kind: api.SpanKind;
4445
readonly parentSpanId?: string;
45-
readonly attributes: api.SpanAttributes = {};
46+
readonly attributes: api.Attributes = {};
4647
readonly links: api.Link[] = [];
4748
readonly events: TimedEvent[] = [];
4849
readonly startTime: api.HrTime;
@@ -88,7 +89,7 @@ export class Span implements api.Span, ReadableSpan {
8889
return this._spanContext;
8990
}
9091

91-
setAttribute(key: string, value?: SpanAttributeValue): this;
92+
setAttribute(key: string, value?: AttributeValue): this;
9293
setAttribute(key: string, value: unknown): this {
9394
if (value == null || this._isSpanEnded()) return this;
9495
if (key.length === 0) {
@@ -111,7 +112,7 @@ export class Span implements api.Span, ReadableSpan {
111112
return this;
112113
}
113114

114-
setAttributes(attributes: api.SpanAttributes): this {
115+
setAttributes(attributes: api.Attributes): this {
115116
for (const [k, v] of Object.entries(attributes)) {
116117
this.setAttribute(k, v);
117118
}
@@ -127,7 +128,7 @@ export class Span implements api.Span, ReadableSpan {
127128
*/
128129
addEvent(
129130
name: string,
130-
attributesOrStartTime?: api.SpanAttributes | api.TimeInput,
131+
attributesOrStartTime?: api.Attributes | api.TimeInput,
131132
startTime?: api.TimeInput
132133
): this {
133134
if (this._isSpanEnded()) return this;
@@ -148,9 +149,11 @@ export class Span implements api.Span, ReadableSpan {
148149
if (typeof startTime === 'undefined') {
149150
startTime = hrTime();
150151
}
152+
153+
const attributes = sanitizeAttributes(attributesOrStartTime);
151154
this.events.push({
152155
name,
153-
attributes: attributesOrStartTime as api.SpanAttributes,
156+
attributes,
154157
time: timeInputToHrTime(startTime),
155158
});
156159
return this;
@@ -193,7 +196,7 @@ export class Span implements api.Span, ReadableSpan {
193196
}
194197

195198
recordException(exception: api.Exception, time: api.TimeInput = hrTime()): void {
196-
const attributes: api.SpanAttributes = {};
199+
const attributes: api.Attributes = {};
197200
if (typeof exception === 'string') {
198201
attributes[SemanticAttributes.EXCEPTION_MESSAGE] = exception;
199202
} else if (exception) {
@@ -217,7 +220,7 @@ export class Span implements api.Span, ReadableSpan {
217220
attributes[SemanticAttributes.EXCEPTION_TYPE] ||
218221
attributes[SemanticAttributes.EXCEPTION_MESSAGE]
219222
) {
220-
this.addEvent(ExceptionEventName, attributes as api.SpanAttributes, time);
223+
this.addEvent(ExceptionEventName, attributes, time);
221224
} else {
222225
api.diag.warn(`Failed to record an exception ${exception}`);
223226
}
@@ -260,7 +263,7 @@ export class Span implements api.Span, ReadableSpan {
260263
* @param value Attribute value
261264
* @returns truncated attribute value if required, otherwise same value
262265
*/
263-
private _truncateToSize(value: SpanAttributeValue): SpanAttributeValue {
266+
private _truncateToSize(value: AttributeValue): AttributeValue {
264267
const limit = this._attributeValueLengthLimit;
265268
// Check limit
266269
if (limit <= 0) {

packages/opentelemetry-sdk-trace-base/src/TimedEvent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { HrTime, SpanAttributes } from '@opentelemetry/api';
17+
import { HrTime, Attributes } from '@opentelemetry/api';
1818

1919
/**
2020
* Represents a timed event.
@@ -25,5 +25,5 @@ export interface TimedEvent {
2525
/** The name of the event. */
2626
name: string;
2727
/** The attributes of the event. */
28-
attributes?: SpanAttributes;
28+
attributes?: Attributes;
2929
}

packages/opentelemetry-sdk-trace-base/src/Tracer.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ export class Tracer implements api.Tracer {
9292
}
9393

9494
const spanKind = options.kind ?? api.SpanKind.INTERNAL;
95-
const links = options.links ?? [];
95+
const links = (options.links ?? []).map(link => {
96+
return {
97+
context: link.context,
98+
attributes: sanitizeAttributes(link.attributes),
99+
};
100+
});
96101
const attributes = sanitizeAttributes(options.attributes);
97102
// make sampling decision
98103
const samplingResult = this._sampler.shouldSample(
@@ -124,8 +129,10 @@ export class Tracer implements api.Tracer {
124129
links,
125130
options.startTime
126131
);
127-
// Set default attributes
128-
span.setAttributes(Object.assign(attributes, samplingResult.attributes));
132+
// Set initial span attributes. The attributes object may have been mutated
133+
// by the sampler, so we sanitize the merged attributes before setting them.
134+
const initAttributes = sanitizeAttributes(Object.assign(attributes, samplingResult.attributes));
135+
span.setAttributes(initAttributes);
129136
return span;
130137
}
131138

packages/opentelemetry-sdk-trace-base/src/export/ReadableSpan.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import {
1818
SpanKind,
1919
SpanStatus,
20-
SpanAttributes,
20+
Attributes,
2121
HrTime,
2222
Link,
2323
SpanContext,
@@ -34,7 +34,7 @@ export interface ReadableSpan {
3434
readonly startTime: HrTime;
3535
readonly endTime: HrTime;
3636
readonly status: SpanStatus;
37-
readonly attributes: SpanAttributes;
37+
readonly attributes: Attributes;
3838
readonly links: Link[];
3939
readonly events: TimedEvent[];
4040
readonly duration: HrTime;

0 commit comments

Comments
 (0)