From d109fb5172e5dbf84c0112f77b2f35bd997203a0 Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Thu, 2 Apr 2026 16:43:31 +0200 Subject: [PATCH 1/5] [Osquery] Add osquery response action privilege checks to external Api --- .../lib/check_response_action_authz.test.ts | 96 +++++++++++++++ .../server/lib/check_response_action_authz.ts | 36 ++++++ .../plugins/shared/osquery/server/plugin.ts | 19 +++ .../live_query/create_live_query_route.ts | 13 +- .../plugins/shared/osquery/server/types.ts | 18 +++ .../rule_response_actions_validators.test.ts | 112 +++++++++++++++++- .../utils/rule_response_actions_validators.ts | 53 ++++++++- .../api/rules/bulk_actions/route.ts | 2 + .../api/rules/create_rule/route.ts | 2 + .../api/rules/import_rules/route.ts | 2 + .../api/rules/patch_rule/route.ts | 2 + .../api/rules/update_rule/route.ts | 2 + .../server/request_context_factory.ts | 3 + .../plugins/security_solution/server/types.ts | 2 + 14 files changed, 348 insertions(+), 14 deletions(-) create mode 100644 x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts create mode 100644 x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.ts diff --git a/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts b/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts new file mode 100644 index 0000000000000..5001260b39baa --- /dev/null +++ b/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import type { CoreStart, KibanaRequest } from '@kbn/core/server'; +import { checkOsqueryResponseActionAuthz } from './check_response_action_authz'; + +describe('checkOsqueryResponseActionAuthz', () => { + let request: KibanaRequest; + let mockCoreStart: CoreStart; + + const createMockCoreStart = (capabilities: Record): CoreStart => { + return { + capabilities: { + resolveCapabilities: jest.fn().mockResolvedValue({ + osquery: capabilities, + }), + }, + } as unknown as CoreStart; + }; + + beforeEach(() => { + request = httpServerMock.createKibanaRequest(); + }); + + it('should return true when user has writeLiveQueries', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: true, runSavedQueries: false }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, {}); + expect(result).toBe(true); + }); + + it('should return false when user lacks writeLiveQueries for direct query', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: false, runSavedQueries: false }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, {}); + expect(result).toBe(false); + }); + + it('should return true when user has runSavedQueries with saved_query_id', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: false, runSavedQueries: true }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, { + saved_query_id: 'test-query-id', + }); + expect(result).toBe(true); + }); + + it('should return true when user has runSavedQueries with pack_id', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: false, runSavedQueries: true }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, { + pack_id: 'test-pack-id', + }); + expect(result).toBe(true); + }); + + it('should return false when user has runSavedQueries but no saved_query_id or pack_id', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: false, runSavedQueries: true }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, {}); + expect(result).toBe(false); + }); + + it('should return false when user lacks both privileges with saved_query_id', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: false, runSavedQueries: false }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, { + saved_query_id: 'test-query-id', + }); + expect(result).toBe(false); + }); + + it('should return true when user has writeLiveQueries regardless of saved_query_id', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: true, runSavedQueries: false }); + + const result = await checkOsqueryResponseActionAuthz(mockCoreStart, request, { + saved_query_id: 'test-query-id', + }); + expect(result).toBe(true); + }); + + it('should call resolveCapabilities with the correct request and path', async () => { + mockCoreStart = createMockCoreStart({ writeLiveQueries: true, runSavedQueries: false }); + + await checkOsqueryResponseActionAuthz(mockCoreStart, request, {}); + + expect(mockCoreStart.capabilities.resolveCapabilities).toHaveBeenCalledWith(request, { + capabilityPath: 'osquery.*', + }); + }); +}); diff --git a/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.ts b/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.ts new file mode 100644 index 0000000000000..6915eb0b1d8ed --- /dev/null +++ b/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { CoreStart, KibanaRequest } from '@kbn/core/server'; +import type { CheckResponseActionAuthzParams } from '../types'; + +/** + * Checks whether the requesting user has the required osquery privileges + * for a given action configuration. + * + * Privilege logic (mirrors create_live_query_route): + * - Direct query → needs writeLiveQueries + * - Saved query / pack → needs writeLiveQueries OR runSavedQueries + * + * @returns true if authorized, false otherwise + */ +export const checkOsqueryResponseActionAuthz = async ( + coreStart: CoreStart, + request: KibanaRequest, + actionParams: CheckResponseActionAuthzParams +): Promise => { + const { + osquery: { writeLiveQueries, runSavedQueries }, + } = await coreStart.capabilities.resolveCapabilities(request, { + capabilityPath: 'osquery.*', + }); + + return !!( + writeLiveQueries || + (runSavedQueries && (actionParams.saved_query_id || actionParams.pack_id)) + ); +}; diff --git a/x-pack/platform/plugins/shared/osquery/server/plugin.ts b/x-pack/platform/plugins/shared/osquery/server/plugin.ts index 5b28d9bfbc370..1739c5b666f2b 100644 --- a/x-pack/platform/plugins/shared/osquery/server/plugin.ts +++ b/x-pack/platform/plugins/shared/osquery/server/plugin.ts @@ -6,6 +6,7 @@ */ import type { + KibanaRequest, PluginInitializerContext, CoreSetup, CoreStart, @@ -49,6 +50,8 @@ import { registerFeatures } from './utils/register_features'; import { CASE_ATTACHMENT_TYPE_ID } from '../common/constants'; import { createActionService } from './handlers/action/create_action_service'; import { backfillScheduleIds } from './lib/backfill_schedule_ids'; +import { checkOsqueryResponseActionAuthz } from './lib/check_response_action_authz'; +import { CustomHttpRequestError } from '../common/error'; import { SchemaService } from './lib/schema_service'; const BACKFILL_TASK_TYPE = 'osquery:backfillScheduleIds'; @@ -146,8 +149,24 @@ export class OsqueryPlugin implements Plugin => { + const [coreStart] = await core.getStartServices(); + const isAuthorized = await checkOsqueryResponseActionAuthz(coreStart, request, actionParams); + + if (!isAuthorized) { + throw new CustomHttpRequestError( + 'User is not authorized to create/update osquery response action', + 403 + ); + } + }; + return { createActionService: this.createActionService, + checkResponseActionAuthz, }; } diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts index 872564e45e7fd..57175d09d1947 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts @@ -23,6 +23,7 @@ import type { StartPlugins } from '../../types'; import { createActionHandler } from '../../handlers'; import { parser as OsqueryParser } from './osquery_parser'; import { getUserInfo } from '../../lib/get_user_info'; +import { checkOsqueryResponseActionAuthz } from '../../lib/check_response_action_authz'; export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => { router.versioned @@ -52,15 +53,11 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp async (context, request, response) => { const [coreStartServices, startPlugins] = await osqueryContext.getStartServices(); - const { - osquery: { writeLiveQueries, runSavedQueries }, - } = await coreStartServices.capabilities.resolveCapabilities(request, { - capabilityPath: 'osquery.*', - }); - const isInvalid = !( - writeLiveQueries || - (runSavedQueries && (request.body.saved_query_id || request.body.pack_id)) + await checkOsqueryResponseActionAuthz(coreStartServices, request, { + saved_query_id: request.body.saved_query_id, + pack_id: request.body.pack_id, + }) ); const client = await osqueryContext.service diff --git a/x-pack/platform/plugins/shared/osquery/server/types.ts b/x-pack/platform/plugins/shared/osquery/server/types.ts index c5f91811dba5c..e4133b4423e5c 100644 --- a/x-pack/platform/plugins/shared/osquery/server/types.ts +++ b/x-pack/platform/plugins/shared/osquery/server/types.ts @@ -23,11 +23,29 @@ import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin import type { RuleRegistryPluginStartContract } from '@kbn/rule-registry-plugin/server'; import type { CasesServerSetup } from '@kbn/cases-plugin/server'; import type { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; +import type { KibanaRequest } from '@kbn/core/server'; import type { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'; import type { createActionService } from './handlers/action/create_action_service'; +export interface CheckResponseActionAuthzParams { + saved_query_id?: string; + pack_id?: string; +} + export interface OsqueryPluginSetup { createActionService: ReturnType; + /** + * Validates that the requesting user has the required osquery privileges + * for the given response action configuration. + * Throws a 403 CustomHttpRequestError if the user lacks authorization. + * + * Used by security_solution when creating/updating detection rules + * that include osquery response actions. + */ + checkResponseActionAuthz: ( + request: KibanaRequest, + actionParams: CheckResponseActionAuthzParams + ) => Promise; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.test.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.test.ts index 9e0d8c8f5d7f7..ffc87039812e1 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.test.ts @@ -92,16 +92,20 @@ describe('Rules Endpoint response actions validators', () => { await expect(validateRuleResponseActions(options)).resolves.toBeUndefined(); }); - it('should only validate .endpoint response actions', async () => { + it('should validate both .endpoint and .osquery response actions independently', async () => { endpointAuthz.canIsolateHost = false; + const mockOsqueryAuthz = jest.fn().mockResolvedValue(undefined); rulePayload.response_actions = [ - { action_type_id: '.osquery', params: {} }, + { action_type_id: '.osquery', params: { query: 'SELECT 1' } }, createRulePayloadResponseActionMock(), ]; + options.checkOsqueryResponseActionAuthz = mockOsqueryAuthz; await expect(validateRuleResponseActions(options)).rejects.toThrow( 'User is not authorized to create/update isolate response action' ); + // Osquery authz was still called for the osquery action + expect(mockOsqueryAuthz).toHaveBeenCalledTimes(1); }); interface AuthzTestCase { @@ -496,4 +500,108 @@ describe('Rules Endpoint response actions validators', () => { `); }); }); + + describe('osquery response actions validation', () => { + let options: ValidateRuleResponseActionsOptions; + let mockOsqueryAuthz: jest.Mock; + + beforeEach(() => { + mockOsqueryAuthz = jest.fn().mockResolvedValue(undefined); + options = { + rulePayload: { + response_actions: [ + { + action_type_id: '.osquery' as const, + params: { query: 'SELECT * FROM processes' }, + }, + ], + }, + endpointService, + endpointAuthz, + spaceId: 'foo', + checkOsqueryResponseActionAuthz: mockOsqueryAuthz, + }; + }); + + it('should call osquery authz checker for .osquery actions', async () => { + await validateRuleResponseActions(options); + + expect(mockOsqueryAuthz).toHaveBeenCalledWith({ + saved_query_id: undefined, + pack_id: undefined, + }); + }); + + it('should pass saved_query_id to osquery authz checker', async () => { + options.rulePayload = { + response_actions: [ + { + action_type_id: '.osquery' as const, + params: { saved_query_id: 'test-saved-query' }, + }, + ], + }; + + await validateRuleResponseActions(options); + + expect(mockOsqueryAuthz).toHaveBeenCalledWith({ + saved_query_id: 'test-saved-query', + pack_id: undefined, + }); + }); + + it('should pass pack_id to osquery authz checker', async () => { + options.rulePayload = { + response_actions: [ + { + action_type_id: '.osquery' as const, + params: { pack_id: 'test-pack' }, + }, + ], + }; + + await validateRuleResponseActions(options); + + expect(mockOsqueryAuthz).toHaveBeenCalledWith({ + saved_query_id: undefined, + pack_id: 'test-pack', + }); + }); + + it('should throw when osquery authz checker rejects', async () => { + const authzError: Error & { statusCode?: number } = new Error( + 'User is not authorized to create/update osquery response action' + ); + authzError.statusCode = 403; + mockOsqueryAuthz.mockRejectedValue(authzError); + + await expect(validateRuleResponseActions(options)).rejects.toThrow( + 'User is not authorized to create/update osquery response action' + ); + }); + + it('should skip osquery validation when no authz checker is provided', async () => { + delete options.checkOsqueryResponseActionAuthz; + + // Should not throw even though there are osquery actions + await expect(validateRuleResponseActions(options)).resolves.toBeUndefined(); + }); + + it('should not validate unchanged osquery actions on update', async () => { + const osqueryAction = { + action_type_id: '.osquery' as const, + params: { query: 'SELECT * FROM processes' }, + }; + options.rulePayload = { response_actions: [osqueryAction] }; + existingRule.params.responseActions = [ + { actionTypeId: '.osquery', params: { query: 'SELECT * FROM processes' } }, + ] as RuleResponseAction[]; + options.existingRule = existingRule; + + await validateRuleResponseActions(options); + + // Osquery authz should not be called since the action is unchanged + expect(mockOsqueryAuthz).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts index c3fb37fbc1067..daf13b1b7d120 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts @@ -36,6 +36,11 @@ import { CustomHttpRequestError } from '../../../../utils/custom_http_request_er type RuleResponseActions = Pick; +export type CheckOsqueryResponseActionAuthz = (actionParams: { + saved_query_id?: string; + pack_id?: string; +}) => Promise; + export interface ValidateRuleResponseActionsOptions< T extends RuleResponseActions = RuleResponseActions > { @@ -51,6 +56,12 @@ export interface ValidateRuleResponseActionsOptions< * are only applied to the response actions that have changed */ existingRule?: RuleAlertType | null; + /** + * Optional callback to validate osquery response action authorization. + * Should be bound to the current request before passing in. + * When provided, osquery response actions will be validated for privileges. + */ + checkOsqueryResponseActionAuthz?: CheckOsqueryResponseActionAuthz; } /** @@ -65,6 +76,7 @@ export const validateRuleResponseActions = async < spaceId, rulePayload: { response_actions: ruleResponseActions }, existingRule, + checkOsqueryResponseActionAuthz, }: ValidateRuleResponseActionsOptions): Promise => { const logger = endpointService.createLogger('validateRuleResponseActions'); const existingRuleResponseActions = existingRule?.params?.responseActions; @@ -160,12 +172,26 @@ export const validateRuleResponseActions = async < } break; } + } else if (isOsqueryResponseAction(actionData)) { + if (checkOsqueryResponseActionAuthz) { + const params = actionData.params; + await checkOsqueryResponseActionAuthz({ + saved_query_id: + 'saved_query_id' in params ? params.saved_query_id : undefined, + pack_id: 'pack_id' in params ? params.pack_id : undefined, + }); + } else { + logger.debug( + () => + `Skipping osquery response action validation - no osquery authz checker provided: ${stringify( + actionData + )}` + ); + } } else { logger.debug( () => - `Skipping validation of response action - action type id not '.endpoint': ${stringify( - actionData - )}` + `Skipping validation of response action - unknown action type: ${stringify(actionData)}` ); } } @@ -177,7 +203,10 @@ type ImportRuleResponseActions = Pick = Pick & { +> = Pick< + ValidateRuleResponseActionsOptions, + 'endpointAuthz' | 'endpointService' | 'spaceId' | 'checkOsqueryResponseActionAuthz' +> & { rulesToImport: T[]; }; @@ -203,6 +232,7 @@ export const validateRuleImportResponseActions = async < endpointAuthz, spaceId, rulesToImport, + checkOsqueryResponseActionAuthz, }: ValidateRuleImportResponseActionsOptions): Promise< ValidateRuleImportResponseActionsResult > => { @@ -220,6 +250,7 @@ export const validateRuleImportResponseActions = async < endpointService, spaceId, rulePayload: rule, + checkOsqueryResponseActionAuthz, }); response.valid.push(rule); @@ -279,6 +310,20 @@ const isEndpointResponseAction = ( ); }; +/** @private */ +const isOsqueryResponseAction = ( + ruleResponseAction: + | RuleResponseOsqueryAction + | RuleResponseEndpointAction + | OsqueryResponseAction + | EndpointResponseAction +): ruleResponseAction is OsqueryResponseAction | RuleResponseOsqueryAction => { + return ( + ('action_type_id' in ruleResponseAction && ruleResponseAction.action_type_id === '.osquery') || + ('actionTypeId' in ruleResponseAction && ruleResponseAction.actionTypeId === '.osquery') + ); +}; + /** @private */ const validateEndpointKillSuspendProcessResponseAction = ({ config, command }: ProcessesParams) => { if (config.overwrite && config.field) { diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts index ecce1e48069b3..80563eaf63d32 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/bulk_actions/route.ts @@ -299,6 +299,8 @@ export const performBulkActionRoute = ( rulePayload: {}, spaceId, existingRule: rule, + checkOsqueryResponseActionAuthz: + ctx.securitySolution.getCheckOsqueryResponseActionAuthz(), }); // during dry run only validation is getting performed and rule is not saved in ES, thus return early diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts index 5f1c3894f8ed5..478acb850363e 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/create_rule/route.ts @@ -98,6 +98,8 @@ export const createRuleRoute = (router: SecuritySolutionPluginRouter): void => { endpointService: ctx.securitySolution.getEndpointService(), rulePayload: request.body, spaceId: ctx.securitySolution.getSpaceId(), + checkOsqueryResponseActionAuthz: + ctx.securitySolution.getCheckOsqueryResponseActionAuthz(), }); const createdRule = await detectionRulesClient.createCustomRule({ diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts index 0573d44bf5110..648aba431cfb3 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/import_rules/route.ts @@ -183,6 +183,8 @@ export const importRulesRoute = ( endpointService, spaceId, rulesToImport: validatedActionRules, + checkOsqueryResponseActionAuthz: + ctx.securitySolution.getCheckOsqueryResponseActionAuthz(), }); const ruleChunks = chunk(CHUNK_PARSED_OBJECT_SIZE, validatedResponseActionsRules); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts index 60dea9b4707d3..30b1b789df13d 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/patch_rule/route.ts @@ -87,6 +87,8 @@ export const patchRuleRoute = (router: SecuritySolutionPluginRouter) => { rulePayload: request.body, spaceId: securitySolutionCtx.getSpaceId(), existingRule, + checkOsqueryResponseActionAuthz: + securitySolutionCtx.getCheckOsqueryResponseActionAuthz(), }); checkDefaultRuleExceptionListReferences({ exceptionLists: params.exceptions_list }); diff --git a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts index e863d84ce7b47..4419fa691ec8a 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/lib/detection_engine/rule_management/api/rules/update_rule/route.ts @@ -83,6 +83,8 @@ export const updateRuleRoute = (router: SecuritySolutionPluginRouter) => { rulePayload: request.body, spaceId: ctx.securitySolution.getSpaceId(), existingRule, + checkOsqueryResponseActionAuthz: + ctx.securitySolution.getCheckOsqueryResponseActionAuthz(), }); const updatedRule = await detectionRulesClient.updateRule({ diff --git a/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts b/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts index dd5c33ddb8d24..6d1370a6cd652 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts @@ -191,6 +191,9 @@ export class RequestContextFactory implements IRequestContextFactory { getEndpointService: () => endpointAppContextService, + getCheckOsqueryResponseActionAuthz: () => + (params) => plugins.osquery.checkResponseActionAuthz(request, params), + getConfig: () => config, getFrameworkRequest: () => frameworkRequest, diff --git a/x-pack/solutions/security/plugins/security_solution/server/types.ts b/x-pack/solutions/security/plugins/security_solution/server/types.ts index de1b03826e37c..b74c1400202f9 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/types.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/types.ts @@ -50,6 +50,7 @@ import type { MonitoringEntitySourceDataClient } from './lib/entity_analytics/pr import type { MlAuthz } from './lib/machine_learning/authz'; import type { SiemMigrationClients } from './lib/siem_migrations/types'; import type { EntityStoreCrudClient } from './lib/entity_analytics/entity_store/entity_store_crud_client'; +import type { CheckOsqueryResponseActionAuthz } from './endpoint/services/actions/utils/rule_response_actions_validators'; export { AppClient }; @@ -59,6 +60,7 @@ export interface SecuritySolutionApiRequestHandlerContext { getServerBasePath: () => string; getEndpointAuthz: () => Promise>; getEndpointService: () => EndpointAppContextService; + getCheckOsqueryResponseActionAuthz: () => CheckOsqueryResponseActionAuthz; getConfig: () => ConfigType; getFrameworkRequest: () => FrameworkRequest; getAppClient: () => AppClient; From d68bcb58873493b1f60c473bff18aa813d957f9f Mon Sep 17 00:00:00 2001 From: Tomasz Ciecierski Date: Thu, 2 Apr 2026 16:45:26 +0200 Subject: [PATCH 2/5] [Osquery] import fix --- x-pack/platform/plugins/shared/osquery/server/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/osquery/server/plugin.ts b/x-pack/platform/plugins/shared/osquery/server/plugin.ts index 1739c5b666f2b..02c90cfc0b5cb 100644 --- a/x-pack/platform/plugins/shared/osquery/server/plugin.ts +++ b/x-pack/platform/plugins/shared/osquery/server/plugin.ts @@ -51,7 +51,7 @@ import { CASE_ATTACHMENT_TYPE_ID } from '../common/constants'; import { createActionService } from './handlers/action/create_action_service'; import { backfillScheduleIds } from './lib/backfill_schedule_ids'; import { checkOsqueryResponseActionAuthz } from './lib/check_response_action_authz'; -import { CustomHttpRequestError } from '../common/error'; +import { CustomHttpRequestError } from './common/error'; import { SchemaService } from './lib/schema_service'; const BACKFILL_TASK_TYPE = 'osquery:backfillScheduleIds'; From a378e9eb1402188894b9bf3e74933be101a014a4 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:00:11 +0000 Subject: [PATCH 3/5] Changes from node scripts/lint_ts_projects --fix --- x-pack/platform/plugins/shared/osquery/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/platform/plugins/shared/osquery/tsconfig.json b/x-pack/platform/plugins/shared/osquery/tsconfig.json index 5fb84d2c97d1b..e81de3508cec4 100644 --- a/x-pack/platform/plugins/shared/osquery/tsconfig.json +++ b/x-pack/platform/plugins/shared/osquery/tsconfig.json @@ -94,6 +94,7 @@ "@kbn/unified-search-plugin", "@kbn/core-notifications-browser", "@kbn/core-chrome-browser", - "@kbn/scout" + "@kbn/scout", + "@kbn/core-http-server-mocks" ] } From 44f972a5b1dc6e16bf48ff6ef0332b7d82d4e712 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:13:26 +0000 Subject: [PATCH 4/5] Changes from node scripts/regenerate_moon_projects.js --update --- x-pack/platform/plugins/shared/osquery/moon.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/platform/plugins/shared/osquery/moon.yml b/x-pack/platform/plugins/shared/osquery/moon.yml index 4b03d51723312..4b6346a9542dd 100644 --- a/x-pack/platform/plugins/shared/osquery/moon.yml +++ b/x-pack/platform/plugins/shared/osquery/moon.yml @@ -86,6 +86,7 @@ dependsOn: - '@kbn/core-notifications-browser' - '@kbn/core-chrome-browser' - '@kbn/scout' + - '@kbn/core-http-server-mocks' tags: - plugin - prod From ee49ed4389ed6f164ed4ca0e0a43a88b9d72258c Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 2 Apr 2026 15:16:39 +0000 Subject: [PATCH 5/5] Changes from node scripts/eslint_all_files --no-cache --fix --- .../server/lib/check_response_action_authz.test.ts | 7 +++---- .../routes/live_query/create_live_query_route.ts | 10 ++++------ .../actions/utils/rule_response_actions_validators.ts | 3 +-- .../server/request_context_factory.ts | 4 ++-- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts b/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts index 5001260b39baa..776d8fc4c78f4 100644 --- a/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts +++ b/x-pack/platform/plugins/shared/osquery/server/lib/check_response_action_authz.test.ts @@ -13,15 +13,14 @@ describe('checkOsqueryResponseActionAuthz', () => { let request: KibanaRequest; let mockCoreStart: CoreStart; - const createMockCoreStart = (capabilities: Record): CoreStart => { - return { + const createMockCoreStart = (capabilities: Record): CoreStart => + ({ capabilities: { resolveCapabilities: jest.fn().mockResolvedValue({ osquery: capabilities, }), }, - } as unknown as CoreStart; - }; + } as unknown as CoreStart); beforeEach(() => { request = httpServerMock.createKibanaRequest(); diff --git a/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts b/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts index 57175d09d1947..43208101dc6d1 100644 --- a/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts +++ b/x-pack/platform/plugins/shared/osquery/server/routes/live_query/create_live_query_route.ts @@ -53,12 +53,10 @@ export const createLiveQueryRoute = (router: IRouter, osqueryContext: OsqueryApp async (context, request, response) => { const [coreStartServices, startPlugins] = await osqueryContext.getStartServices(); - const isInvalid = !( - await checkOsqueryResponseActionAuthz(coreStartServices, request, { - saved_query_id: request.body.saved_query_id, - pack_id: request.body.pack_id, - }) - ); + const isInvalid = !(await checkOsqueryResponseActionAuthz(coreStartServices, request, { + saved_query_id: request.body.saved_query_id, + pack_id: request.body.pack_id, + })); const client = await osqueryContext.service .getRuleRegistryService() diff --git a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts index daf13b1b7d120..770f92ba05d98 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/endpoint/services/actions/utils/rule_response_actions_validators.ts @@ -176,8 +176,7 @@ export const validateRuleResponseActions = async < if (checkOsqueryResponseActionAuthz) { const params = actionData.params; await checkOsqueryResponseActionAuthz({ - saved_query_id: - 'saved_query_id' in params ? params.saved_query_id : undefined, + saved_query_id: 'saved_query_id' in params ? params.saved_query_id : undefined, pack_id: 'pack_id' in params ? params.pack_id : undefined, }); } else { diff --git a/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts b/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts index 6d1370a6cd652..2c5a7a70c2785 100644 --- a/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts +++ b/x-pack/solutions/security/plugins/security_solution/server/request_context_factory.ts @@ -191,8 +191,8 @@ export class RequestContextFactory implements IRequestContextFactory { getEndpointService: () => endpointAppContextService, - getCheckOsqueryResponseActionAuthz: () => - (params) => plugins.osquery.checkResponseActionAuthz(request, params), + getCheckOsqueryResponseActionAuthz: () => (params) => + plugins.osquery.checkResponseActionAuthz(request, params), getConfig: () => config,