Skip to content

[ML] Alerting: Escape URL-like string from being displayed as links#226849

Merged
rbrtj merged 15 commits intoelastic:mainfrom
rbrtj:ml-escape-url-looking-strings-from-alert-context-messages
Oct 6, 2025
Merged

[ML] Alerting: Escape URL-like string from being displayed as links#226849
rbrtj merged 15 commits intoelastic:mainfrom
rbrtj:ml-escape-url-looking-strings-from-alert-context-messages

Conversation

@rbrtj
Copy link
Contributor

@rbrtj rbrtj commented Jul 7, 2025

Resolves #202507
The PR aims to escape URL-like strings from being displayed as links, mainly in email clients.
To achieve that, we append - hyphens after field_names.

Here are the results of testing for:

  • Top records field_name = service.name
  • Top influencers influencer_field_name = service.name
  • Context message variables: anomaly.entityName = service.name, partition_field_name = testing_it, correlated_by_field_value = correlated_by_field_value, by_field_name = testing.it, by_field_value = test_value. I replaced some field values just because they're expected when constructing full context message, and I'm using mocked data.

I think it covers all occurrences of possible URL-like field names in the alert message.

image

// Adjusted

  • removed = from top influencers
  • dropped ( ) from top records
  • Added a . dot after the Actual value
    results:
image
@rbrtj rbrtj self-assigned this Jul 7, 2025
@rbrtj rbrtj requested a review from a team as a code owner July 7, 2025 15:44
@rbrtj rbrtj marked this pull request as draft July 7, 2025 15:45
@rbrtj rbrtj added release_note:enhancement :ml backport:skip This PR does not require backporting Team:ML Team label for ML (also use :ml) t// v9.2.0 labels Jul 23, 2025
@rbrtj rbrtj requested a review from darnautov July 23, 2025 13:09
@rbrtj rbrtj marked this pull request as ready for review July 23, 2025 13:09
@elasticmachine
Copy link
Contributor

Pinging @elastic/ml-ui (:ml)

@pmuellr
Copy link
Contributor

pmuellr commented Aug 11, 2025

Some concerns:

  • some customers may notice the zero-width char, or it may cause problems in downstream processors
  • not sure the matching handles all the cases
Looking at the default message
['{{rule.name}}'] Elastic Stack Machine Learning Alert:
- Job IDs: '{{context.jobIds}}'
- Time: '{{context.timestampIso8601}}'
- Anomaly score: '{{context.score}}'

'{{{context.message}}}'

'{{#context.topInfluencers.length}}'
  Top influencers:
  '{{#context.topInfluencers}}'
    '{{{influencer_field_name}}}' = '{{{influencer_field_value}}}' ['{{score}}']
  '{{/context.topInfluencers}}'
'{{/context.topInfluencers.length}}'

'{{#context.topRecords.length}}'
  Top records:
  '{{#context.topRecords}}'
    '{{function}}'('{{{field_name}}}') '{{{by_field_value}}}''{{{over_field_value}}}''{{{partition_field_value}}}' ['{{score}}']. Typical: '{{typical}}', Actual: '{{actual}}'
  '{{/context.topRecords}}'
'{{/context.topRecords.length}}'

'{{! Replace kibanaBaseUrl if not configured in Kibana }}'
[Open in Anomaly Explorer]('{{{kibanaBaseUrl}}}{{{context.anomalyExplorerUrl}}'})

Don't we really just have a problem with influencer_field_name and field_name? Maybe with the values? But I think the field names are most likely to get invalidly linkified given they are likely to contain . chars.

Did you consider just wrapping these in backtics? `. Markdown and Slack both treat backtics as fixed with font, and I believe exclude them from linkification. So, for example

   `'{{{influencer_field_name}}}'` = '{{{influencer_field_value}}}' ['{{score}}']

I'm curious why there are single quotes around the values. They seem like they'd make the message kinda noisy.

@rbrtj
Copy link
Contributor Author

rbrtj commented Aug 13, 2025

Some concerns:

  • some customers may notice the zero-width char, or it may cause problems in downstream processors
  • not sure the matching handles all the cases

Looking at the default message
Don't we really just have a problem with influencer_field_name and field_name? Maybe with the values? But I think the field names are most likely to get invalidly linkified given they are likely to contain . chars.

Did you consider just wrapping these in backtics? `. Markdown and Slack both treat backtics as fixed with font, and I believe exclude them from linkification. So, for example

   `'{{{influencer_field_name}}}'` = '{{{influencer_field_value}}}' ['{{score}}']

I'm curious why there are single quotes around the values. They seem like they'd make the message kinda noisy.

Hi!
I agree that the initial solution isn't good. There's an additional concern, the fix combines both updating the alert template and adding escaping. This means users who already have rules defined could end up in an intermediate state, having escaping logic but an outdated template, which isn't good.

However, I've tested using backticks, and I don't think they produce good results (gmail client).

For testing, I used influencer_field_name = service.name and influencer_field_value = https://google.com

Example 1:

`{{influencer_field_name}}` = `{{influencer_field_value}}` ['{{score}}']
image

Example 2:

`{{{influencer_field_name}}}` = `{{{influencer_field_value}}}` ['{{score}}']
image
@pmuellr
Copy link
Contributor

pmuellr commented Aug 25, 2025

I think it may be gmail auto-link-ifying, which is something I hadn't thought of.

I sent myself an email with the gmail web ui, with the following text: service.name should not be link-ified, and sure enough, it auto-linkified service.name. So there may not be an easy way around this :-(

@darnautov
Copy link
Contributor

@rbrtj could you please check how these email alerts appear in other email clients, like Apple Mail? If it turns out to be a Gmail specific issue, I agree with @pmuellr, there’s probably not much we can do about it.

@rbrtj
Copy link
Contributor Author

rbrtj commented Aug 26, 2025

@pmuellr @darnautov
I have performed some testing with the Apple client, and the results are somewhat different. Given how various mail clients handle linkifying differently, I'm not sure if we should try to fix it, since a solution for one might break something in another.

All testing was done without any escaping logic, only by manipulating the context message markdown.
The message I used: service.name https://google.com google.com www.google.com

The default: {{context.message}}
default context message

Triple brackets: {{{context.message}}}
triple brackets message

Double brackets with ticks `{{context.message}}`
double brackets with ticks

Triple brackets with ticks `{{{context.message}}}`
triple brackets and backticks

It seems like the last one triple brackets with ticks worked well for the apple client.

@pmuellr
Copy link
Contributor

pmuellr commented Aug 27, 2025

I guess the idea is going to be how to defeat auto-link-ification for field names, like service.name. IIRC, in looking at the context variables used, most were numbers, but the field name is likely going to be problematic due to the .'s in them.

And it seems like "surrounding" these doesn't always work - like with quotes or backtics, etc. I wonder if just suffixing or prefixing the values, at least in the template, might defeat it? So something like field:{{influencer_field_name}} ... (where field: is the "defeating" prefix), or {{influencer_field_name}(field). We don't want to change the actual context value, but it's usage in the mustache template. It would likely have to be something where a string of text without spaces would not be a legal URL, so something like {{influencer_field_name}}?field (not that we would use that!!) wouldn't work, since it would still be a valid URL.

If we find something that works, we could use that in the default context message, and suggest that technique for other folks in the main doc for context variables (and this rule type as well).

@rbrtj
Copy link
Contributor Author

rbrtj commented Sep 3, 2025

@pmuellr
Not sure if I understood correctly, but tested for:

field:{{influencer_field_name}} field:{{influencer_field_value}}
image
{{influencer_field_name}}(field) {{influencer_field_value}}(field)
image
@pmuellr
Copy link
Contributor

pmuellr commented Sep 3, 2025

I did some testing, and it appears appending "- " (without the quotes - a hyphen and space) immediately after the variable containing the value service.name will defeat auto-linkification both within our markdown implementation (markdown-it) and gmail.

Obviously not great, but it is good news that there's a common "autolinkify defeater". The text copy will of course be slightly odd looking, but likely better than linkified. If someone clicks the text in the email to select it, they might well pick up the hyphen, but hoping it would be obviously problematic wherever it might be used.

I suspect there are more characters that can be used here. I was hoping : would work since that's kind of a natural for the contextual usage. But it didn't - auto-linkified 😭

I haven't tried with existing full URL values like you show with https://google.com - I suspect the auto-linkifiers may be more forgiving for somethng that REALLY looks like a URL (the prefix) and go ahead and linkify. But maybe that doesn't matter as much as the field names like service.name.

@rbrtj
Copy link
Contributor Author

rbrtj commented Sep 15, 2025

Hey, sorry for the late response, but I've tested proposed "- " (just added it to the template, so like {{influencer_field_name}}- ), and it actually seems to break linkifying of all the links, see:

influencer_field_value = 'https://www.google.com';
influencer_field_name = 'service.name';
image
influencer_field_value = 'www.google.com';
influencer_field_name = 'service.name';
image

WDYT? @darnautov @pmuellr

@darnautov
Copy link
Contributor

Hey, sorry for the late response, but I've tested proposed "- " (just added it to the template, so like {{influencer_field_name}}- ), and it actually seems to break linkifying of all the links, see:

influencer_field_value = 'https://www.google.com';
influencer_field_name = 'service.name';
image ``` influencer_field_value = 'www.google.com'; influencer_field_name = 'service.name'; ``` image WDYT? @darnautov @pmuellr

I reckon it's fine for influencer_field_name, but not sure if we need to prefix influencer_field_value at all.

@pmuellr
Copy link
Contributor

pmuellr commented Sep 17, 2025

Ya, I think we want this just for the field NAME and not VALUE. With a - suffix on the field name, hopefully folks will realize the trailing - isn't part of the fiend name, but might be confused if they saw it on a value ("where did the - come from, it's not in my data!).

@rbrtj
Copy link
Contributor Author

rbrtj commented Sep 23, 2025

I'm not sure if we're on the same page, but adding a hyphen also breaks valid links.
For the specific case of influencer_field_name = service.name, it would work fine, but I'm assuming both the field name and field value can be valid links. Are we okay with this change?

// Edit: The original case with service.name wouldn't be solved by this solution, since it comes from {{context.message}} and we'd only append the hyphen in the default message skeleton so that users can delete it themselves, right?

image
@pmuellr
Copy link
Contributor

pmuellr commented Sep 23, 2025

I'm assuming both the field name and field value can be valid links. Are we okay with this change?

Seems to me like a field name that was a valid URL the user would want to link to, is a bit of a stretch. Like, they had a field named elastic.co, and you'd want that field name to be a link to that? Is there an existing use case for field names that happen to be useful links for the user?

// Edit: The original case with service.name wouldn't be solved by this solution, since it comes from {{context.message}} and we'd only append the hyphen in the default message skeleton so that users can delete it themselves, right?

You could do this in both the constructed context.message variable AND in the default message skeleton supplied in the UX. "This" being whatever we figure out is the "best way to defeat auto-linkification".

@rbrtj
Copy link
Contributor Author

rbrtj commented Sep 30, 2025

I'm assuming both the field name and field value can be valid links. Are we okay with this change?

Seems to me like a field name that was a valid URL the user would want to link to, is a bit of a stretch. Like, they had a field named elastic.co, and you'd want that field name to be a link to that? Is there an existing use case for field names that happen to be useful links for the user?

// Edit: The original case with service.name wouldn't be solved by this solution, since it comes from {{context.message}} and we'd only append the hyphen in the default message skeleton so that users can delete it themselves, right?

You could do this in both the constructed context.message variable AND in the default message skeleton supplied in the UX. "This" being whatever we figure out is the "best way to defeat auto-linkification".

Seems to me like a field name that was a valid URL the user would want to link to, is a bit of a stretch, I agree. I've updated the PR with the fix which includes appending hyphens after field_names.

@rbrtj rbrtj requested a review from darnautov October 1, 2025 13:35
Top influencers:
'{{#context.topInfluencers}}'
'{{influencer_field_name}}' = '{{influencer_field_value}}' ['{{score}}']
'{{influencer_field_name}}-' = '{{influencer_field_value}}' ['{{score}}']
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we lose the = here? I think e.g.

IMO

customer_full_name.keyword- Sultan Al Bryan [4]

looks better than

customer_full_name.keyword- = Sultan Al Bryan [4]

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 don't have a strong opinion on this, I agree it looks a bit less odd.
WDYT @darnautov?

Copy link
Contributor

@darnautov darnautov Oct 3, 2025

Choose a reason for hiding this comment

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

agree, makes sense to use = (if it still breaks auto linkifying)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adjusted the message in e7ed992

image
Top records:
'{{#context.topRecords}}'
'{{function}}'('{{field_name}}') '{{by_field_value}}''{{over_field_value}}''{{partition_field_value}}' ['{{score}}']. Typical: '{{typical}}', Actual: '{{actual}}'
'{{function}}'('{{field_name}}-') '{{by_field_value}}''{{over_field_value}}''{{partition_field_value}}' ['{{score}}']. Typical: '{{typical}}', Actual: '{{actual}}'
Copy link
Contributor

Choose a reason for hiding this comment

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

Would dropping the brackets ( and ) improve the formatting here?

high_sum taxful_total_price- 'Wilhemina St. Ryan' [0]. Typical: $63.86, Actual: $200

maybe better than:

high_sum(taxful_total_price-) 'Wilhemina St. Ryan' [0]. Typical: $63.86, Actual: $200

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in e7ed992

Copy link
Contributor

@peteharverson peteharverson left a comment

Choose a reason for hiding this comment

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

Tested latest changes with a Slack Connector and an Email connector and LGTM.

While using the - is not ideal, I think the latest formatting edits are as good as we're going to get. Example email:

Screenshot 2025-10-06 at 11 29 59
@elasticmachine
Copy link
Contributor

💛 Build succeeded, but was flaky

Failed CI Steps

Test Failures

  • [job] [logs] FTR Configs #51 / Reporting Generate CSV from SearchSource validation Searches large amount of data, stops at Max Size Reached

Metrics [docs]

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
ml 5.4MB 5.4MB +136.0B

History

cc @rbrtj

@rbrtj rbrtj added backport:version Backport to applied version labels v9.3.0 and removed backport:skip This PR does not require backporting labels Oct 6, 2025
@rbrtj rbrtj merged commit 2f9a43b into elastic:main Oct 6, 2025
17 checks passed
@kibanamachine
Copy link
Contributor

Starting backport for target branches: 9.2

https://github.com/elastic/kibana/actions/runs/18281686119

kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Oct 6, 2025
…lastic#226849)

Resolves elastic#202507
The PR aims to escape URL-like strings from being displayed as links,
mainly in email clients.
To achieve that, we append `-` hyphens after field_names.

Here are the results of testing for:
- Top records `field_name` = `service.name`
- Top influencers `influencer_field_name` = `service.name`
- Context message variables: `anomaly.entityName = service.name`,
`partition_field_name = testing_it`, `correlated_by_field_value =
correlated_by_field_value`, `by_field_name = testing.it`,
`by_field_value = test_value`. I replaced some field values just because
they're expected when constructing full context message, and I'm using
mocked data.

I think it covers all occurrences of possible URL-like field names in
the alert message.

<img width="1048" height="262" alt="image"
src="https://github.com/user-attachments/assets/b16fff05-a0e9-4669-a8bd-8919ede95410"
/>

// Adjusted
- removed `=` from top influencers
- dropped `( )`  from top records
- Added a `.` dot after the `Actual` value
 results:
<img width="859" height="238" alt="image"
src="https://github.com/user-attachments/assets/a789ecec-3895-4307-b051-81d791d9bb98"
/>

(cherry picked from commit 2f9a43b)
@kibanamachine
Copy link
Contributor

💚 All backports created successfully

Status Branch Result
9.2

Note: Successful backport PRs will be merged automatically after passing CI.

Questions ?

Please refer to the Backport tool documentation

kibanamachine added a commit that referenced this pull request Oct 6, 2025
…inks (#226849) (#237641)

# Backport

This will backport the following commits from `main` to `9.2`:
- [[ML] Alerting: Escape URL-like string from being displayed as links
(#226849)](#226849)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Robert
Jaszczurek","email":"92210485+rbrtj@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-10-06T13:00:19Z","message":"[ML]
Alerting: Escape URL-like string from being displayed as links
(#226849)\n\nResolves
https://github.com/elastic/kibana/issues/202507\nThe PR aims to escape
URL-like strings from being displayed as links,\nmainly in email
clients.\nTo achieve that, we append `-` hyphens after
field_names.\n\nHere are the results of testing for:\n- Top records
`field_name` = `service.name`\n- Top influencers `influencer_field_name`
= `service.name`\n- Context message variables: `anomaly.entityName =
service.name`,\n`partition_field_name = testing_it`,
`correlated_by_field_value =\ncorrelated_by_field_value`, `by_field_name
= testing.it`,\n`by_field_value = test_value`. I replaced some field
values just because\nthey're expected when constructing full context
message, and I'm using\nmocked data.\n\nI think it covers all
occurrences of possible URL-like field names in\nthe alert
message.\n\n<img width=\"1048\" height=\"262\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/b16fff05-a0e9-4669-a8bd-8919ede95410\"\n/>\n\n//
Adjusted\n- removed `=` from top influencers\n- dropped `( )` from top
records\n- Added a `.` dot after the `Actual` value\n results:\n<img
width=\"859\" height=\"238\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/a789ecec-3895-4307-b051-81d791d9bb98\"\n/>","sha":"2f9a43bddfe8764e6af87aa3861593ea47bc8ab2","branchLabelMapping":{"^v9.3.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix",":ml","Team:ML","backport:version","v9.2.0","v9.3.0"],"title":"[ML]
Alerting: Escape URL-like string from being displayed as
links","number":226849,"url":"https://github.com/elastic/kibana/pull/226849","mergeCommit":{"message":"[ML]
Alerting: Escape URL-like string from being displayed as links
(#226849)\n\nResolves
https://github.com/elastic/kibana/issues/202507\nThe PR aims to escape
URL-like strings from being displayed as links,\nmainly in email
clients.\nTo achieve that, we append `-` hyphens after
field_names.\n\nHere are the results of testing for:\n- Top records
`field_name` = `service.name`\n- Top influencers `influencer_field_name`
= `service.name`\n- Context message variables: `anomaly.entityName =
service.name`,\n`partition_field_name = testing_it`,
`correlated_by_field_value =\ncorrelated_by_field_value`, `by_field_name
= testing.it`,\n`by_field_value = test_value`. I replaced some field
values just because\nthey're expected when constructing full context
message, and I'm using\nmocked data.\n\nI think it covers all
occurrences of possible URL-like field names in\nthe alert
message.\n\n<img width=\"1048\" height=\"262\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/b16fff05-a0e9-4669-a8bd-8919ede95410\"\n/>\n\n//
Adjusted\n- removed `=` from top influencers\n- dropped `( )` from top
records\n- Added a `.` dot after the `Actual` value\n results:\n<img
width=\"859\" height=\"238\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/a789ecec-3895-4307-b051-81d791d9bb98\"\n/>","sha":"2f9a43bddfe8764e6af87aa3861593ea47bc8ab2"}},"sourceBranch":"main","suggestedTargetBranches":["9.2"],"targetPullRequestStates":[{"branch":"9.2","label":"v9.2.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.3.0","branchLabelMappingKey":"^v9.3.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/226849","number":226849,"mergeCommit":{"message":"[ML]
Alerting: Escape URL-like string from being displayed as links
(#226849)\n\nResolves
https://github.com/elastic/kibana/issues/202507\nThe PR aims to escape
URL-like strings from being displayed as links,\nmainly in email
clients.\nTo achieve that, we append `-` hyphens after
field_names.\n\nHere are the results of testing for:\n- Top records
`field_name` = `service.name`\n- Top influencers `influencer_field_name`
= `service.name`\n- Context message variables: `anomaly.entityName =
service.name`,\n`partition_field_name = testing_it`,
`correlated_by_field_value =\ncorrelated_by_field_value`, `by_field_name
= testing.it`,\n`by_field_value = test_value`. I replaced some field
values just because\nthey're expected when constructing full context
message, and I'm using\nmocked data.\n\nI think it covers all
occurrences of possible URL-like field names in\nthe alert
message.\n\n<img width=\"1048\" height=\"262\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/b16fff05-a0e9-4669-a8bd-8919ede95410\"\n/>\n\n//
Adjusted\n- removed `=` from top influencers\n- dropped `( )` from top
records\n- Added a `.` dot after the `Actual` value\n results:\n<img
width=\"859\" height=\"238\"
alt=\"image\"\nsrc=\"https://github.com/user-attachments/assets/a789ecec-3895-4307-b051-81d791d9bb98\"\n/>","sha":"2f9a43bddfe8764e6af87aa3861593ea47bc8ab2"}}]}]
BACKPORT-->

Co-authored-by: Robert Jaszczurek <92210485+rbrtj@users.noreply.github.com>
rylnd pushed a commit to rylnd/kibana that referenced this pull request Oct 17, 2025
…lastic#226849)

Resolves elastic#202507
The PR aims to escape URL-like strings from being displayed as links,
mainly in email clients.
To achieve that, we append `-` hyphens after field_names.

Here are the results of testing for:
- Top records `field_name` = `service.name`
- Top influencers `influencer_field_name` = `service.name`
- Context message variables: `anomaly.entityName = service.name`,
`partition_field_name = testing_it`, `correlated_by_field_value =
correlated_by_field_value`, `by_field_name = testing.it`,
`by_field_value = test_value`. I replaced some field values just because
they're expected when constructing full context message, and I'm using
mocked data.

I think it covers all occurrences of possible URL-like field names in
the alert message.

<img width="1048" height="262" alt="image"
src="https://github.com/user-attachments/assets/b16fff05-a0e9-4669-a8bd-8919ede95410"
/>

// Adjusted
- removed `=` from top influencers
- dropped `( )`  from top records
- Added a `.` dot after the `Actual` value
 results:
<img width="859" height="238" alt="image"
src="https://github.com/user-attachments/assets/a789ecec-3895-4307-b051-81d791d9bb98"
/>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport:version Backport to applied version labels :ml release_note:fix Team:ML Team label for ML (also use :ml) t// v9.2.0 v9.3.0

6 participants