Skip to content

Commit a459f88

Browse files
nsdeschenesclaude
andcommitted
fix(tracemetrics): Validate sort field names in parseSortBys
Extract SORTABLE_SAMPLE_COLUMNS to shared types module and use it in parseSortBys to reject unknown field names from serialized query params, falling back to default sort. Previously only the shape was validated, allowing arbitrary field strings through to the API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1f27063 commit a459f88

File tree

4 files changed

+25
-6
lines changed

4 files changed

+25
-6
lines changed

static/app/views/explore/metrics/metricInfoTabs/metricsSamplesTableHeader.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
StyledSimpleTableHeaderCell,
1010
} from 'sentry/views/explore/metrics/metricInfoTabs/metricInfoTabStyles';
1111
import {
12+
SORTABLE_SAMPLE_COLUMNS,
1213
TraceMetricKnownFieldKey,
1314
VirtualTableSampleColumnKey,
1415
type SampleTableColumnKey,
@@ -19,11 +20,6 @@ import {
1920
useSetQueryParamsSortBys,
2021
} from 'sentry/views/explore/queryParams/context';
2122

22-
const SORTABLE_COLUMNS = new Set<SampleTableColumnKey>([
23-
TraceMetricKnownFieldKey.METRIC_VALUE,
24-
TraceMetricKnownFieldKey.TIMESTAMP,
25-
]);
26-
2723
interface MetricsSamplesTableHeaderProps {
2824
columns: SampleTableColumnKey[];
2925
embedded?: boolean;
@@ -76,7 +72,7 @@ function FieldHeaderCellWrapper({
7672
const columnType = getMetricTableColumnType(field);
7773
const label = getFieldLabel(field);
7874
const hasPadding = field !== VirtualTableSampleColumnKey.EXPAND_ROW;
79-
const canSort = SORTABLE_COLUMNS.has(field);
75+
const canSort = SORTABLE_SAMPLE_COLUMNS.has(field);
8076

8177
function handleSortClick() {
8278
const kind = sort === 'desc' ? 'asc' : 'desc';

static/app/views/explore/metrics/metricQuery.spec.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,22 @@ describe('decodeMetricsQueryParams', () => {
155155
expect(result?.queryParams.sortBys).toEqual([{field: 'timestamp', kind: 'desc'}]);
156156
});
157157

158+
it('falls back to default sortBys when field is not sortable', () => {
159+
const json = JSON.stringify({
160+
metric: {name: 'test_metric', type: 'counter'},
161+
query: '',
162+
aggregateFields: [{yAxes: ['sum(value,test_metric,counter,-)']}],
163+
aggregateSortBys: [],
164+
sortBys: [{field: 'arbitrary_field', kind: 'desc'}],
165+
mode: 'samples',
166+
});
167+
168+
const result = decodeMetricsQueryParams(json);
169+
170+
expect(result).not.toBeNull();
171+
expect(result?.queryParams.sortBys).toEqual([{field: 'timestamp', kind: 'desc'}]);
172+
});
173+
158174
it('falls back to default sortBys when format is invalid', () => {
159175
const json = JSON.stringify({
160176
metric: {name: 'test_metric', type: 'counter'},

static/app/views/explore/metrics/metricQuery.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type {Location} from 'history';
33
import {defined} from 'sentry/utils';
44
import type {Sort} from 'sentry/utils/discover/fields';
55
import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
6+
import {SORTABLE_SAMPLE_COLUMNS} from 'sentry/views/explore/metrics/types';
67
import type {AggregateField} from 'sentry/views/explore/queryParams/aggregateField';
78
import {validateAggregateSort} from 'sentry/views/explore/queryParams/aggregateSortBy';
89
import {isGroupBy, type GroupBy} from 'sentry/views/explore/queryParams/groupBy';
@@ -234,6 +235,7 @@ function parseSortBys(value: unknown, fields: string[]): Sort[] {
234235
typeof v === 'object' &&
235236
'field' in v &&
236237
typeof v.field === 'string' &&
238+
SORTABLE_SAMPLE_COLUMNS.has(v.field) &&
237239
'kind' in v &&
238240
(v.kind === 'asc' || v.kind === 'desc')
239241
);

static/app/views/explore/metrics/types.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,8 @@ export enum VirtualTableSampleColumnKey {
106106
}
107107

108108
export type SampleTableColumnKey = TraceMetricFieldKey | VirtualTableSampleColumnKey;
109+
110+
export const SORTABLE_SAMPLE_COLUMNS = new Set<SampleTableColumnKey>([
111+
TraceMetricKnownFieldKey.METRIC_VALUE,
112+
TraceMetricKnownFieldKey.TIMESTAMP,
113+
]);

0 commit comments

Comments
 (0)