Skip to content

[Security Solution] Adds mitre_attack plugin and MITRE ATT&CK data client - #288525

Open
dplumlee wants to merge 3 commits into
elastic:mainfrom
dplumlee:managed-mitre-m1-plugin
Open

[Security Solution] Adds mitre_attack plugin and MITRE ATT&CK data client#288525
dplumlee wants to merge 3 commits into
elastic:mainfrom
dplumlee:managed-mitre-m1-plugin

Conversation

@dplumlee

@dplumlee dplumlee commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Completes: https://github.com/elastic/security-team/issues/19072 (internal)
Related Epic: https://github.com/elastic/security-team/issues/17157 (internal)
Builds on: #287226

Adds the mitre_attack plugin, which is the backend data layer for the managed MITRE initiative. It takes the artifact and packages that were merged in #287226 and actually puts them to use: registering a Saved Object type for MITRE entities, populating it at startup from the bundled artifact, and exposing a read-only data client that the API routes will consume in later PRs.

Everything is behind xpack.mitreAttack.managedSourceEnabled, which is off by default. With the flag off the plugin does nothing at all, so this PR has no effect on any existing behavior. The legacy mitre_tactics_techniques.ts blob remains the only data source until the UI cutover lands.

What has been added

  • mitre_attack plugin: new plugin at x-pack/solutions/security/plugins/mitre_attack with both a server and a minimal browser side. The browser plugin exists only to surface the flag as isEnabled on its start contract, which is what security_solution's useMitreConfiguration() hook will read to decide between the new API and the legacy blob.
  • mitre-attack-entity Saved Object type: space agnostic, hidden, stored in the Security Solution Saved Objects index. dynamic: false with only the fields we query, sort, or aggregate on mapped.
  • MitreAttackDataService: owns startup population. On start() it bulk-creates every entity from loadMitreArtifact() using a deterministic Saved Object id of {framework}:{framework_version}:{id} with overwrite: true, so re-running on every restart is idempotent and multiple MITRE versions can coexist. Population is fire and forget, and a failure gets logged without blocking or crashing startup.
  • MitreAttackDataClient: read-only client with getById() and list() methods, returned from the server start contract as getMitreDataClient(). Reads go through ensureInitialized() first, so a read arriving after a failed startup population triggers one retry and then degrades to an empty collection rather than throwing an error.

Notes for reviewers

  • scripts/check_saved_objects can't see this type yet: Saved Object registration is gated on the feature flag (matching the typesTiedToFeatureFlags pattern in security_solution/server/saved_objects.ts), and the checker boots Kibana with default config. That means the mappings are not CI-validated until the flag default flips on. I ran the checker manually against a flag-on build and it passes.
  • Bounded error logging: If bulkCreate returns per-object errors, we log the total count plus the first 5 failing ids rather than every failure, since a fully broken run would otherwise put hundreds/thousands lines in the log. I couldn't find an existing convention for this in the codebase, but figured this might be a better solution than just logging all of what would likely be similar errors for each entity.
  • search() is deliberately not here: The RFC lists it as technically not needed for milestone 1 MVP, and in creating this PR it seemed to be a bit too much work for something that won't be used until later. I decided to stick with the MVP methods on the data client, and it can come later with future milestones that actually consume it.

How to test

There's still nothing to click, the routes and UI come in the next two PRs, so testing is mostly confirming the Saved Objects land correctly. You can do this via terminal with some of the prompts below or kibana dev tools.

Testing utils

Flag off (the default) should produce nothing at all:

curl -s -u elastic:changeme "localhost:9200/.kibana_security_solution/_count?q=type:mitre-attack-entity"

Then add this to kibana.dev.yml and restart:

xpack.mitreAttack.managedSourceEnabled: true

You should start to see the relevant logging from the mitreAttack plugin population services and you should get 873 entities for the bundled v19.1 artifact, with deterministic ids:

curl -s -u elastic:changeme "localhost:9200/.kibana_security_solution/_search?q=type:mitre-attack-entity&size=2" | jq -r '.hits.hits[]._id'
# mitre-attack-entity:enterprise:19.1:TA0043
# mitre-attack-entity:enterprise:19.1:TA0042

Restarting again should leave the count at 873 rather than duplicating anything, which is the point of the deterministic ids. It's also worth confirming the indexed fields work, since these are what the coverage overview and technique picker will rely on:

# tactics come back in MITRE matrix order
curl -s -u elastic:changeme "localhost:9200/.kibana_security_solution/_search" -H 'Content-Type: application/json' -d '{"query":{"bool":{"filter":[{"term":{"type":"mitre-attack-entity"}},{"term":{"mitre-attack-entity.type":"tactic"}}]}},"sort":[{"mitre-attack-entity.position":{"order":"asc"}}],"size":3,"_source":["mitre-attack-entity.id"]}' | jq -c '.hits.hits[]._source'

# 8 subtechniques under T1003
curl -s -u elastic:changeme "localhost:9200/.kibana_security_solution/_count" -H 'Content-Type: application/json' -d '{"query":{"bool":{"filter":[{"term":{"type":"mitre-attack-entity"}},{"term":{"mitre-attack-entity.technique_id":"T1003"}}]}}}'

Next steps

Picking up from the plan in #287226, the remaining PRs for this milestone:

  1. Internal API routes (#19073): /internal/mitre/* to serve entities to the browser, built on the data client added here.
  2. UI cutover (#19074): the rule create/edit technique picker and coverage overview move off the hardcoded blob via a useMitreConfiguration() hook.
  3. Turn the flag on (#19076): enable by default, then delete the legacy blob and its generation script.

Checklist

Check the PR satisfies following conditions.

Reviewers should verify this PR satisfies this list as well.

  • Documentation was added for features that require explanation or tutorials
  • Unit or functional tests were updated or added to match the most common scenarios
  • If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the docker list

@dplumlee dplumlee self-assigned this Sep 2, 2026
@dplumlee dplumlee added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting Feature:Rule MITRE ATT&CK® Security Solution Detection Rules MITRE ATT&CK® features Team:Detection Engineering Security Detection Engineering Team v9.6.0 labels Sep 2, 2026
@dplumlee
dplumlee force-pushed the managed-mitre-m1-plugin branch from 64fbd48 to bec3dc0 Compare September 2, 2026 03:25
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ Elastic Docs Style Checker (Vale)

No issues found on modified lines!


The Vale linter checks documentation changes against the Elastic Docs style guide. To use Vale locally or report issues, refer to Elastic style guide for Vale.

@dplumlee dplumlee changed the title adds plugin and client code for managed mitre feature [Security Solution] Adds mitre_attack plugin and MITRE ATT&CK data client Sep 2, 2026
@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
mitreAttack - 1 +1

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
mitreAttack - 670.0B +670.0B
Unknown metric groups

shared chunks total size

id before after diff
all 7.0MB 7.0MB +145.0B

total optimizer output size

id before after diff
all 62.5MB 62.5MB +815.0B

Test Failures

  • [job] [logs] Scout Lane #6 - stateful-classic / default / local-stateful-classic - Discover data grid sample size - saved search and Dashboard - new Discover sessions use the default sample size
  • [job] [logs] Scout Lane #23 - stateful-classic / default / local-stateful-classic - StepDetailsPage - displays step detail metrics

History

cc @dplumlee

@dplumlee
dplumlee marked this pull request as ready for review September 2, 2026 05:51
@dplumlee
dplumlee requested a review from a team as a code owner September 2, 2026 05:52
schema.literal('technique'),
schema.literal('subtechnique'),
]),
superseded_by_id: schema.maybe(schema.arrayOf(schema.string())),
]),
superseded_by_id: schema.maybe(schema.arrayOf(schema.string())),
position: schema.maybe(schema.number()),
tactic_ids: schema.maybe(schema.arrayOf(schema.string())),
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:skip This PR does not require backporting Feature:Rule MITRE ATT&CK® Security Solution Detection Rules MITRE ATT&CK® features release_note:skip Skip the PR/issue when compiling release notes Team:Detection Engineering Security Detection Engineering Team v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants