Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] Discuss rule field diffs and conflicts to handle in the UI #148913

Closed

Conversation

banderror
Copy link
Contributor

@banderror banderror commented Jan 13, 2023

Epics: https://github.com/elastic/security-team/issues/1974 (internal), #174168

Summary

This "PR" provides some examples we could use as a foundation for discussing and designing the UI for showing rule field diffs and resolving conflicts.

First, it describes six typical situations we can have for any rule field.

Then, it expands on how each of these six situations can look like for the following fields:

  • rule name
  • rule tags
  • data source: index patterns or data view
  • rule query

NOTE: You can comment this PR description line-by-line in security_solution/common/detection_engine/prebuilt_rules/rule_field_updates_and_conflicts.md.

There's no intention to merge this PR.

Typical situations

Given a rule that can be upgraded, for every top-level field of this rule, we will have 3 versions of the field:

  • Base version: the original stock version of the field shipped by Elastic and installed by the user.
  • Current version: the version currently installed, potentially with user’s customizations.
    • If the user didn't customize this field, current == base.
    • If the field was customized, current != base.
  • Target version: the latest stock version of the field shipped by Elastic and available for upgrade.

Using the above 3 versions, the app will attempt to automatically merge them into the final Merged version. The merge can either succeed, or fail with a conflict. Users will have to resolve conflicts manually in the UI. This automatic merge is supposed to improve the UX by reducing the amount of work our users will have to do during rule upgrade.

Here are six typical situations that we will have to handle in the app:

  1. base=A, current=A, target=A => merged=A, conflict=false
    • Situation: Stock rule was installed. The user didn't customize the field. Elastic doesn't have any updates for this field.
    • How to handle: ❓ Don't show the field in the upgrade flyout?
  2. base=A, current=A, target=B => merged=B, conflict=false
    • Situation: Stock rule was installed. The user didn't customize the field. Elastic updated this field in the latest version.
    • How to handle: Show this field in the flyout. Automatically pick the updated version from Elastic as the merged one.
  3. base=A, current=B, target=A => merged=B, conflict=false
    • Situation: Stock rule was installed. The user customized the field. Elastic doesn't have any updates for this field.
    • How to handle: ❓ Don't show the field in the upgrade flyout? Or show the field, and automatically pick the current customized version as the merged one?
  4. base=A, current=B, target=B => merged=B, conflict=false
    • Situation: Stock rule was installed. The user customized the field. Elastic updated this field in the latest version to exactly the same value as the user did it in their cluster.
    • How to handle: ❓ Don't show the field in the upgrade flyout?
  5. base=A, current=B, target=C => merged=D, conflict=false
    • Situation: Stock rule was installed. The user customized the field. Elastic updated this field in the latest version. The customized version and the Elastic's version are different. The app was able to automatically merge them into a new version without conflict.
    • How to handle: ❓ Show this field in the flyout. Let the user see all the 4 versions if they want. Automatically pick the merged version. Don't show it as a conflict. Maybe highlight that the field was auto-merged (user might want to review and fix the merged version)?
  6. base=A, current=B, target=C => merged=C, conflict=true
    • Situation: Stock rule was installed. The user customized the field. Elastic updated this field in the latest version. The customized version and the Elastic's version are different. The app was not able to automatically merge them into a new version because of a conflict.
    • How to handle: ❓ Show this field in the flyout. Let the user see all the 4 versions if they want. Automatically pick the target (or current?) version. Show it as a conflict at the top of the flyout.

Let's review these situations by example, using a few different fields.

Rule name

Rule name is one of the most simple fields to reason about: it's a one-line string.

  1. base=A, current=A, target=A => merged=A, conflict=false

    A = 'GCP Storage Bucket Deletion'
    
  2. base=A, current=A, target=B => merged=B, conflict=false

    A = 'GCP Storage Bucket Deletion'
    B = 'Google Cloud Storage Bucket Deletion'
    
  3. base=A, current=B, target=A => merged=B, conflict=false

    A = 'GCP Storage Bucket Deletion'
    B = 'GCP Storage Bucket Deletion (Critical Backups)'
    
  4. base=A, current=B, target=B => merged=B, conflict=false

    A = 'GCP Storage Bucket Deletion'
    B = 'Google Cloud Storage Bucket Deletion'
    
  5. base=A, current=B, target=C => merged=D, conflict=false

    If A != B != C, it should probably always be a conflict.
    See below.
    
  6. base=A, current=B, target=C => merged=C, conflict=true

    A = 'GCP Storage Bucket Deletion'
    B = 'GCP Storage Bucket Deletion (Critical Backups)'
    C = 'Google Cloud Storage Bucket Deletion'
    
    Notice how the B implies that the user has likely narrowed down the query to something more
    specific to their cloud environment. Auto-merging into C w/o a conflict would mean obscuring
    the change in the query which is no longer generic and is not consistent with C.
    
    Technically, in this case we might be able to auto-merge into this:
    D = 'Google Cloud Storage Bucket Deletion (Critical Backups)'
    
    However in general, the problem with that is we can't guarantee that any technical auto-merge
    won't break the consistency with changes in other rule fields.
    

Rule tags

The tags field is a slightly more complex field than the rule name, because it's an array of simple single-line strings. This gives us an opportunity to auto-merge changes in it.

  1. base=A, current=A, target=A => merged=A, conflict=false

    A = ['Elastic', 'Cloud', 'GCP']
    
  2. base=A, current=A, target=B => merged=B, conflict=false

    A = ['Elastic', 'Cloud', 'GCP']
    B = ['Elastic', 'Cloud', 'GCP', 'SecOps']
    
  3. base=A, current=B, target=A => merged=B, conflict=false

    A = ['Elastic', 'Cloud', 'GCP']
    B = ['Elastic', 'Cloud', 'GCP', 'Critical Backups']
    
  4. base=A, current=B, target=B => merged=B, conflict=false

    A = ['Elastic', 'Cloud', 'GCP']
    B = ['Elastic', 'Cloud', 'GCP', 'SecOps']
    
  5. base=A, current=B, target=C => merged=D, conflict=false

    Example 1:
    A = ['Elastic', 'Cloud', 'GCP']
    B = ['Elastic', 'Cloud', 'GCP', 'Critical Backups']
    C = ['Elastic', 'Cloud', 'GCP', 'SecOps']
    D = ['Elastic', 'Cloud', 'GCP', 'SecOps', 'Critical Backups']
    
    Example 2:
    A = ['Elastic', 'Cloud', 'GCP']
    B = ['Cloud', 'SecOps', 'Critical Backups']
    C = ['Elastic', 'Cloud', 'Google Cloud', 'SecOps']
    D = ['Cloud', 'Google Cloud', 'SecOps', 'Critical Backups']
    
  6. base=A, current=B, target=C => merged=C, conflict=true

    If A != B != C, it should always be possible to merge them without a conflict.
    

Data source: index patterns or data view

Data source is an object that can represent two different types of sources:

  • An array of index patterns. Each pattern is a simple single-line string.
    {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    
  • A data view stored in Kibana and referenced from the rule by its ID.
    {
      type: 'data_view',
      data_view_id: 'logs-*'
    }
    

Screenshot 2023-01-13 at 19 16 32

Screenshot 2023-01-13 at 19 16 48

Examples of situations:

  1. base=A, current=A, target=A => merged=A, conflict=false

    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    
  2. base=A, current=A, target=B => merged=B, conflict=false

    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    B = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*', '-*elastic-cloud-logs-*']
    }
    
  3. base=A, current=B, target=A => merged=B, conflict=false

    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    B = {
      type: 'data_view',
      data_view_id: 'logs-*'
    }
    
  4. base=A, current=B, target=B => merged=B, conflict=false

    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    B = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*', 'winlogbeat-*']
    }
    
  5. base=A, current=B, target=C => merged=D, conflict=false

    Example 1 (user added their Cross-Cluster Search index patterns, Elastic added a new pattern):
    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    B = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*', 'remote_cluster:filebeat-*', 'remote_cluster:logs-*']
    }
    C = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*', '-*elastic-cloud-logs-*']
    }
    D = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*', 'remote_cluster:filebeat-*', 'remote_cluster:logs-*', '-*elastic-cloud-logs-*']
    }
    
    Example 2 (user replaced index patterns with their own Cross-Cluster Search patterns,
    Elastic made them more specific + added a new one):
    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    B = {
      type: 'index_patterns',
      index_patterns: ['remote_cluster:filebeat-*', 'remote_cluster:logs-*']
    }
    C = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-gcp-*', '-*elastic-cloud-logs-*']
    }
    D = {
      type: 'index_patterns',
      index_patterns: ['remote_cluster:filebeat-*', 'remote_cluster:logs-*', 'logs-gcp-*', '-*elastic-cloud-logs-*']
    }
    
  6. base=A, current=B, target=C => merged=C, conflict=true

    A = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-*']
    }
    B = {
      type: 'data_view',
      data_view_id: 'logs-*'
    }
    C = {
      type: 'index_patterns',
      index_patterns: ['filebeat-*', 'logs-gcp-*', '-*elastic-cloud-logs-*']
    }
    

Query

Rule query is a complex object that can represent two different types of queries:

  • Inline query. Stored in the rule:
    {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: [{...}, {...}]
    }
    
  • Saved query. Stored as a separate object in Kibana and can be shared between multiple rules and reused in other places in Kibana:
    {
      type: 'saved_query',
      saved_query_id: 'a3a74be0-936a-11ed-b5ba-97720cfe8cbc'
    }
    

Screenshot 2023-01-13 at 18 42 52

Screenshot 2023-01-13 at 19 15 34

Examples of situations:

  1. base=A, current=A, target=A => merged=A, conflict=false

    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    
  2. base=A, current=A, target=B => merged=B, conflict=false

    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: []
    }
    
  3. base=A, current=B, target=A => merged=B, conflict=false

    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: [
        {
          meta: {
            alias: 'Not Windows or Linux',
            type: 'combined',
            relation: 'AND',
            params: [
              { query: {...}, meta: {...} },
              { query: {...}, meta: {...} },
            ]
          }
        }
      ]
    }
    
  4. base=A, current=B, target=B => merged=B, conflict=false

    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: []
    }
    
  5. base=A, current=B, target=C => merged=D, conflict=false

    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: [
        {
          meta: {
            alias: 'Not Windows or Linux',
            type: 'combined',
            relation: 'AND',
            params: [
              { query: {...}, meta: {...} },
              { query: {...}, meta: {...} },
            ]
          }
        }
      ]
    }
    C = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: []
    }
    D = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: [
        {
          meta: {
            alias: 'Not Windows or Linux',
            type: 'combined',
            relation: 'AND',
            params: [
              { query: {...}, meta: {...} },
              { query: {...}, meta: {...} },
            ]
          }
        }
      ]
    }
    
  6. base=A, current=B, target=C => merged=C, conflict=true

    Example 1:
    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'saved_query',
      saved_query_id: 'a3a74be0-936a-11ed-b5ba-97720cfe8cbc'
    }
    C = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: []
    }
    
    Example 2:
    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:* and event.category:network',
      language: 'kuery',
      filters: []
    }
    C = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: []
    }
    
    Example 3:
    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'lucene',
      filters: []
    }
    C = {
      type: 'inline_query',
      query: 'host.name:* and event.kind:alert',
      language: 'kuery',
      filters: []
    }
    
    Example 4:
    A = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: []
    }
    B = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: [
        {
          "meta": {
            "type": "combined",
            "relation": "AND",
            "params": [
              {
                "query": {
                  "match_phrase": {
                    "host.os.family": "windows"
                  }
                },
                "meta": {
                  "negate": true,
                  "key": "host.os.family",
                  "field": "host.os.family",
                  "params": {
                    "query": "windows"
                  },
                  "type": "phrase",
                  "disabled": false,
                  "alias": null
                }
              },
              {
                "meta": {
                  "negate": true,
                  "key": "host.os.family",
                  "field": "host.os.family",
                  "params": {
                    "query": "linux"
                  },
                  "type": "phrase",
                  "disabled": false,
                  "alias": null
                },
                "query": {
                  "match_phrase": {
                    "host.os.family": "linux"
                  }
                }
              }
            ],
            "disabled": false,
            "negate": false,
            "alias": "Not Windows or Linux"
          },
          "query": {},
          "$state": {
            "store": "appState"
          }
        }
      ]
    }
    C = {
      type: 'inline_query',
      query: 'host.name:*',
      language: 'kuery',
      filters: [
        {
          "$state": {
            "store": "appState"
          },
          "meta": {
            "type": "combined",
            "relation": "AND",
            "params": [
              {
                "query": {
                  "exists": {
                    "field": "source.ip"
                  }
                },
                "meta": {
                  "negate": false,
                  "key": "source.ip",
                  "field": "source.ip",
                  "value": "exists",
                  "type": "exists",
                  "disabled": false,
                  "alias": null
                }
              },
              {
                "meta": {
                  "negate": false,
                  "key": "destination.ip",
                  "field": "destination.ip",
                  "value": "exists",
                  "type": "exists",
                  "disabled": false,
                  "alias": null
                },
                "query": {
                  "exists": {
                    "field": "destination.ip"
                  }
                }
              }
            ],
            "disabled": false,
            "negate": false,
            "alias": null
          },
          "query": {}
        }
      ]
    }
    

@banderror banderror added discuss release_note:skip Skip the PR/issue when compiling release notes Team:Detections and Resp Security Detection Response Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Detection Rule Management Security Detection Rule Management Team skip-ci Feature:Prebuilt Detection Rules Security Solution Prebuilt Detection Rules area labels Jan 13, 2023
@banderror banderror self-assigned this Jan 13, 2023
@banderror banderror changed the title [Security Solution] Discuss: rule field updates and conflicts [Security Solution] Discuss: rule field diffs and conflicts Jan 13, 2023
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 3 times, most recently from 9c046d7 to 26f09d3 Compare January 13, 2023 18:43
@banderror banderror marked this pull request as ready for review January 13, 2023 18:46
@banderror banderror requested a review from a team as a code owner January 13, 2023 18:46
@elasticmachine
Copy link
Contributor

Pinging @elastic/security-detections-response (Team:Detections and Resp)

@elasticmachine
Copy link
Contributor

Pinging @elastic/security-solution (Team: SecuritySolution)

@banderror banderror changed the title [Security Solution] Discuss: rule field diffs and conflicts [Security Solution] Discuss rule field diffs and conflicts to handle in the UI Jan 13, 2023
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 5 times, most recently from 3c54cd4 to 1d8ffe0 Compare January 18, 2023 12:06
@ARWNightingale
Copy link

Hi All, Working to the examples above of the rule update, this link shows how each update/conflict can be displayed, please comment on the figma doc. Note I have not shown the UI for the rule name, just want to clarify the decision on this.

https://www.figma.com/file/gLHm8LpTtSkAUQHrkG3RHU/%5B8.7%5D-%5BRules%5D-Rule-Immutability%2FCustomization?node-id=2386%3A442678&t=c02QLjx393ELtooj-1


1. **`base=A, current=A, target=A => merged=A, conflict=false`**
- **Situation**: Stock rule was installed. The user didn't customize the field. Elastic doesn't have any updates for this field.
- **How to handle**: ❓ Don't show the field in the upgrade flyout?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the current designs, I don't believe the user would be able to view this rule in the upgrade flyout as it wouldn't even show up in the Rule Updates table since it has no changes, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@spong Nope, that's two different things:

  1. A rule will be shown in the Rule Updates table if there's a rule.version of this rule which is higher than the currently installed rule.version.
  2. In this doc, however, we're looking at six possible situations for rule fields. Once a rule is shown in the table, some of its fields will have updates, and some of them won't. I assume most of the fields would fall into the first "typical situation".

In other words, base, current, target and merged are versions of a field, not versions of the whole rule.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that was the idea, so if there essentially no update we should not bother the user with this information? if we can do that?

- **How to handle**: Show this field in the flyout. Automatically pick the updated version from Elastic as the merged one.
3. **`base=A, current=B, target=A => merged=B, conflict=false`**
- **Situation**: Stock rule was installed. The user customized the field. Elastic doesn't have any updates for this field.
- **How to handle**: ❓ Don't show the field in the upgrade flyout? Or show the field, and automatically pick the current customized version as the merged one?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still show the field (as it is a delta from before), but TBD on if automatically picking the customized version as the merged one. I think we need to decide on the validation flow a little, e.g. should we even auto-select one of the three radio buttons, or leave the group un-selected, thus requiring the user to select one to pass validation? Will comment on the designs with regards to this 👍

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good to me, would be good to get lots of opinions on.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some thoughts, I'm not sure about that, but:

I'd definitely lean towards auto-selecting in the 3rd case (the field would pass validation), but (maybe?) not auto-selecting in the 5th case where we will have auto-merged fields (such field would require the user to click "ok" or something like that to confirm that the auto-merge was done correctly; after that, the field would pass validation). I left a related comment in Figma.

I think this is about finding a balance between the easiness of upgrading rules and safety.

- **How to handle**: ❓ Don't show the field in the upgrade flyout? Or show the field, and automatically pick the current customized version as the merged one?
4. **`base=A, current=B, target=B => merged=B, conflict=false`**
- **Situation**: Stock rule was installed. The user customized the field. Elastic updated this field in the latest version to exactly the same value as the user did it in their cluster.
- **How to handle**: ❓ Don't show the field in the upgrade flyout?
Copy link
Member

@spong spong Jan 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to situation 1, since there ends up being no delta would this rule even show up in the Rule Updates table?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explained the difference in #148913 (comment)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, ideally not, can this be checked prior to loading the updates table though?

Copy link
Contributor Author

@banderror banderror Jan 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ARWNightingale If all fields of a given rule fall into this 4th category, or resolve into "no updates are required" in any other way, I think technically we definitely could hide this rule in the Rule Updates table. I think the question is should we do that? Because:

  • The chance of every single customized field having the same value as the corresponding updates from Elastic approaches zero, I'd guess.
  • In reality, it's possible to get a new version of Elastic rule shipped with no real updates to the content. We track this bug in [Security Solution] Side effects of no-content version bumps in prebuilt rules #130576. If we hide such no-content rule updates, we would:
    • on one hand, improve the UX
    • on the other hand, hide this bug

I'd say let's try to get the bug fixed on the detection-rules package side (its release process), and only if it's not fully possible handle this in the app.

@banderror banderror force-pushed the rule-field-updates-and-conflicts branch from 1d8ffe0 to 23ce178 Compare January 19, 2023 11:40
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 4 times, most recently from 7fb2b64 to 668d7fe Compare March 7, 2023 17:54
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 5 times, most recently from 69a294b to 4bcfc40 Compare March 27, 2023 09:22
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 3 times, most recently from c707eb5 to d4a9de1 Compare April 4, 2023 09:50
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 4 times, most recently from df3988c to 1da1c0d Compare April 12, 2023 10:41
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch 4 times, most recently from 2d81e04 to a12ef1b Compare April 25, 2023 08:47
@banderror banderror force-pushed the rule-field-updates-and-conflicts branch from a12ef1b to efd8a1c Compare April 26, 2023 09:37
@banderror
Copy link
Contributor Author

I'm closing this one since we've had an initial discussion and don't need to make any further decisions. We can revisit this PR later -- once we start working on customizing prebuilt rules. By then we'll probably have multiple dedicated tickets for handling conflicts -- one per each rule field or set of related fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss Feature:Prebuilt Detection Rules Security Solution Prebuilt Detection Rules area release_note:skip Skip the PR/issue when compiling release notes skip-ci Team:Detection Rule Management Security Detection Rule Management Team Team:Detections and Resp Security Detection Response Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants