Skip to content

[Threat Intel 4/11] SSRF-guarded HTTP client for feed fetching - #287200

Merged
stephmilovic merged 9 commits into
elastic:mainfrom
stephmilovic:threat-intel-4a-ssrf-client
Sep 2, 2026
Merged

[Threat Intel 4/11] SSRF-guarded HTTP client for feed fetching#287200
stephmilovic merged 9 commits into
elastic:mainfrom
stephmilovic:threat-intel-4a-ssrf-client

Conversation

@stephmilovic

@stephmilovic stephmilovic commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the SSRF-guarded HTTP client used by Threat Intelligence feed adapters. Network fetches go through pinned DNS, blocked private ranges, and bounded redirects. Inline data: URLs are supported only for local generator fixtures, with encoded and decoded size limits.

Where this sits

# PR Depends on Status
1 #287197 workflow gate correction + fixtures nothing ✅ merged
2 #287198 contracts, constants, shared libs nothing ✅ merged
3 #287199 content parsing #287198 closed
4 #287200 SSRF-guarded HTTP client ← this PR #287198 ✅ merged
5 #287201 index templates, seeding, inference features #287198, #287200 ✅ merged
6 #287202 indicator alias #287198, #287201 ✅ merged
7 #287203 IOC extraction #287198, #287200 ✅ merged
8 #287204 LLM services, routes, source catalog API #287198, #287200, #287202, #287203 ✅ merged
9 #287205 RSS adapter #287198, #287200, #287203, #287204 ✅ merged
10 #287206 remaining adapters, dispatcher, fetch_source step #287198, #287200, #287203, #287205 d✅ mergedaft
11 #287207 promote/scrub tasks and plugin wiring #287198#287206 ✅ merged
12 #287479 generator fixture article URLs nothing ✅ merged

#287479 came first in the merge train and is already on main. The numbered series (#287197#287207) stacks on top in dependency order. #287199 (content parsing) was closed; IOC extraction ships its own section-header classifier.

Scope review follow-up

  • Removed stale feed comments and an unexplained Mustard reference in tests.
  • Rejects non-data: URLs in the inline fixture path.
  • Bounds decoded inline RSS fixtures to 10 MiB before decoding.

Product boundary

threatIntelSupplyEnabled stays off until direct-index cross-space isolation is hardened. This PR only adds the client.

To test

node scripts/jest --config x-pack/solutions/security/plugins/security_solution/server/threat_intel/jest.config.js adapters/http_client.test.ts adapters/rss/decode_data_url.test.ts

PR developed with Cursor + Auto

@stephmilovic stephmilovic added release_note:skip Skip the PR/issue when compiling release notes backport:skip This PR does not require backporting Team:Threat Hunting Security Solution Threat Hunting Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. v9.6.0 labels Aug 25, 2026
@kibanamachine kibanamachine added the reviewer:libra PR review with Libra. This disables Claude and Scout reviewers label Aug 25, 2026
@kibanamachine

Copy link
Copy Markdown
Contributor

Selected for Libra review

This PR was selected for Libra review as part of the temporary 10% trial because PR #287200 is divisible by 10.

To opt out permanently, remove the reviewer:libra label. It will not be added again to this PR.

@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

Pinging @elastic/security-threat-hunting (Team:Threat Hunting)

@infra-vault-gh-plugin-prod

Copy link
Copy Markdown

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

@kibanamachine kibanamachine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Libra found 3 issues.

Generated by Libra

@stephmilovic

Copy link
Copy Markdown
Contributor Author

All three addressed, details in the threads.

The DNS one was the most valuable: the bound looked like it was there and was not, because the signal was created after validation. Since the hostname is operator-supplied that was a real way to pin a task worker indefinitely.

Also confirming the observation that body: null in the old redirect fixtures is why none of the stream handling was exercised. There is a body-bearing response helper now that records whether the stream was released, and it covers all three throwable redirect paths.

@kibanamachine kibanamachine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Libra found 3 issues.

Generated by Libra

@stephmilovic

Copy link
Copy Markdown
Contributor Author

