Skip to content

Policy evaluation NPE #5426

@correiaafonso12

Description

@correiaafonso12

Bug Report

Hey everyone

Describe the Bug

If a policy constraint's left operand is bound to a scope, but no evaluation function for that scope is registered, a set of default cases applies depending on the constraint operator (ref). As of right now, only the operators EQ, IN and NEQ are being handled. If the policy constraint contains any other operator, the function returns null, which propagates a Null Pointer Exception (NPE).

Expected Behavior

If a constraint is being evaluated but no evaluation function for it was found, the evaluation should fail.

Note that the policydefinitions API tries to enforce this by searching for an evaluation function when a PolicyDefinition is being registered, but one can be created for a different scope. For example, a policy constraint left operand can be bound to the CATALOG_SCOPE, but it's function bound to the TRANSFER_SCOPE.

Observed Behavior

NPE propagated on Policy evaluation.

For example, when requesting a Catalog, the NPE is propagated on the Provider Connector and the Consumer Connector receives a 500.

Steps to Reproduce

Steps to reproduce the behavior:

  1. Create an extension that registers the following:
// Action needs to be bound so that the permission in not filtered out (ScopeFilter, L76-L78)
ruleBindingRegistry.bind("http://www.w3.org/ns/odrl/2/use", CATALOG_SCOPE);
// Bind a left operand to CATALOG_SCOPE
ruleBindingRegistry.bind("https://w3id.org/edc/v0.0.1/ns/leftOp", CATALOG_SCOPE);
// Bind an evaluation function to another scope (e.g. TRANSFER_SCOPE). Needed to be able to create the PolicyDefinition
policyEngine.registerScope(TRANSFER_SCOPE, TransferProcessPolicyContext.class);
policyEngine.registerFunction(
                TransferProcessPolicyContext.class,
                Permission.class,
                "https://w3id.org/edc/v0.0.1/ns/leftOp",
                (operator, rightValue, rule, ctx) -> true);
  1. Start a Connector pair, Consumer and Provider
  2. Create any dummy Asset
  3. Create a PolicyDefinition as follows:
{
	"@context": {
		"@vocab": "https://w3id.org/edc/v0.0.1/ns/"
	},
	"@id": "test-policy",
	"policy": {
		"@context": "http://www.w3.org/ns/odrl.jsonld",
		"@type": "Set",
		"permission": [
			{
				"action": "odrl:use", // use bound action
				"constraint": {
					"leftOperand": "leftOp", // use bound operand
					"operator": "isAnyOf", // use an unhandled operator (e.g. isAnyOf)
					"rightOperand": "rightOp"
				}
			}
		]
	}
}
  1. Create a ContractDefinition with the created policy definition as the access policy and usage policy
  2. Send a Catalog Request
  3. Verify receives 500 error from Provider

Context Information

  • Tested in version 0.14.0, but I believe the Policy Engine / Evaluator has not changed since

Detailed Description

500 error:

Image

NPE logs:

SEVERE 2025-12-22T17:42:03.084785254 JerseyExtension: Unexpected exception caught

java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because "result" is null

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitRule(PolicyEvaluator.java:240)

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPermission(PolicyEvaluator.java:99)

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPermission(PolicyEvaluator.java:48)

	at org.eclipse.edc.policy.model.Permission.accept(Permission.java:42)

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.lambda$visitPolicy$0(PolicyEvaluator.java:75)

	at java.base/java.util.ArrayList.forEach(Unknown Source)

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPolicy(PolicyEvaluator.java:75)

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.visitPolicy(PolicyEvaluator.java:48)

	at org.eclipse.edc.policy.model.Policy.accept(Policy.java:94)

	at org.eclipse.edc.policy.evaluator.PolicyEvaluator.evaluate(PolicyEvaluator.java:67)

	at org.eclipse.edc.policy.engine.PolicyEngineImpl.evaluate(PolicyEngineImpl.java:140)

	at org.eclipse.edc.connector.controlplane.catalog.ContractDefinitionResolverImpl.lambda$resolveFor$2(ContractDefinitionResolverImpl.java:62)

	at java.base/java.util.Optional.map(Unknown Source)

	at org.eclipse.edc.connector.controlplane.catalog.ContractDefinitionResolverImpl.lambda$resolveFor$3(ContractDefinitionResolverImpl.java:62)

	at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)

	at java.base/java.util.stream.SliceOps$1$1.accept(Unknown Source)

	at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source)

	at java.base/java.util.concurrent.ConcurrentHashMap$ValueSpliterator.tryAdvance(Unknown Source)

	at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(Unknown Source)

	at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(Unknown Source)

	at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source)

	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)

	at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source)

	at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(Unknown Source)

	at java.base/java.util.stream.ReferencePipeline.toArray(Unknown Source)

	at java.base/java.util.stream.ReferencePipeline.toArray(Unknown Source)

	at java.base/java.util.stream.ReferencePipeline.toList(Unknown Source)

	at org.eclipse.edc.connector.controlplane.catalog.ContractDefinitionResolverImpl.resolveFor(ContractDefinitionResolverImpl.java:67)

	at org.eclipse.edc.connector.controlplane.catalog.DatasetResolverImpl.query(DatasetResolverImpl.java:67)

	at org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.lambda$getCatalog$0(CatalogProtocolServiceImpl.java:62)

	at org.eclipse.edc.spi.result.AbstractResult.map(AbstractResult.java:145)

	at org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.lambda$getCatalog$1(CatalogProtocolServiceImpl.java:61)

	at org.eclipse.edc.transaction.spi.NoopTransactionContext.execute(NoopTransactionContext.java:34)

	at org.eclipse.edc.connector.controlplane.services.catalog.CatalogProtocolServiceImpl.getCatalog(CatalogProtocolServiceImpl.java:60)

	at org.eclipse.edc.protocol.dsp.http.message.DspRequestHandlerImpl.createResource(DspRequestHandlerImpl.java:132)

	at org.eclipse.edc.protocol.dsp.catalog.http.api.controller.BaseDspCatalogApiController.requestCatalog(BaseDspCatalogApiController.java:84)

Possible Implementation

I believe that the default cases that execute when no evaluation function is found were added in the very early stages of the Policy Engine, but now are not needed. A possible implementation would be to return false if an evaluation function was not found, and never return null.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageall new issues awaiting classification

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions