Skip to content

fix(policy): allow bare references to approved functions in SpEL conditions#28946

Merged
pmbrull merged 2 commits into
mainfrom
fix/policy-bare-isowner-spel-validation
Jun 11, 2026
Merged

fix(policy): allow bare references to approved functions in SpEL conditions#28946
pmbrull merged 2 commits into
mainfrom
fix/policy-bare-isowner-spel-validation

Conversation

@yan-3005

@yan-3005 yan-3005 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #28947

Problem

Creating or updating any policy whose rule condition uses the bare, parenthesis-less form of a boolean function fails with:

Expression contains a disallowed SpEL construct: PropertyOrFieldReference ('isOwner').
Only literals, boolean/comparison operators, list/map literals, ternaries, and
method calls on approved @Function-annotated methods are allowed.

Reported by a SaaS customer (Orsted, P0). Their roles have policies — created on older releases — whose rules use conditions like !isOwner, noOwner || isOwner, etc. Adding or deleting a rule re-validates every rule in the policy, so the legacy bare conditions block the whole operation.

Root cause

ExpressionValidator was rewritten in #27987 from a regex denylist to an AST allowlist (ALLOWED_NODE_CLASSES, default-deny). SpEL parses isOwner() as a MethodReference (allowlisted) but parses the bare isOwner as a PropertyOrFieldReference, which was not on the allowlist — so the bare form is rejected.

The bare form is functionally identical to the parenthesised call: SpEL's read-only data binding resolves the property isOwner to the no-arg boolean getter isOwner(). It is also documented as valid in RuleEvaluator's own @Function examples ("!isOwner", "!noOwner"), so policies authored on older releases use it.

Fix

Allow standalone PropertyOrFieldReference nodes, gated on the same ALLOWED_FUNCTIONS allowlist already used for method names (ensureCallableNameAllowed). This is generic — it covers every approved @Function in bare form (isOwner, noOwner, isReviewer, matchTeam, hasDomain, …), not just isOwner.

Security — no regression from #27987

Compound references such as System.exit(0), Runtime.getRuntime().exec(...), entity.getClass() parse as CompoundExpression / TypeReference / ConstructorReference — none of which are allowlisted — so they remain blocked, and a bare disallowed name (System, entity) is rejected by the name gate. All existing negative tests still pass; the !System.exit(0) regression guard stays green.

Tests

  • Unit (ExpressionValidatorTest): bare approved references (!isOwner, !noOwner, isReviewer, noOwner() || isOwner, …) accepted; bare disallowed names (System, entity, someObject, Runtime) rejected. Verified these fail without the production change.
  • Integration (PolicyResourceIT): create and update a policy with a bare-reference condition.

ExpressionValidatorTest: 34/34 pass. RuleEvaluatorTest (runtime evaluation): 21/21 pass. mvn spotless:apply clean.

…itions

The AST-based ExpressionValidator (introduced in #27987) rejected
policy/alert conditions that use the bare, parenthesis-less form of a
boolean function (e.g. `!isOwner`, `noOwner || isOwner`). SpEL parses
these as PropertyOrFieldReference nodes, which were not on the node
allowlist, so creating or updating any policy containing such a rule
failed with "disallowed SpEL construct: PropertyOrFieldReference
('isOwner')".

The bare form is functionally identical to the parenthesised call -
SpEL resolves it to the same no-arg boolean getter - and is documented
as valid in RuleEvaluator's @function examples, so existing policies
from older releases use it.

Allow standalone PropertyOrFieldReference nodes, gated on the same
ALLOWED_FUNCTIONS allowlist used for method names. Compound references
(e.g. `System.exit(0)`) parse as CompoundExpression and remain blocked,
so no security case from #27987 is reopened.

Adds unit tests for bare approved/disallowed references and an
integration test covering create and update of a policy with a bare
condition.
Copilot AI review requested due to automatic review settings June 11, 2026 07:05
@yan-3005 yan-3005 added safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch labels Jun 11, 2026
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

✅ PR checks passed

The linked issue has a description and all required Shipping project fields set. Thanks!

Copilot AI 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.

Pull request overview

This PR updates the backend SpEL policy/alert expression safety validator to accept bare (parenthesis-less) references to approved @Function methods (e.g. !isOwner, noOwner || isOwner), restoring compatibility with legacy policies while keeping the same function-name allowlist gate.

Changes:

  • Allowlist SpEL PropertyOrFieldReference AST nodes and gate their names using the existing ALLOWED_FUNCTIONS allowlist (same as method calls).
  • Add unit coverage ensuring bare references to approved functions are accepted and unknown bare references are rejected.
  • Add an integration regression test validating both create and update of policies using bare-reference conditions.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/ExpressionValidator.java Allow bare function references by allowlisting PropertyOrFieldReference and applying the same name-gating as for MethodReference.
openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/ExpressionValidatorTest.java Adds unit tests for allowed/disallowed bare references.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/PolicyResourceIT.java Adds create+update integration regression test for bare-reference policy conditions.

Address review: bare PropertyOrFieldReference acceptance was gated on the
full ALLOWED_FUNCTIONS set, so a bare arg-taking function (e.g.
`matchAnyTag`) passed validation but would fail at SpEL evaluation since
it has no matching no-arg getter.

Build a separate ALLOWED_BARE_FUNCTIONS set from @function methods that
take no arguments and return boolean, and gate bare references on it.
Method-call form is unaffected (still gated on ALLOWED_FUNCTIONS). All
legitimate bare conditions (isOwner, noOwner, hasDomain, matchTeam,
isReviewer, ...) remain valid; nonsensical bare arg-taking references are
now rejected up front.
@github-actions

Copy link
Copy Markdown
Contributor

🟡 Playwright Results — all passed (9 flaky)

✅ 3470 passed · ❌ 0 failed · 🟡 9 flaky · ⏭️ 80 skipped

Shard Passed Failed Flaky Skipped
✅ Shard 1 301 0 0 4
🟡 Shard 2 803 0 3 9
🟡 Shard 3 806 0 2 8
🟡 Shard 4 840 0 3 12
🟡 Shard 5 720 0 1 47
🟡 9 flaky test(s) (passed on retry)
  • Features/DataQuality/TestCaseImportExportE2eFlow.spec.ts › EditAll User: Complete export-import-validate flow (shard 2, 1 retry)
  • Features/DataQuality/TestCaseResultPermissions.spec.ts › User with only VIEW cannot PATCH results (shard 2, 1 retry)
  • Features/Glossary/GlossaryP3Tests.spec.ts › should handle multiple rapid API calls (shard 2, 1 retry)
  • Features/KnowledgeCenterTextEditor.spec.ts › Rich Text Editor - Text Formatting (shard 3, 1 retry)
  • Features/Tasks/TaskNavigation.spec.ts › navigating to /table/TASK-XXXXX should show 404 (invalid URL pattern) (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Table (shard 4, 1 retry)
  • Pages/CustomProperties.spec.ts › Date Time (shard 4, 1 retry)
  • Pages/DataProductAndSubdomains.spec.ts › Add assets to data product and verify count (shard 4, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

@sonarqubecloud

Copy link
Copy Markdown

@pmbrull pmbrull merged commit 69ad482 into main Jun 11, 2026
65 of 90 checks passed
@pmbrull pmbrull deleted the fix/policy-bare-isowner-spel-validation branch June 11, 2026 10:14
@gitar-bot

gitar-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 1 resolved / 1 findings

Allows bare references to approved functions in SpEL conditions by updating the AST validator, resolving the legacy validation failure for no-arg boolean getters.

✅ 1 resolved
Edge Case: Bare refs to arg-requiring functions pass validation but fail at runtime

📄 openmetadata-service/src/main/java/org/openmetadata/service/security/policyevaluator/ExpressionValidator.java:166-180 📄 openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/ExpressionValidatorTest.java:70-71
ensureCallableNameAllowed now accepts any PropertyOrFieldReference whose name is in ALLOWED_FUNCTIONS, regardless of whether that @Function is a no-arg boolean predicate. The set only stores function names, not arity. So a bare reference like matchTeam or hasDomain (functions that actually require arguments) now passes validation — the new test at ExpressionValidatorTest.java:70-71 even asserts "matchTeam" and "hasDomain" are accepted.

At save time this is fine, but at policy-evaluation time SpEL will try to resolve matchTeam as a property/getter on the RuleEvaluator root object. There is no getMatchTeam()/isMatchTeam() getter or field, so evaluation throws SpelEvaluationException (property or field cannot be found). The net effect is that the validator allows users to persist a syntactically-accepted but runtime-broken rule condition that will fail when the policy is enforced — previously such a bare form was rejected up front.

This is functionally safe (no injection: the name is still gated on the allowlist) and matches the validator's stated scope of injection safety only, hence minor. But it weakens the early-feedback the validator used to give. Consider restricting bare PropertyOrFieldReference acceptance to functions known to be no-arg boolean getters (e.g. resolve the @Function method and check getParameterCount() == 0 and a boolean return type when building the allowlist), or at least documenting that bare references are only meaningful for no-arg predicates.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@github-actions

Copy link
Copy Markdown
Contributor

Failed to cherry-pick changes to the 1.13 branch.
Please cherry-pick the changes manually.
You can find more details here.

@github-actions

Copy link
Copy Markdown
Contributor

Failed to cherry-pick changes to the 1.12.11 branch.
Please cherry-pick the changes manually.
You can find more details here.

yan-3005 added a commit that referenced this pull request Jun 11, 2026
…itions (#28946)

* fix(policy): allow bare references to approved functions in SpEL conditions

The AST-based ExpressionValidator (introduced in #27987) rejected
policy/alert conditions that use the bare, parenthesis-less form of a
boolean function (e.g. `!isOwner`, `noOwner || isOwner`). SpEL parses
these as PropertyOrFieldReference nodes, which were not on the node
allowlist, so creating or updating any policy containing such a rule
failed with "disallowed SpEL construct: PropertyOrFieldReference
('isOwner')".

The bare form is functionally identical to the parenthesised call -
SpEL resolves it to the same no-arg boolean getter - and is documented
as valid in RuleEvaluator's @function examples, so existing policies
from older releases use it.

Allow standalone PropertyOrFieldReference nodes, gated on the same
ALLOWED_FUNCTIONS allowlist used for method names. Compound references
(e.g. `System.exit(0)`) parse as CompoundExpression and remain blocked,
so no security case from #27987 is reopened.

Adds unit tests for bare approved/disallowed references and an
integration test covering create and update of a policy with a bare
condition.

* fix(policy): restrict bare references to no-arg boolean functions

Address review: bare PropertyOrFieldReference acceptance was gated on the
full ALLOWED_FUNCTIONS set, so a bare arg-taking function (e.g.
`matchAnyTag`) passed validation but would fail at SpEL evaluation since
it has no matching no-arg getter.

Build a separate ALLOWED_BARE_FUNCTIONS set from @function methods that
take no arguments and return boolean, and gate bare references on it.
Method-call form is unaffected (still gated on ALLOWED_FUNCTIONS). All
legitimate bare conditions (isOwner, noOwner, hasDomain, matchTeam,
isReviewer, ...) remain valid; nonsensical bare arg-taking references are
now rejected up front.
# Conflicts:
#	openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/ExpressionValidatorTest.java
yan-3005 added a commit that referenced this pull request Jun 11, 2026
…itions (#28946)

* fix(policy): allow bare references to approved functions in SpEL conditions

The AST-based ExpressionValidator (introduced in #27987) rejected
policy/alert conditions that use the bare, parenthesis-less form of a
boolean function (e.g. `!isOwner`, `noOwner || isOwner`). SpEL parses
these as PropertyOrFieldReference nodes, which were not on the node
allowlist, so creating or updating any policy containing such a rule
failed with "disallowed SpEL construct: PropertyOrFieldReference
('isOwner')".

The bare form is functionally identical to the parenthesised call -
SpEL resolves it to the same no-arg boolean getter - and is documented
as valid in RuleEvaluator's @function examples, so existing policies
from older releases use it.

Allow standalone PropertyOrFieldReference nodes, gated on the same
ALLOWED_FUNCTIONS allowlist used for method names. Compound references
(e.g. `System.exit(0)`) parse as CompoundExpression and remain blocked,
so no security case from #27987 is reopened.

Adds unit tests for bare approved/disallowed references and an
integration test covering create and update of a policy with a bare
condition.

* fix(policy): restrict bare references to no-arg boolean functions

Address review: bare PropertyOrFieldReference acceptance was gated on the
full ALLOWED_FUNCTIONS set, so a bare arg-taking function (e.g.
`matchAnyTag`) passed validation but would fail at SpEL evaluation since
it has no matching no-arg getter.

Build a separate ALLOWED_BARE_FUNCTIONS set from @function methods that
take no arguments and return boolean, and gate bare references on it.
Method-call form is unaffected (still gated on ALLOWED_FUNCTIONS). All
legitimate bare conditions (isOwner, noOwner, hasDomain, matchTeam,
isReviewer, ...) remain valid; nonsensical bare arg-taking references are
now rejected up front.
# Conflicts:
#	openmetadata-service/src/test/java/org/openmetadata/service/security/policyevaluator/ExpressionValidatorTest.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs To release Will cherry-pick this PR into the release branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Policy creation/update fails when an existing policy contains !isOwner without parentheses

3 participants