Skip to content

Commit e336052

Browse files
gsoldevilaJordanSh
authored andcommitted
[SOR] Protect search() method to prevent overriding top-level fields with runtime mappings (elastic#244927)
## Summary Closes elastic/kibana-team#2227 The PR tweaks the SOR `search()` method to ignore any runtime mappings that overlap with top-level _Saved Object_ properties.
1 parent da9b719 commit e336052

2 files changed

Lines changed: 54 additions & 1 deletion

File tree

  • src/core
    • packages/saved-objects/api-server-internal/src/lib/apis
    • server/integration_tests/saved_objects/service/lib

src/core/packages/saved-objects/api-server-internal/src/lib/apis/search.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
import type { ApiExecutionContext } from './types';
2424
import { getNamespacesBoolFilter } from '../search';
2525
import type { NamespacesBoolFilter } from '../search/search_dsl/query_params';
26+
import { getRootFields } from '../utils';
2627

2728
export interface PerformSearchParams {
2829
options: SavedObjectsSearchOptions;
@@ -63,6 +64,17 @@ export async function performSearch<T extends SavedObjectsRawDocSource, A = unkn
6364
);
6465
}
6566

67+
const rootFields = getRootFields();
68+
69+
const forbiddenRuntimeMappings = Object.keys(esOptions.runtime_mappings ?? {}).filter((key) =>
70+
rootFields.includes(key)
71+
);
72+
if (forbiddenRuntimeMappings.length > 0) {
73+
throw SavedObjectsErrorHelpers.createBadRequestError(
74+
`'runtime_mappings' contains forbidden fields: ${forbiddenRuntimeMappings.join(', ')}`
75+
);
76+
}
77+
6678
const types = castArray(type).filter((t) => allowedTypes.includes(t));
6779
if (types.length === 0) {
6880
return createEmptySearchResponse();

src/core/server/integration_tests/saved_objects/service/lib/search.test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const registerTypes = (setup: InternalCoreSetup) => {
3939
setup.savedObjects.registerType({
4040
name: 'test-type',
4141
hidden: false,
42-
namespaceType: 'single',
42+
namespaceType: 'multiple',
4343
mappings: {
4444
dynamic: false,
4545
properties: {
@@ -161,4 +161,45 @@ describe('SOR - search API', () => {
161161
expect(documents.hits.total).toHaveProperty('value', 1);
162162
expect(documents.hits.hits).toHaveProperty('0._source.test-type.name', 'John Doe');
163163
});
164+
165+
it('should enforce namespace security with runtime field override attempt', async () => {
166+
// Baseline: should return 5 documents from default namespace
167+
const normalSearch = await savedObjectsRepository.search({
168+
type: 'test-type',
169+
namespaces: ['default'],
170+
query: {
171+
match_all: {},
172+
},
173+
});
174+
expect(normalSearch.hits.total).toHaveProperty('value', 5);
175+
176+
// Baseline: should return 2 documents from namespaceA
177+
const namespaceASearch = await savedObjectsRepository.search({
178+
type: 'test-type',
179+
namespaces: ['namespaceA'],
180+
query: {
181+
match_all: {},
182+
},
183+
});
184+
expect(namespaceASearch.hits.total).toHaveProperty('value', 2);
185+
186+
// Attempt to override namespace filter using runtime mappings should throw an error
187+
await expect(
188+
savedObjectsRepository.search({
189+
type: 'test-type',
190+
namespaces: ['default'],
191+
runtime_mappings: {
192+
namespaces: {
193+
type: 'keyword',
194+
script: {
195+
source: 'emit("default")', // Attempt to make all docs appear in 'default' namespace
196+
},
197+
},
198+
},
199+
query: {
200+
match_all: {},
201+
},
202+
})
203+
).rejects.toThrow("'runtime_mappings' contains forbidden fields: namespaces");
204+
});
164205
});

0 commit comments

Comments
 (0)