From 01a56a1a0b1d97a2df2df9cf0e259a6b58cbc887 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Wed, 30 Sep 2020 12:35:54 -0600 Subject: [PATCH 1/7] adds sequence callout for eql rule types --- .../add_exception_modal/index.test.tsx | 72 +++++++++++++++++++ .../exceptions/add_exception_modal/index.tsx | 10 +++ .../add_exception_modal/translations.ts | 8 +++ .../edit_exception_modal/index.test.tsx | 65 ++++++++++++++++- .../exceptions/edit_exception_modal/index.tsx | 16 +++++ .../edit_exception_modal/translations.ts | 8 +++ 6 files changed, 178 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx index 037462839c72d..35bd5ee572160 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx @@ -25,6 +25,11 @@ import * as helpers from '../helpers'; import { getExceptionListItemSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_item_schema.mock'; import { EntriesArray } from '../../../../../../lists/common/schemas/types'; import { ExceptionListItemSchema } from '../../../../../../lists/common'; +import { + getRulesEqlSchemaMock, + getRulesSchemaMock, +} from '../../../../../common/detection_engine/schemas/response/rules_schema.mocks'; +import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index'); jest.mock('../../../../common/lib/kibana'); @@ -34,6 +39,7 @@ jest.mock('../use_add_exception'); jest.mock('../use_fetch_or_create_rule_exception_list'); jest.mock('../builder'); jest.mock('../../../../shared_imports'); +jest.mock('../../../../detections/containers/detection_engine/rules/use_rule_async'); describe('When the add exception modal is opened', () => { const ruleName = 'test rule'; @@ -73,6 +79,9 @@ describe('When the add exception modal is opened', () => { }, ]); (useCurrentUser as jest.Mock).mockReturnValue({ username: 'test-username' }); + (useRuleAsync as jest.Mock).mockImplementation(() => ({ + rule: getRulesSchemaMock(), + })); }); afterEach(() => { @@ -193,6 +202,9 @@ describe('When the add exception modal is opened', () => { it('should contain the endpoint specific documentation text', () => { expect(wrapper.find('[data-test-subj="add-exception-endpoint-text"]').exists()).toBeTruthy(); }); + it('should not display the eql sequence callout', () => { + expect(wrapper.find('[data-test-subj="eql-sequence-callout"]').exists()).not.toBeTruthy(); + }); }); describe('when there is alert data passed to a detection list exception', () => { @@ -241,6 +253,66 @@ describe('When the add exception modal is opened', () => { .getDOMNode() ).toBeDisabled(); }); + it('should not display the eql sequence callout', () => { + expect(wrapper.find('[data-test-subj="eql-sequence-callout"]').exists()).not.toBeTruthy(); + }); + }); + + describe('when there is an exception being created on a sequence eql rule type', () => { + let wrapper: ReactWrapper; + beforeEach(async () => { + const alertDataMock: Ecs = { _id: 'test-id', file: { path: ['test/path'] } }; + (useRuleAsync as jest.Mock).mockImplementation(() => ({ + rule: { + ...getRulesEqlSchemaMock(), + query: + 'sequence [process where process.name = "test.exe"] [process where process.name = "explorer.exe"]', + }, + })); + wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + const callProps = ExceptionBuilderComponent.mock.calls[0][0]; + await waitFor(() => + callProps.onChange({ exceptionItems: [getExceptionListItemSchemaMock()] }) + ); + }); + it('has the add exception button enabled', () => { + expect( + wrapper.find('button[data-test-subj="add-exception-confirm-button"]').getDOMNode() + ).not.toBeDisabled(); + }); + it('should render the exception builder', () => { + expect(wrapper.find('[data-test-subj="alert-exception-builder"]').exists()).toBeTruthy(); + }); + it('should not prepopulate endpoint items', () => { + expect(defaultEndpointItems).not.toHaveBeenCalled(); + }); + it('should render the close on add exception checkbox', () => { + expect( + wrapper.find('[data-test-subj="close-alert-on-add-add-exception-checkbox"]').exists() + ).toBeTruthy(); + }); + it('should have the bulk close checkbox disabled', () => { + expect( + wrapper + .find('input[data-test-subj="bulk-close-alert-on-add-add-exception-checkbox"]') + .getDOMNode() + ).toBeDisabled(); + }); + it('should display the eql sequence callout', () => { + expect(wrapper.find('[data-test-subj="eql-sequence-callout"]').exists()).toBeTruthy(); + }); }); describe('when there is bulk-closeable alert data passed to an endpoint list exception', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index ad5bc98243467..f51e5ddcef2dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -19,6 +19,7 @@ import { EuiSpacer, EuiFormRow, EuiText, + EuiCallOut, } from '@elastic/eui'; import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import { @@ -353,6 +354,15 @@ export const AddExceptionModal = memo(function AddExceptionModal({ ruleExceptionList && ( <> + {isRuleEQLSequenceStatement && ( + <> + + + + )} {i18n.EXCEPTION_BUILDER_INFO} { const ruleName = 'test rule'; @@ -58,6 +64,9 @@ describe('When the edit exception modal is opened', () => { }, ]); (useCurrentUser as jest.Mock).mockReturnValue({ username: 'test-username' }); + (useRuleAsync as jest.Mock).mockImplementation(() => ({ + rule: getRulesSchemaMock(), + })); }); afterEach(() => { @@ -190,7 +199,58 @@ describe('When the edit exception modal is opened', () => { }); }); - describe('when an detection exception with entries is passed', () => { + describe('when an exception assigned to a sequence eql rule type is passed', () => { + let wrapper: ReactWrapper; + beforeEach(async () => { + (useRuleAsync as jest.Mock).mockImplementation(() => ({ + rule: { + ...getRulesEqlSchemaMock(), + query: + 'sequence [process where process.name = "test.exe"] [process where process.name = "explorer.exe"]', + }, + })); + wrapper = mount( + ({ eui: euiLightVars, darkMode: false })}> + + + ); + const callProps = ExceptionBuilderComponent.mock.calls[0][0]; + await waitFor(() => { + callProps.onChange({ exceptionItems: [...callProps.exceptionListItems] }); + }); + }); + it('has the edit exception button enabled', () => { + expect( + wrapper.find('button[data-test-subj="edit-exception-confirm-button"]').getDOMNode() + ).not.toBeDisabled(); + }); + it('renders the exceptions builder', () => { + expect(wrapper.find('[data-test-subj="edit-exception-modal-builder"]').exists()).toBeTruthy(); + }); + it('should not contain the endpoint specific documentation text', () => { + expect(wrapper.find('[data-test-subj="edit-exception-endpoint-text"]').exists()).toBeFalsy(); + }); + it('should have the bulk close checkbox disabled', () => { + expect( + wrapper + .find('input[data-test-subj="close-alert-on-add-edit-exception-checkbox"]') + .getDOMNode() + ).toBeDisabled(); + }); + it('should display the eql sequence callout', () => { + expect(wrapper.find('[data-test-subj="eql-sequence-callout"]').exists()).toBeTruthy(); + }); + }); + + describe('when a detection exception with entries is passed', () => { let wrapper: ReactWrapper; beforeEach(async () => { wrapper = mount( @@ -229,6 +289,9 @@ describe('When the edit exception modal is opened', () => { .getDOMNode() ).toBeDisabled(); }); + it('should not display the eql sequence callout', () => { + expect(wrapper.find('[data-test-subj="eql-sequence-callout"]').exists()).not.toBeTruthy(); + }); }); describe('when an exception with no entries is passed', () => { diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index 08f7e3af90d0c..ba432391888b8 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -246,6 +246,13 @@ export const EditExceptionModal = memo(function EditExceptionModal({ signalIndexName, ]); + const isRuleEQLSequenceStatement = useMemo((): boolean => { + if (maybeRule != null && maybeRule.query != null) { + return maybeRule.type === 'eql' && maybeRule.query.startsWith('sequence'); + } + return false; + }, [maybeRule]); + return ( @@ -265,6 +272,15 @@ export const EditExceptionModal = memo(function EditExceptionModal({ {!isSignalIndexLoading && !addExceptionIsLoading && !isIndexPatternLoading && ( <> + {isRuleEQLSequenceStatement && ( + <> + + + + )} {i18n.EXCEPTION_BUILDER_INFO} Date: Wed, 30 Sep 2020 17:22:39 -0600 Subject: [PATCH 2/7] moves logic to utils file --- .../security_solution/common/detection_engine/utils.ts | 8 ++++++++ .../components/exceptions/add_exception_modal/index.tsx | 8 ++++++++ .../components/exceptions/edit_exception_modal/index.tsx | 5 +++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index d7b23755699f5..bfcf7ffe3dafd 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -17,6 +17,14 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { return found.length > 0; }; +export const hasEqlSequenceQuery = (ruleQuery: string | undefined): boolean => { + if (ruleQuery != null) { + const parsedQuery = ruleQuery.split(' '); + return parsedQuery[0] === 'sequence' && parsedQuery[1] !== 'where'; + } + return false; +}; + export const isEqlRule = (ruleType: Type | undefined): boolean => ruleType === 'eql'; export const isThresholdRule = (ruleType: Type | undefined): boolean => ruleType === 'threshold'; export const isQueryRule = (ruleType: Type | undefined): boolean => diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx index f51e5ddcef2dc..bf483387580ce 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx @@ -21,6 +21,7 @@ import { EuiText, EuiCallOut, } from '@elastic/eui'; +import { hasEqlSequenceQuery, isEqlRule } from '../../../../../common/detection_engine/utils'; import { Status } from '../../../../../common/detection_engine/schemas/common/schemas'; import { ExceptionListItemSchema, @@ -316,6 +317,13 @@ export const AddExceptionModal = memo(function AddExceptionModal({ const addExceptionMessage = exceptionListType === 'endpoint' ? i18n.ADD_ENDPOINT_EXCEPTION : i18n.ADD_EXCEPTION; + const isRuleEQLSequenceStatement = useMemo((): boolean => { + if (maybeRule != null) { + return isEqlRule(maybeRule.type) && hasEqlSequenceQuery(maybeRule.query); + } + return false; + }, [maybeRule]); + return ( diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx index ba432391888b8..257c8e8c4d873 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx @@ -22,6 +22,7 @@ import { EuiCallOut, } from '@elastic/eui'; +import { hasEqlSequenceQuery, isEqlRule } from '../../../../../common/detection_engine/utils'; import { useFetchIndex } from '../../../containers/source'; import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index'; import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async'; @@ -247,8 +248,8 @@ export const EditExceptionModal = memo(function EditExceptionModal({ ]); const isRuleEQLSequenceStatement = useMemo((): boolean => { - if (maybeRule != null && maybeRule.query != null) { - return maybeRule.type === 'eql' && maybeRule.query.startsWith('sequence'); + if (maybeRule != null) { + return isEqlRule(maybeRule.type) && hasEqlSequenceQuery(maybeRule.query); } return false; }, [maybeRule]); From 78b4c59927040cd6a7c214938fc9f7de42c79830 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 1 Oct 2020 16:35:47 -0600 Subject: [PATCH 3/7] trims query string before logic --- .../plugins/security_solution/common/detection_engine/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index bfcf7ffe3dafd..bfe6f0df39452 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -19,7 +19,7 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { export const hasEqlSequenceQuery = (ruleQuery: string | undefined): boolean => { if (ruleQuery != null) { - const parsedQuery = ruleQuery.split(' '); + const parsedQuery = ruleQuery.trim().split(' '); return parsedQuery[0] === 'sequence' && parsedQuery[1] !== 'where'; } return false; From 5f296317e49ee05f5eb774f44699b80fb1fdc08b Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Thu, 1 Oct 2020 22:33:58 -0600 Subject: [PATCH 4/7] fixes logic and adds tests --- .../common/detection_engine/utils.test.ts | 25 ++++++++++++++++++- .../common/detection_engine/utils.ts | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts index ea50acc9b46be..d928a470b56e7 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { hasLargeValueList, hasNestedEntry, isThreatMatchRule } from './utils'; +import { hasEqlSequenceQuery, hasLargeValueList, hasNestedEntry, isThreatMatchRule } from './utils'; import { EntriesArray } from '../shared_imports'; describe('#hasLargeValueList', () => { @@ -113,3 +113,26 @@ describe('#hasNestedEntry', () => { }); }); }); + +describe('#hasEqlSequenceQuery', () => { + describe('when a non-sequence query is passed', () => { + const query = 'process where process.name == "regsvr32.exe"'; + it('should return false', () => { + expect(hasEqlSequenceQuery(query)).toEqual(false); + }); + }); + + describe('when a sequence query is passed', () => { + const query = 'sequence [process where process.name = "test.exe"]'; + it('should return true', () => { + expect(hasEqlSequenceQuery(query)).toEqual(true); + }); + }); + + describe('when a sequence query is passed with extra white space', () => { + const query = ' sequence [process where process.name = "test.exe"]'; + it('should return true', () => { + expect(hasEqlSequenceQuery(query)).toEqual(true); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index bfe6f0df39452..f12dc14f7ebea 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -19,7 +19,7 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { export const hasEqlSequenceQuery = (ruleQuery: string | undefined): boolean => { if (ruleQuery != null) { - const parsedQuery = ruleQuery.trim().split(' '); + const parsedQuery = ruleQuery.split(' ').filter((word) => word !== ''); return parsedQuery[0] === 'sequence' && parsedQuery[1] !== 'where'; } return false; From e489d8b63f5ad393083d11deb7831840af2e31b6 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 2 Oct 2020 11:16:54 -0600 Subject: [PATCH 5/7] adds coverage for new line --- .../common/detection_engine/utils.test.ts | 11 +++++++++-- .../common/detection_engine/utils.ts | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts index d928a470b56e7..2b354eefdf056 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts @@ -129,10 +129,17 @@ describe('#hasEqlSequenceQuery', () => { }); }); - describe('when a sequence query is passed with extra white space', () => { - const query = ' sequence [process where process.name = "test.exe"]'; + describe('when a sequence query is passed with extra white space and new line characters', () => { + const query = ' sequence \n [process where process.name = "test.exe"]'; it('should return true', () => { expect(hasEqlSequenceQuery(query)).toEqual(true); }); }); + + describe('when a non-sequence query is passed using the word sequence', () => { + const query = 'sequence \n where true'; + it('should return false', () => { + expect(hasEqlSequenceQuery(query)).toEqual(false); + }); + }); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index f12dc14f7ebea..af69c0c93c657 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -19,7 +19,7 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { export const hasEqlSequenceQuery = (ruleQuery: string | undefined): boolean => { if (ruleQuery != null) { - const parsedQuery = ruleQuery.split(' ').filter((word) => word !== ''); + const parsedQuery = ruleQuery.split(' ').filter((word) => word !== '' && word !== '\n'); return parsedQuery[0] === 'sequence' && parsedQuery[1] !== 'where'; } return false; From 5db391998a2be09404b6f1e0139d8e6b6286efc1 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 2 Oct 2020 11:40:18 -0600 Subject: [PATCH 6/7] adds regex for validation --- .../common/detection_engine/utils.test.ts | 13 ++++++++++--- .../common/detection_engine/utils.ts | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts index 2b354eefdf056..202733574b69f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.test.ts @@ -129,15 +129,22 @@ describe('#hasEqlSequenceQuery', () => { }); }); - describe('when a sequence query is passed with extra white space and new line characters', () => { - const query = ' sequence \n [process where process.name = "test.exe"]'; + describe('when a sequence query is passed with extra white space and escape characters', () => { + const query = '\tsequence \n [process where process.name = "test.exe"]'; it('should return true', () => { expect(hasEqlSequenceQuery(query)).toEqual(true); }); }); describe('when a non-sequence query is passed using the word sequence', () => { - const query = 'sequence \n where true'; + const query = 'sequence where true'; + it('should return false', () => { + expect(hasEqlSequenceQuery(query)).toEqual(false); + }); + }); + + describe('when a non-sequence query is passed using the word sequence with extra white space and escape characters', () => { + const query = ' sequence\nwhere\ttrue'; it('should return false', () => { expect(hasEqlSequenceQuery(query)).toEqual(false); }); diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts index af69c0c93c657..d35c5980d96a2 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts @@ -19,7 +19,7 @@ export const hasNestedEntry = (entries: EntriesArray): boolean => { export const hasEqlSequenceQuery = (ruleQuery: string | undefined): boolean => { if (ruleQuery != null) { - const parsedQuery = ruleQuery.split(' ').filter((word) => word !== '' && word !== '\n'); + const parsedQuery = ruleQuery.trim().split(/[ \t\r\n]+/); return parsedQuery[0] === 'sequence' && parsedQuery[1] !== 'where'; } return false; From bc046d40c3f117b4fbdfb06203e5f0d22e44abf9 Mon Sep 17 00:00:00 2001 From: Davis Plumlee Date: Fri, 2 Oct 2020 12:53:53 -0600 Subject: [PATCH 7/7] changes language --- .../components/exceptions/add_exception_modal/translations.ts | 2 +- .../components/exceptions/edit_exception_modal/translations.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/translations.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/translations.ts index 667a219ed4dfd..794b38484288c 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/translations.ts @@ -86,6 +86,6 @@ export const ADD_EXCEPTION_SEQUENCE_WARNING = i18n.translate( 'xpack.securitySolution.exceptions.addException.sequenceWarning', { defaultMessage: - 'This rule is a sequence statement. The exception created will apply to all events in the sequence.', + "This rule's query contains an EQL sequence statement. The exception created will apply to all events in the sequence.", } ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts index 21e204079e499..f07a1559e4dfa 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/translations.ts @@ -94,6 +94,6 @@ export const EDIT_EXCEPTION_SEQUENCE_WARNING = i18n.translate( 'xpack.securitySolution.exceptions.editException.sequenceWarning', { defaultMessage: - 'This rule is a sequence statement. The exception modified will apply to all events in the sequence.', + "This rule's query contains an EQL sequence statement. The exception modified will apply to all events in the sequence.", } );