All three addressed, details in the threads.

The Azure one is the finding I would most want a second opinion on, because it is the only address here that is not in an RFC special-use range: it looks like ordinary public space to every rule in the classifier, and only Azure knows it is special. If there are other cloud platform endpoints with that shape, they would have the same problem, and I would rather add them now than discover them one review at a time.

I also probed the guard against the obfuscated host forms while I was in here, to check the fix composed rather than assuming: integer, hex, and octal IPv4, IPv4-mapped and IPv4-compatible IPv6, 6to4, NAT64, and a trailing-dot FQDN all normalize into something the classifier rejects, and a genuinely public IPv4-mapped address stays routable. The Azure /32 is pinned through both the literal and mapped paths for the same reason.

@kibanamachine kibanamachine left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Libra found 3 issues.

Generated by Libra

@jonwalstedt
jonwalstedt force-pushed the threat-intel-4a-ssrf-client branch from 1ffbdfc to 58324e6 Compare August 28, 2026 19:59
@jonwalstedt

Copy link
Copy Markdown
Contributor

@stephmilovic — the TI MVP scope-narrowing handoff is complete. Summary for the whole stack (posting here on the lead PR so it's in one place):

Rewrites force-pushed to all 8 fork branches (base stays main on every PR; nothing chained):

  • #287200 4a-ssrf — rebase only; already aligned (feed-URL wording, no entry-link/source_url fetch)
  • #287201 4b-setup — enabled flag; 8 enabled + AWS/FortiGuard packs disabled; create-only seed preserves operator choice; body_html removed from template + shared schema
  • #287202 4c-alias — rebase only (no HTML dep)
  • #287203 5-ioc — classifyHeader transplanted to services/section_headers.ts; tests rewritten to structured text; HTML path dropped
  • #287204 6-llm-and-catalog — severity/report-content helpers → services/; sources API narrowed to list + enable/disable (create/delete routes removed); body_html dropped from routes/schemas
  • #287205 7-adapters-core — adapter-local htmlFragmentToText (bounded plain text, the one narrow markup boundary); RSS stores text-only + entry-link is provenance only; STIX/TAXII repointed to services/
  • #287206 8-adapters-rest — canonicalizeUrl transplanted adapter-local; KEV/text-list/vendor repointed; config.vendor override removed
  • #287207 9-tasks-and-wiring — scrub drops body_html; enrich workflow sends text-only; ingest enabled:true filter + plain-text enrich tests added

Commit preservation: I force-pushed with --force-with-lease after re-verifying every fork tip was unchanged since my git range-diff check — none of your commits were overwritten; each rewrite is your commits rebased onto main plus the scope commits.

#287199 is closed (no surviving downstream dependency; the in-scope helpers were transplanted onto their owning PRs as above).

Validation: integration branch merges all 8 clean; threat_intel 966 pass, kbn-workflows 350 pass; check.js --scope=branch green (typecheck 0 errors, ESLint 0/0 on 102 files, oxlint clean); all surviving-ref greps (body_html, extractArticleHtml, htmlToStructured, @mozilla/readability, css-tree, source create/delete, create_threat_report URL mode, RSS entry-link fetch) clean; exactly one RSS fragment-to-text helper, adapter-only import; mutation checks confirmed the enabled-filter and RSS entry-link guard tests are load-bearing.

Docs: Project AlertZero docs updated in a draft PR — elastic/project-alertzero#163 (new decision D25, resolved approved-source questions, buildout/mvp-slice/mvp-spec/risk-register/changelog, regenerated docs-data.js + megadoc; the .docx needs a pandoc pass).

I left all PR descriptions untouched per your note — those are yours to update.

@stephmilovic

Copy link
Copy Markdown
Contributor Author

@elasticmachine merge upstream

stephmilovic and others added 8 commits September 1, 2026 16:10
Fourth slice of the threat intel supply pipeline, and the security-critical one.
Every outbound feed fetch goes through this. Nothing calls it yet and
`threatIntelSupplyEnabled` defaults off, so it is inert.

Feed URLs are operator-supplied, so a source is a request for Kibana to fetch a URL
of someone else's choosing. The guard:

  - Rejects any scheme other than http and https.
  - Classifies literal hosts against the shared special-use ranges, which covers the
    obfuscated IPv4 forms (integer, hex, octal) and the IPv4-mapped and
    IPv4-compatible IPv6 forms that smuggle a restricted address through an IPv6
    literal. The WHATWG URL parser normalizes most of these before we see them, and
    the tests assert that.
  - Rejects hostnames that always resolve somewhere local: single-label names, and
    the `.localhost`, `.local`, `.internal`, and `.localdomain` suffixes. A trailing
    dot is stripped first, since `metadata.google.internal.` is a valid absolute FQDN
    that resolves identically and does not end with `.internal`.
  - Resolves the hostname and re-checks every answer, so a public name pointing at a
    private address is rejected before any connection.
  - Pins the connection to the address it validated. Validating and then letting Node
    resolve the name again at connect time leaves a DNS-rebinding window where the
    second answer differs from the checked one, so the request goes through a
    dispatcher whose lookup returns the pinned address. TLS SNI and the Host header
    keep the original hostname, so certificate verification is unaffected.
  - Re-runs all of that on every redirect hop, with its own pin per hop, and caps the
    chain. An open redirect on a legitimate feed host is the usual way in.
  - Strips `Authorization`, `x-api-key`, and `Cookie` on cross-origin hops.
  - Caps the response body incrementally on the decoded stream, because
    `Content-Length` is absent on chunked and gzipped responses.

Credentials embedded in a feed URL are moved into an `Authorization: Basic` header
and the request goes to the credential-free URL. Node's fetch rejects a URL
containing credentials outright, before it issues anything, so without this an
authenticated feed could be saved, validated, and scheduled, and then fail on every
run. The derived header goes through the same cross-origin stripping.

`assertSafeUrl` is deliberately not exported. On its own it cannot see where a DNS
name points, so a caller who reached for it directly would miss the
public-name-private-record case. `assertSafeUrlResolved` is the complete check.

The `fetchFn` and `lookupFn` test seams are reachable only through
`createFetchUrl`, so production callers cannot bypass the guard by accident.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…andling

Three review findings on the SSRF client.

The DNS pre-flight sat outside every bound this function has. `dns.lookup` takes
no AbortSignal, and the combined timeout/cancellation signal was created after
validation, so a resolver that never answers kept a feed run pending indefinitely
past both the 30s timeout and a step abort. Hostnames are operator-supplied, so
that is a task-worker exhaustion vector rather than a hypothetical. The signal is
now established first and every pre-flight, initial and per-redirect, races it.

Every 3xx was treated as a redirect, but fetch only follows 301, 302, 303, 307,
and 308. A 304 answering a caller's conditional headers therefore threw "missing
Location" instead of being returned to the adapter.

The redirect body was released only after URL parsing, the hop-limit check, and
the destination SSRF check, all of which can throw. `finally` then calls
`Agent.close()`, which waits on in-flight requests, and an unread stream keeps
the request in flight, so a server streaming an endless redirect body could hang
the run instead of failing it. The body is now released immediately after the
status is classified, before anything throwable. This is a real gap in the earlier
close-ordering fix, which only moved the cancel ahead of `close()` on the success
path.

Tests cover all three: a resolver that never answers, a caller abort mid-lookup, a
pre-aborted signal, a 304 round-trip, a followed 308, and body release on each of
the three throwable redirect paths. The existing redirect tests used `body: null`,
which is why none of this was exercised.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…als, test the pin

Three more review findings on the SSRF client.

168.63.129.16 was treated as ordinary public space. It is not in any RFC
special-use range, so every rule in the classifier passed it, but on Azure it is the
VM-scoped platform endpoint (WireServer / host plugin). Since feed URLs are
operator-supplied and `FetchUrlOptions.headers` lets a caller add the `x-ms-*`
headers it wants, that was a way through the SSRF boundary on any Azure deployment.
The other cloud metadata endpoints sit in link-local 169.254.0.0/16 and were already
covered. Added as a /32, with neighbours asserted routable so it cannot silently
widen.

Proxy-Authorization was missing from the cross-origin strip set. It is a standard
credential header and the public `headers` option can carry it, so a feed host
redirecting cross-origin could collect proxy credentials.

The DNS-pinning test only asserted that a dispatcher object existed. It would have
passed if the Agent re-resolved the hostname or pinned the wrong address, which is
the entire guarantee, so the central anti-rebinding property was effectively
untested. The connect lookup is now a named function exported as
`pinnedLookupForTest`, so the pin is asserted directly instead of by reflecting into
undici's private option storage: both call shapes, an IPv6 address, indifference to
the hostname asked for, and the pre-flight-to-pin handoff under a resolver that
returns a private address on its second answer. Plus per-hop dispatcher separation.
Verified it bites: making the pin answer with a different address fails 4 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The /32 is matched in the IPv4 classifier, and IPv4-mapped IPv6 is normalized back
through it, so both spellings are blocked. Pinning it so a future change to either
path cannot open the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed, fix header case

Three review findings on the SSRF client, one of them a real bypass.

`http://[::ffff:0:169.254.169.254]/` reached the metadata endpoint. The URL parser
canonicalizes it to `::ffff:0:a9fe:a9fe`, and the classifier only matched
`::ffff:<hi>:<lo>`, so the extra zero group made it look routable. Because a literal
host is treated as already-validated, DNS validation is skipped, which makes a miss
here a direct route to the embedded target rather than a defence-in-depth gap.

Rather than add the one spelling, the embedded-IPv4 forms are now matched by shape:
all leading groups zero, any intervening groups only the `ffff` marker or zeros, and
the last two groups carrying the address. That covers mapped, translatable/SIIT, and
IPv4-compatible in one rule. Enumerating spellings has now missed one, and the same
generalization applies to the dotted matcher.

`redactUrl`'s fallback runs precisely because the URL did not parse, so it must not
assume valid userinfo syntax. `[^/@\s]*@` stopped at the first `/` and the first `@`,
so `//user:sec/ret@host` was returned untouched and `//us@er:s3cr3t@host` kept its
password, both reachable from `assertSafeUrl`'s error message. It over-redacts
through the last `@` now: losing part of a malformed URL beats logging a credential.

Caller precedence over the URL-derived credential only worked for the exact spelling
`Authorization`. Header names are case-insensitive, so a caller passing
`authorization` left both keys in the object, and since fetch normalizes names the
stale URL credential could be sent alongside the intended one. Detection is
case-insensitive now, with tests across four spellings asserting exactly one
authorization header survives and the URL credential is gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@stephmilovic
stephmilovic force-pushed the threat-intel-4a-ssrf-client branch from db9cc7d to 5c215dc Compare September 1, 2026 22:10
@stephmilovic stephmilovic removed the Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. label Sep 1, 2026
Estimate decoded size from the base64 payload length so fixture
data URLs cannot allocate multi-megabyte buffers before the feed cap
check runs.

Co-authored-by: Cursor <cursoragent@cursor.com>
@kibanamachine

Copy link
Copy Markdown
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Metrics [docs]

✅ unchanged

Test Failures

  • [job] [logs] FTR Configs #63 / dashboard Reporting Dashboard Reporting Screenshots Preserve Layout provides a button to copy POST URL
  • [job] [logs] FTR Configs #90 / machine learning - anomaly detection rule editor flyout shows scope controls when filter lists exist and allows save

History

@stephmilovic
stephmilovic merged commit 5723c23 into elastic:main Sep 2, 2026
41 of 42 checks passed
stephmilovic added a commit that referenced this pull request Sep 4, 2026
…og API (#287204)

## Summary

Adds LLM enrichment services and Threat Intelligence HTTP routes,
including the fixed-catalog source list and enable/disable API. Manual
report creation and provenance URLs go through the shared HTTP/HTTPS
normalizer.

Also wires the routes, inference features, and one-time bootstrap into
`plugin.ts`, gated by `threatIntelSupplyEnabled`. This wiring is
interim: it exists so the enrichment eval suite (see Evaluation below)
has routes to call ahead of the full pipeline wiring. `#287207` replaces
it with a proper `wiring.ts` module; that PR's description has a rebase
note covering the `plugin.ts` conflict resolution.

## Where this sits

| # | PR | Depends on | Status |
|---|---|---|---|
| 1 | #287197 workflow gate correction + fixtures | nothing | ✅ merged |
| 2 | #287198 contracts, constants, shared libs | nothing | ✅ merged |
| 3 | #287199 content parsing | #287198 | closed |
| 4 | #287200 SSRF-guarded HTTP client | #287198 | ✅ merged |
| 5 | #287201 index templates, seeding, inference features | #287198,
#287200 | ✅ merged |
| 6 | #287202 indicator alias | #287198, #287201 | ✅ merged |
| 7 | #287203 IOC extraction | #287198, #287200 | ✅ merged |
| 8 | #287204 LLM services, routes, source catalog API **← this PR** |
#287198, #287200, #287202, #287203 | 👀 ready |
| 9 | #287205 RSS adapter | #287198, #287200, #287203, #287204 | draft |
| 10 | #287206 remaining adapters, dispatcher, fetch_source step |
#287198, #287200, #287203, #287205 | draft |
| 11 | #287207 promote/scrub tasks and plugin wiring | #287198#287206 |
draft |
| 12 | #287479 generator fixture article URLs | nothing | ✅ merged |
| 13 | #289343 Scout API tests for the deterministic routes | #287204 |
draft |
| 14 | #289345 enrichment eval suite | #287204 | draft |


#287479 came first in the merge train and is already on `main`. The
numbered series (#287197#287207) stacks on top in dependency order.
#287199 (content parsing) was closed; IOC extraction ships its own
section-header classifier.

## Scope review follow-up

- Shared `normalizeProvenanceUrl` helper (HTTP/HTTPS only, strips
credentials, bounded length).
- Source create/delete routes removed. Updates may change only
`enabled`.
- Lists and mutations reject catalog IDs outside the approved set.
- Ensures the per-space indicator alias on `list_sources` (idempotent).

## Evaluation

A `@kbn/evals` suite exercises all four enrichment stages against the
BlackHat demo pack article text, posting each input directly to the
internal route and scoring the structured response. It runs as a tracked
baseline (scores recorded, no build-failing thresholds yet). Latest full
run, EIS Claude Sonnet 4.6 as model and judge, 1 repetition, 20
examples:

| Stage | Evaluator | Score |
|---|---|---|
| assess_relevance | IsIntelligenceMatch | 1.00 |
| assess_relevance | RelevanceShapeValid | 1.00 |
| classify_severity | SeverityExactMatch | 0.83 |
| classify_severity | SeverityWithinOneLevel | 1.00 |
| enrich_taxonomy | CategoryRecall | 0.80 |
| enrich_taxonomy | RegionRecall | 1.00 |
| extract_diamond | DiamondNoIocLeak | 1.00 |
| extract_diamond | DiamondSignalCount | 1.00 |
| extract_diamond | criteria (LLM judge, majority vote) | 1.00 |

The suite ships in #289345, a separate eval-only PR that stacks on this
one (it needs the routes and the flag-gated wiring added here). The four
deterministic routes are covered separately by Scout API tests in
#289343. Any prompt or calibration fix the evals surface lands by
amending this PR, not the eval PR.

## To test

```
node scripts/jest --config x-pack/solutions/security/plugins/security_solution/server/threat_intel/jest.config.js routes/list_sources.test.ts services/provenance_url.test.ts
```

_PR developed with Cursor + Auto_

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Jon Wålstedt <jon.walstedt@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
jonwalstedt added a commit that referenced this pull request Sep 11, 2026
…d the flag (#287207)

## Summary

Wires Threat Intelligence behind `threatIntelSupplyEnabled`: bootstrap,
managed workflow installation, promote/scrub tasks, and route
registration. Promote mirrors extracted IOCs into
`.threat-intel-indicators` for Indicator Match rules.

## Where this sits

| # | PR | Depends on | Status |
|---|---|---|---|
| 1 | #287197 workflow gate correction + fixtures | nothing | ✅ merged |
| 2 | #287198 contracts, constants, shared libs | nothing | ✅ merged |
| 3 | #287199 content parsing | #287198 | closed |
| 4 | #287200 SSRF-guarded HTTP client | #287198 | ✅ merged |
| 5 | #287201 index templates, seeding, inference features | #287198,
#287200 | ✅ merged |
| 6 | #287202 indicator alias | #287198, #287201 | ✅ merged |
| 7 | #287203 IOC extraction | #287198, #287200 | ✅ merged |
| 8 | #287204 LLM services, routes, source catalog API | #287198,
#287200, #287202, #287203 | ✅ merged |
| 9 | #287205 RSS adapter | #287198, #287200, #287203, #287204 | ✅
merged |
| 10 | #287206 remaining adapters, dispatcher, fetch_source step |
#287198, #287200, #287203, #287205 | ✅ merged |
| 11 | #287207 promote/scrub tasks and plugin wiring **← this PR** |
#287198#287206 | ready for review |
| 12 | #287479 generator fixture article URLs | nothing | ✅ merged |
| 13 | #289343 Scout API tests for the deterministic routes | #287204 |
ready for review |
| 14 | #289345 enrichment eval suite | #287204 | ready for review |
| 15 | #290144 read APIs, readiness, space-keyed attribution | #287207 |
draft |


#287479 came first in the merge train and is already on `main`. The
numbered series (#287197#287205) is merged; #287206 and #287207 now sit
directly on `main`. #287199 (content parsing) was closed; IOC extraction
ships its own section-header classifier.

## Scope review follow-up

- Installs the managed workflows with the right space topology:
`attribute_alerts_to_reports` installs per space (it queries
`.alerts-security.alerts-*` and writes per-space hit totals, so a global
install would clobber every space's totals onto one shared doc), while
`ingest_threat_feeds` and `enrich_threat_report` install once globally.
Alert analysis and threat intel share a single `ready()` call so neither
install set is dropped, and spaces created after boot are reconciled on
the promote task. All three ship `enabled: false`, and enrich routes its
HTTP calls through a fixed space (`default`) instead of the install-time
`workflow.spaceId` (which is `'*'` globally).
- Ensures the default-space indicator alias on start.
- Chunks promotion bulk writes and treats HTTP 408/500 as retryable bulk
failures.
- Sanitizes provenance and extracted IOC reference URLs during
promotion.
- Documents direct-index cross-space isolation as the blocker to
enabling the feature.

## Bootstrap fix folded in from #289343

`seed_default_sources.ts` sorted the legacy-source disable scan on
`_id`, which Elasticsearch rejects with `illegal_argument_exception:
Fielddata access on the _id field is disallowed` unless
`indices.id_field_data.enabled` is set, so it failed bootstrap on a
stock cluster. Found this while getting #289343's Scout suite green and
moved the fix here since this PR is the one wiring up bootstrap and
already needs `security-threat-hunting` review, so it isn't adding a
reviewer #289343 wouldn't otherwise need.

## Product boundary

`threatIntelSupplyEnabled` defaults to false. Do not enable until
direct-index cross-space isolation is hardened or the administrator
trust model is explicitly accepted.

## To test

```
node scripts/jest --config x-pack/solutions/security/plugins/security_solution/server/threat_intel/jest.config.js wiring.test.ts tasks/promote_threat_indicators.test.ts
node scripts/jest x-pack/solutions/security/plugins/security_solution/server/workflows/
node scripts/jest src/platform/packages/shared/kbn-workflows/managed/definitions/threat_intel/
```

_PR developed with Cursor + Auto + Sonnet 5 + Opus 4.8_

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Jon Wålstedt <jon.walstedt@elastic.co>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
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 release_note:skip Skip the PR/issue when compiling release notes reviewer:libra PR review with Libra. This disables Claude and Scout reviewers Team:Threat Hunting Security Solution Threat Hunting Team v9.6.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants