Skip to content

Conversation

@ebarlas
Copy link
Contributor

@ebarlas ebarlas commented Nov 7, 2025

Currently, there are no limits on the size of a user profile. Profiles store username, initials, avatars, etc.

Authorized Kibana observability clients can store an unlimited amount of data in user profile via update-profile.

This change puts a limit on profile size to avoid heap memory pressure and OOM crashes.

A new configuration setting, xpack.security.profile.max_size, was introduced with a default value of 10 MB to remain safely above the 1 MB request limit size enforced by Kibana.

Limit enforcement is implemented with a profile document read before the update, to provide a full view of the profile footprint. This approach is intended to be lightweight. Still, a document read is now incurred for every update request.

@ebarlas ebarlas requested a review from a team November 7, 2025 02:43
@ebarlas ebarlas self-assigned this Nov 7, 2025
@ebarlas ebarlas added >bug :Security/Security Security issues without another label Team:Security Meta label for security team labels Nov 7, 2025
@elasticsearchmachine
Copy link
Collaborator

Hi @ebarlas, I've created a changelog YAML for you.

Copy link
Member

@legrego legrego left a comment

Choose a reason for hiding this comment

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

@ebarlas, what will Kibana see in terms of HTTP status code & error payload when this limit is hit? I ask because I feel we should monitor this within our SLOs. I want to make sure we have a way to reliably detect this condition, and distinguish it from other potential failure scenarios.

@ebarlas
Copy link
Contributor Author

ebarlas commented Nov 12, 2025

What will Kibana see in terms of HTTP status code & error payload when this limit is hit?

Elasticsearch request processing will fail with an ElasticsearchException, which results in a 400 Bad Request response with a detailed message.

Here's a curl snippet for reference:

curl -i -k -X PUT "https://localhost:9200/_security/profile/${PROFILE_UID}/_data" -u '...' -H "Content-Type: application/json" -d '{...}'
HTTP/1.1 400 Bad Request
...

{"error":{"root_cause":[{"type":"exception","reason":"cannot update profile [...] because the combined profile size of [...] bytes exceeds the maximum of [...] bytes"}],"type":"exception","reason":"cannot update profile [...] because the combined profile size of [...] bytes exceeds the maximum of [...] bytes"},"status":400}

Copy link
Contributor

@jfreden jfreden left a comment

Choose a reason for hiding this comment

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

Good job! This approach LGTM! I've left some comments, let me know what you think.

Map<String, Object> data = combineMaps(mapFromBytesReference(doc.doc.applicationData()), request.getData());
int actualSize = serializationSize(labels) + serializationSize(data);
if (actualSize > limit) {
throw new ElasticsearchException(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be an IllegalArgumentException and result in a 400 since it's caused by an issue triggered by a series of bad requests. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. ElasticsearchStatusException looks like a good option.

Copy link
Contributor Author

@ebarlas ebarlas Nov 13, 2025

Choose a reason for hiding this comment

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

@legrego , I updated my comment above to reflect the 400 Bad Request response change.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks @ebarlas


public class ProfileService {

public static final Setting<Integer> MAX_SIZE_SETTING = Setting.intSetting(
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be a ByteSizeValue Setting then you can set it to 10mb?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch!

return XContentHelper.convertToMap(bytesRef, false, XContentType.JSON).v2();
}

static int serializationSize(Map<String, Object> map) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Clever!


static Map<String, Object> mapFromBytesReference(BytesReference bytesRef) {
if (bytesRef == null || bytesRef.length() == 0) {
return new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this could be Map.of()

if (actualSize > limit) {
throw new ElasticsearchException(
Strings.format(
"cannot update profile [%s] because the combined profile size of [%s] bytes exceeds the maximum of [%s] bytes",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think using a human readable size here (like 10mb) would be nice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Much better!

@jfreden
Copy link
Contributor

jfreden commented Nov 13, 2025

Can you take the PR out of draft mode?

@ebarlas ebarlas marked this pull request as ready for review November 13, 2025 17:08
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-security (Team:Security)

Copy link
Contributor

@jfreden jfreden left a comment

Choose a reason for hiding this comment

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

LGTM! Just a comment on the default value that I think was accidentally set to 100MB.

ebarlas and others added 2 commits November 14, 2025 14:21
…ecurity/profile/ProfileService.java

Co-authored-by: Johannes Fredén <109296772+jfreden@users.noreply.github.com>
@ebarlas ebarlas merged commit 860ad79 into elastic:main Nov 17, 2025
40 checks passed
ebarlas added a commit to ebarlas/elasticsearch that referenced this pull request Nov 26, 2025
…137712)

Add a configurable size limit for user profiles stored in
Elasticsearch. This limit prevents large profiles from exhausting heap
memory and impacting cluster stability.

(cherry picked from commit 860ad79)
ebarlas added a commit to ebarlas/elasticsearch that referenced this pull request Nov 26, 2025
…137712)

Add a configurable size limit for user profiles stored in
Elasticsearch. This limit prevents large profiles from exhausting heap
memory and impacting cluster stability.

(cherry picked from commit 860ad79)
ebarlas added a commit to ebarlas/elasticsearch that referenced this pull request Nov 26, 2025
…137712)

Add a configurable size limit for user profiles stored in
Elasticsearch. This limit prevents large profiles from exhausting heap
memory and impacting cluster stability.

(cherry picked from commit 860ad79)
@ebarlas
Copy link
Contributor Author

ebarlas commented Nov 26, 2025

💚 All backports created successfully

Status Branch Result
9.2
9.1
8.19

Questions ?

Please refer to the Backport tool documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

>bug :Security/Security Security issues without another label Team:Security Meta label for security team v8.19.8 v9.1.8 v9.2.2 v9.3.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants