diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx
index 9907aea709e58..bcabd1325ed51 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx
@@ -16,6 +16,14 @@ import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '..
import { useUserPrivileges } from '../../../../common/components/user_privileges';
import { TableId } from '@kbn/securitysolution-data-table';
import { TimelineId } from '../../../../../common/types/timeline';
+import { useListsConfig } from '../../../containers/detection_engine/lists/use_lists_config';
+
+jest.mock('../../../containers/detection_engine/lists/use_lists_config');
+
+const mockUseHasSecurityCapability = jest.fn().mockReturnValue(false);
+jest.mock('../../../../helper_hooks', () => ({
+ useHasSecurityCapability: () => mockUseHasSecurityCapability(),
+}));
jest.mock('../../../../common/components/user_privileges');
@@ -98,6 +106,14 @@ const openAlertDetailsPageButton = '[data-test-subj="open-alert-details-page-men
const applyAlertTagsButton = '[data-test-subj="alert-tags-context-menu-item"]';
describe('Alert table context menu', () => {
+ beforeEach(() => {
+ (useListsConfig as jest.Mock).mockImplementation(() => ({
+ loading: false,
+ needsConfiguration: false,
+ }));
+ mockUseHasSecurityCapability.mockReturnValue(true);
+ });
+
describe('Case actions', () => {
test('it render AddToCase context menu item if timelineId === TimelineId.detectionsPage', () => {
const wrapper = mount(, {
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
index c2d477d5a87c6..398b2dc524c20 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
@@ -35,7 +35,7 @@ import { useSignalIndex } from '../../../containers/detection_engine/alerts/use_
import { EventFiltersFlyout } from '../../../../management/pages/event_filters/view/components/event_filters_flyout';
import { useAlertsActions } from './use_alerts_actions';
import { useExceptionFlyout } from './use_add_exception_flyout';
-import { useExceptionActions } from './use_add_exception_actions';
+import { useAlertExceptionActions } from './use_add_exception_actions';
import { useEventFilterModal } from './use_event_filter_modal';
import type { Status } from '../../../../../common/api/detection_engine';
import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/timeline/body/translations';
@@ -195,7 +195,7 @@ const AlertContextMenuComponent: React.FC {
+ const { exceptionActionItems } = useExceptionActions({
+ isEndpointAlert,
+ onAddExceptionTypeClick,
+ });
+
+ const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } =
+ useListsConfig();
+ const canReadEndpointExceptions = useHasSecurityCapability('writeEndpointExceptions');
+
+ const canAccessRuleExceptions = useMemo(
+ () => !listsConfigLoading && !needsListsConfiguration && canReadEndpointExceptions,
+ [canReadEndpointExceptions, listsConfigLoading, needsListsConfiguration]
+ );
+ // Endpoint exceptions are available for:
+ // Serverless Endpoint Essentials/Complete PLI and
+ // on ESS Security Kibana sub-feature Endpoint Exceptions (enabled when Security feature is enabled)
+ if (canAccessRuleExceptions) {
+ return { exceptionActionItems };
+ }
+
+ return {
+ exceptionActionItems: exceptionActionItems.map((item) => {
+ if (item.name === ACTION_ADD_ENDPOINT_EXCEPTION) {
+ item.disabled = true;
+ }
+ return item;
+ }),
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx
index d342092dd3d9d..9fb4ea3e17d3d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.test.tsx
@@ -13,12 +13,12 @@ import type { TakeActionDropdownProps } from '.';
import { TakeActionDropdown } from '.';
import { generateAlertDetailsDataMock } from '../../../common/components/event_details/__mocks__';
import { getDetectionAlertMock } from '../../../common/mock/mock_detection_alerts';
-import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
+import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { TimelineId } from '../../../../common/types/timeline';
import { TestProviders } from '../../../common/mock';
import { mockTimelines } from '../../../common/mock/mock_timelines_plugin';
import { createStartServicesMock } from '../../../common/lib/kibana/kibana_react.mock';
-import { useKibana, useGetUserCasesPermissions, useHttp } from '../../../common/lib/kibana';
+import { useGetUserCasesPermissions, useHttp, useKibana } from '../../../common/lib/kibana';
import { mockCasesContract } from '@kbn/cases-plugin/public/mocks';
import { initialUserPrivilegesState as mockInitialUserPrivilegesState } from '../../../common/components/user_privileges/user_privileges_context';
import { useUserPrivileges } from '../../../common/components/user_privileges';
@@ -30,15 +30,17 @@ import {
import { endpointMetadataHttpMocks } from '../../../management/pages/endpoint_hosts/mocks';
import type { HttpSetup } from '@kbn/core/public';
import {
- isAlertFromEndpointEvent,
isAlertFromEndpointAlert,
+ isAlertFromEndpointEvent,
} from '../../../common/utils/endpoint_alert_check';
import { getUserPrivilegesMockDefaultValue } from '../../../common/components/user_privileges/__mocks__';
import { allCasesPermissions } from '../../../cases_test_utils';
import { HostStatus } from '../../../../common/endpoint/types';
import { ENDPOINT_CAPABILITIES } from '../../../../common/endpoint/service/response_actions/constants';
import { ALERT_TAGS_CONTEXT_MENU_ITEM_TITLE } from '../../../common/components/toolbar/bulk_actions/translations';
+import { useListsConfig } from '../../containers/detection_engine/lists/use_lists_config';
+jest.mock('../../containers/detection_engine/lists/use_lists_config');
jest.mock('../../../common/components/user_privileges');
jest.mock('../user_info', () => ({
@@ -92,6 +94,11 @@ jest.mock('../../containers/detection_engine/alerts/use_host_isolation_status',
jest.mock('../../../common/components/user_privileges');
+const mockUseHasSecurityCapability = jest.fn().mockReturnValue(false);
+jest.mock('../../../helper_hooks', () => ({
+ useHasSecurityCapability: () => mockUseHasSecurityCapability(),
+}));
+
describe('take action dropdown', () => {
let defaultProps: TakeActionDropdownProps;
let mockStartServicesMock: ReturnType;
@@ -131,6 +138,12 @@ describe('take action dropdown', () => {
});
(useHttp as jest.Mock).mockReturnValue(mockStartServicesMock.http);
+
+ (useListsConfig as jest.Mock).mockImplementation(() => ({
+ loading: false,
+ needsConfiguration: false,
+ }));
+ mockUseHasSecurityCapability.mockReturnValue(true);
});
afterEach(() => {
@@ -206,6 +219,7 @@ describe('take action dropdown', () => {
).toEqual('Add Endpoint exception');
});
});
+
test('should render "Add rule exception"', async () => {
await waitFor(() => {
expect(wrapper.find('[data-test-subj="add-exception-menu-item"]').first().text()).toEqual(
diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
index e7703c86f23bb..67175f05ece2e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx
@@ -10,6 +10,7 @@ import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui';
import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import { TableId } from '@kbn/securitysolution-data-table';
+import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step';
import {
AlertsCasesTourSteps,
@@ -17,9 +18,8 @@ import {
} from '../../../common/components/guided_onboarding_tour/tour_config';
import { isActiveTimeline } from '../../../helpers';
import { useResponderActionItem } from '../endpoint_responder';
-import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy';
import { TAKE_ACTION } from '../alerts_table/additional_filters_action/translations';
-import { useExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions';
+import { useAlertExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions';
import { useAlertsActions } from '../alerts_table/timeline_actions/use_alerts_actions';
import { useInvestigateInTimeline } from '../alerts_table/timeline_actions/use_investigate_in_timeline';
@@ -157,7 +157,7 @@ export const TakeActionDropdown = React.memo(
[onAddExceptionTypeClick]
);
- const { exceptionActionItems } = useExceptionActions({
+ const { exceptionActionItems } = useAlertExceptionActions({
isEndpointAlert: isAlertFromEndpointAlert({ ecsData }),
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});