AshR2RML.Security (AshR2RML v26.8.29)

Copy Markdown View Source

Closes a specific, demonstrated gap structurally rather than only documenting it: Ash field policies are enforced only on Ash-mediated reads (AshR2RML.OBDA.InMemory calling Ash.read!/2). Ontop connects to a relational data layer directly over JDBC and has no concept of an Ash actor at all, so a field_policy-protected attribute mapped into R2RML would be returned in full to any SPARQL caller once deployed -- confirmed against a live Postgres + Ontop stack, not assumed.

sanitize_mapping/2 is wired into AshR2RML.Compiler.compile_resources/1 (the Ash-first compilation path over real, compiled Ash resource modules): on an AshPostgres.DataLayer- backed resource, any R2RML-mapped attribute that also carries an explicit field_policy is removed from the mapping before it can ever be rendered to R2RML or handed to Ontop -- the attribute is structurally absent from that path rather than merely refused-and-explained. The exclusion is recorded in mapping.metadata[:field_policy_excluded_attributes] so it stays auditable instead of silently invisible. Ash.DataLayer.Ets-backed resources are untouched, because AshR2RML.OBDA.InMemory already enforces field policies for real there.

This does not attempt to distinguish an unconditionally-granting field policy (authorize_if always()) from a genuinely conditional one -- doing so would require inspecting Ash's internal check AST, which is fragile across Ash versions. Any explicit field_policy declared on an R2RML-mapped attribute is excluded, full stop: an over-broad exclusion costs the resource author one metadata entry to review; an under-broad one silently ships the exact vulnerability this module exists to close.

Summary

Functions

Pure transform: removes attributes' predicate_object_maps from mapping and records the removal in mapping.metadata[:field_policy_excluded_attributes]. [] is a no-op returning mapping unchanged. Decoupled from the AshPostgres.DataLayer backend gate in sanitize_mapping/2 so the exclusion behavior itself is directly testable without requiring an AshPostgres.DataLayer-backed fixture.

Returns mapping unchanged unless ash_resource is AshPostgres.DataLayer-backed with R2RML-mapped attributes that also carry an explicit field_policy -- those attributes' predicate_object_maps are removed and the exclusion recorded in mapping.metadata[:field_policy_excluded_attributes].

Returns the R2RML-mapped attributes on ash_resource that also carry an explicit Ash field_policy, independent of data layer -- sanitize_mapping/2 gates the actual exclusion on AshPostgres.DataLayer. Exposed publicly so it can be verified directly against a resource's real field policies without needing an AshPostgres.DataLayer-backed fixture.

Functions

remove_attributes(mapping, attributes)

@spec remove_attributes(AshR2RML.Mapping.Resource.t(), [atom()]) ::
  AshR2RML.Mapping.Resource.t()

Pure transform: removes attributes' predicate_object_maps from mapping and records the removal in mapping.metadata[:field_policy_excluded_attributes]. [] is a no-op returning mapping unchanged. Decoupled from the AshPostgres.DataLayer backend gate in sanitize_mapping/2 so the exclusion behavior itself is directly testable without requiring an AshPostgres.DataLayer-backed fixture.

sanitize_mapping(ash_resource, mapping)

Returns mapping unchanged unless ash_resource is AshPostgres.DataLayer-backed with R2RML-mapped attributes that also carry an explicit field_policy -- those attributes' predicate_object_maps are removed and the exclusion recorded in mapping.metadata[:field_policy_excluded_attributes].

unenforceable_attributes(ash_resource, resource)

@spec unenforceable_attributes(module(), AshR2RML.Mapping.Resource.t()) :: [atom()]

Returns the R2RML-mapped attributes on ash_resource that also carry an explicit Ash field_policy, independent of data layer -- sanitize_mapping/2 gates the actual exclusion on AshPostgres.DataLayer. Exposed publicly so it can be verified directly against a resource's real field policies without needing an AshPostgres.DataLayer-backed fixture.