Skip to content

Consistently prevent using exclusion prefix on its own#139337

Merged
elasticsearchmachine merged 14 commits intoelastic:mainfrom
ywangd:fix-bug-with-empty-exclusion
Dec 16, 2025
Merged

Consistently prevent using exclusion prefix on its own#139337
elasticsearchmachine merged 14 commits intoelastic:mainfrom
ywangd:fix-bug-with-empty-exclusion

Conversation

@ywangd
Copy link
Member

@ywangd ywangd commented Dec 11, 2025

The dash character is used as a prefix to signify index exclusion. It should not be used on its own. For example, an expression like *,- should be invalid. However, today this is handled differently depending on whether security is enabled or disabled. When security is enabled, the standalone exclusion char is ignored and the expression is handled as just *. But when security is disabled, the expression encounters an IndexNotFoundException.

This PR fixes the inconsistency by always throwing InvalidIndexNameException. In addition, this PR also fixes #45504 by ensuring InvalidIndexNameException is thrown for empty index name instead of IndexOutOfBoundsException.

Resolves: #45504

The dash character is used as a prefix to signify index exclusion. It
should not be used on its own. For example, an expression like `*,-`
should be invalid. However, today this is handled differently depending
on whether security is enabled or disabled. When security is enabled,
the standalone exclusion char is ignored and the expression is handled
as just `*`. But when security is disabled, the expression encounters an
IndexNotFoundException.

This PR fixes the inconsistency by always throwing
IllegalArgumentException.
@ywangd ywangd requested a review from tvernum December 11, 2025 00:41
@ywangd ywangd added >bug :Security/Security Security issues without another label v9.3.0 labels Dec 11, 2025
@elasticsearchmachine elasticsearchmachine added the Team:Security Meta label for security team label Dec 11, 2025
@elasticsearchmachine
Copy link
Collaborator

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

@elasticsearchmachine
Copy link
Collaborator

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

@github-actions
Copy link
Contributor

github-actions bot commented Dec 11, 2025

🔍 Preview links for changed docs

@github-actions
Copy link
Contributor

ℹ️ Important: Docs version tagging

👋 Thanks for updating the docs! Just a friendly reminder that our docs are now cumulative. This means all 9.x versions are documented on the same page and published off of the main branch, instead of creating separate pages for each minor version.

We use applies_to tags to mark version-specific features and changes.

Expand for a quick overview

When to use applies_to tags:

✅ At the page level to indicate which products/deployments the content applies to (mandatory)
✅ When features change state (e.g. preview, ga) in a specific version
✅ When availability differs across deployments and environments

What NOT to do:

❌ Don't remove or replace information that applies to an older version
❌ Don't add new information that applies to a specific version without an applies_to tag
❌ Don't forget that applies_to tags can be used at the page, section, and inline level

🤔 Need help?

Comment on lines 127 to 129
if (localIndexExpression.isEmpty()) {
throw invalidExpression(false);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

Currently, an empty expression encounters an IndexOutOfBoundsException because we call localIndexExpression.charAt(0) a few lines below. With security disabled, the error is IndexNotFoundException. I think it's better for them to both throw IllegalArgumentException. Strictly speaking, this change is not about exclusion. But since it's an issue of similar nature, I decide to include it as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, I think this effectively fixes long-standing #45504 👍 Could you add a note to the PR description to close the issue once merged?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah good find. Updated the PR description as well as adding the issue to the changelog.

@ywangd
Copy link
Member Author

ywangd commented Dec 11, 2025

In addition to index exclusion, there is also remote cluster exclusion. Currently, an expression such as -:foo encounters a NoSuchRemoteClusterException. We could decide to change the exception to IllegalArgumentException as well. But I'll leave it out for this PR.

Comment on lines 237 to 238
final var e = expectThrows(ElasticsearchSecurityException.class, () -> trySearch(expressions).get());
assertThat(e.getCause(), instanceOf(IllegalArgumentException.class));
Copy link
Member Author

Choose a reason for hiding this comment

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

IllegalArgumentException is always wrapped with ElasticsearchSecurityException. I think we could argue whether such wrapping is necessary. But I don't want to go down that path for now. Alternatively, if we want to avoid the wrapping, we could throw either InvalidIndexNameException or IndexNotFoundException instead. Please let me know if you prefer either of these.

Copy link
Member Author

@ywangd ywangd Dec 14, 2025

Choose a reason for hiding this comment

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

On second thought, I decided to use InvalidIndexNameException which seems more specific for the particular issue. eb9498b

Copy link
Contributor

Choose a reason for hiding this comment

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

InvalidIndexNameException makes sense to me

@elasticsearchmachine elasticsearchmachine added the serverless-linked Added by automation, don't add manually label Dec 11, 2025
@ywangd ywangd requested a review from a team December 14, 2025 23:50
@n1v0lg n1v0lg requested review from n1v0lg and removed request for tvernum December 15, 2025 09:55
Copy link
Contributor

@n1v0lg n1v0lg left a comment

Choose a reason for hiding this comment

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

LGTM 👍

Comment on lines 127 to 129
if (localIndexExpression.isEmpty()) {
throw invalidExpression(false);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, I think this effectively fixes long-standing #45504 👍 Could you add a note to the PR description to close the issue once merged?

final Set<String> remoteExpressions
) {
if (localIndexExpression.isEmpty()) {
throw invalidExpression(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: I'd add invalidExclusion() and invalidExpression() methods to make the call more self-explanatory (as opposed to the raw boolean arg)

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure see aff7f77

new String[] { "-" },
new String[] { "*", "-" },
new String[] { "testXXX", "-" },
new String[] { "*", "-", "testXXX" }
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd also add:

new String[] { "-", "-" },
new String[] { "-testXXX", "-" },
new String[] { "*", "-testXXX", "-" }
Copy link
Member Author

Choose a reason for hiding this comment

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

Pushed 3468c92

createIndicesWithRandomAliases("test1");

final List<String[]> expressionsList = List.of(
new String[] { "-" },
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd also add:

new String[] { "_all", "-" }, // interestingly this doesn't work in server/src/test/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolverTests.java since we don't allow `_` at that validation stage 
new String[] { "-", "-" },
new String[] { "-testXXX", "-" },
new String[] { "*", "-testXXX", "-" }
Copy link
Member Author

Choose a reason for hiding this comment

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

I think we should make IndexAbstractionResolver behave the same as IndexNameExpressionResolver for handling _all. It's inconsistent right now. The expression _all is a special match-all only when it is used on its own. Otherwise, it is just a normal name. As a normal name, it is invalid because index cannot begin with _. But with security enabled, an expression like foo*,_all ends up being just _all and expands to match all if foo* matches nothing. But if foo* matches anything, it then encounters InvalidIndexNameException. This is not a sensible behaviour and I think we should instead throw InvalidIndexNameException in both cases.

I added a test for { "_all", "-" } on the core side and assert it throws index name "must. not start with '_'". See 3468c92 along with your suggestion.

createIndicesWithRandomAliases("test1");

final List<String[]> expressionsList = List.of(
new String[] { "" },
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's also cover:

new String[] { "", "_all" },
Copy link
Member Author

Choose a reason for hiding this comment

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

Yep see 3468c92

Comment on lines 237 to 238
final var e = expectThrows(ElasticsearchSecurityException.class, () -> trySearch(expressions).get());
assertThat(e.getCause(), instanceOf(IllegalArgumentException.class));
Copy link
Contributor

Choose a reason for hiding this comment

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

InvalidIndexNameException makes sense to me

@elasticsearchmachine
Copy link
Collaborator

Hi @ywangd, I've updated the changelog YAML for you.

@elasticsearchmachine
Copy link
Collaborator

Hi @ywangd, I've updated the changelog YAML for you.

@ywangd ywangd added the auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) label Dec 16, 2025
@ywangd
Copy link
Member Author

ywangd commented Dec 16, 2025

CI Failure is irrelevant but reproducible. I raised #139576

@elasticsearchmachine elasticsearchmachine merged commit b909322 into elastic:main Dec 16, 2025
41 checks passed
@ywangd ywangd deleted the fix-bug-with-empty-exclusion branch December 16, 2025 04:10
breskeby pushed a commit to breskeby/elasticsearch that referenced this pull request Feb 11, 2026
breskeby pushed a commit to breskeby/elasticsearch that referenced this pull request Feb 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge-without-approval Automatically merge pull request when CI checks pass (NB doesn't wait for reviews!) >bug :Security/Security Security issues without another label serverless-linked Added by automation, don't add manually Team:Security Meta label for security team v9.3.0

3 participants