Property Filters
Property filters modify or remove specific properties from FHIR resources before they are returned to the client. They are applied after authorization succeeds, allowing you to grant access to a resource while redacting sensitive fields.
Property filters are a powerful tool for in-place anonymization, but they are not trivial to configure securely when the client also has search access.
Property filters only redact responses. The underlying data remains fully indexed and searchable. A determined client can exploit this to reverse-engineer redacted values through targeted searches, _sort ordering, _include directives, _filter expressions, chained search parameters, or other FHIR query mechanisms. Each of these vectors must be explicitly blocked — there is no single switch that closes all side-channels at once.
The safest approach is to grant only read access (not search) to clients that receive property-filtered data. When a client can only read resources by ID, there is no search surface to exploit. Property filters on read operations are straightforward and secure without additional configuration.
If you must allow search access alongside property filters, read the Preventing Search-Based Data Leakage section carefully and understand that missing a single search parameter from the blocked list can allow a client to circumvent anonymization entirely.
GraphQL and HFQL offer a more controlled surface than REST search:
- GraphQL read (
graphql-read) with inline reference expansion is safe: references are resolved by ID (not by client-controlled search arguments), and property filters apply to all expanded resources automatically. This is the recommended access pattern for clients that need to traverse resource graphs while property filters are active. - HFQL is fail-closed: when
blocked-search-paramsis configured for a resource type, all HFQL queries on that type are rejected outright. There is no partial leakage path. - GraphQL search (
graphql-search) has the same risks as REST search — search arguments map to FHIR search parameters and must be blocked in the same way.
As a rule of thumb: prefer read + GraphQL read over search for property-filtered roles. If search is required, budget time for a thorough audit of every search parameter that could reveal redacted data.
Use Cases
- Clinical studies - Provide access to clinical data while hiding patient identifying information. See the Automatic Anonymization how-to for a complete walkthrough.
- Privacy regulations - Remove contact details or addresses from resources shared with certain roles
- Data minimization - Return only the fields a client actually needs
How They Work
Property filters are attached to authorization rules. When a rule with property filters matches and the validator grants access, Fire Arrow processes each filter against the resource before returning it:
Each filter targets a specific property using a FHIRPath expression and applies a transformation to it.
Available Filters
NullFilter
Removes the property entirely from the resource. The field will not appear in the response.
property-filters:
- property: "name"
filter: NullFilter
RandomFilter
Replaces the property with randomly generated data. This preserves the structure of the resource while making the actual values unreadable. RandomFilter supports HumanName and ContactPoint data types.
property-filters:
- property: "name"
filter: RandomFilter
For a HumanName, RandomFilter generates a random given name and family name. For a ContactPoint, it generates a random value appropriate for the contact system (e.g., a random phone number or email).
Configuration
Property filters are defined as part of an authorization rule:
fire-arrow:
authorization:
rules:
- client-role: Practitioner
resource: Patient
operation: read
validator: Allowed
property-filters:
- property: "name"
filter: NullFilter
- property: "telecom"
filter: RandomFilter
- property: "address"
filter: NullFilter
In this example, when a Practitioner reads a Patient:
- The patient's name is removed entirely
- The patient's telecom (phone, email) is replaced with random values
- The patient's address is removed entirely
All other fields (identifiers, clinical links, etc.) are returned normally.
Preventing Search-Based Data Leakage
Property filters redact fields from responses, but the underlying data remains fully indexed and searchable. Without additional configuration, a client can infer redacted values by issuing targeted searches and observing whether results come back. You must configure blocked-search-params and/or blocked-includes on every search rule that co-exists with property filters to close this side-channel.
This configuration is not trivial. FHIR has a large and complex search surface: direct search parameters, chained parameters, _include/_revinclude, _filter expressions, _has reverse chaining, and more. Missing a single vector means a client can circumvent your anonymization. Treat blocked-search-params configuration as a security audit, not a quick checkbox.
The Threat
When a property filter removes a patient's name, a client can still call:
GET /fhir/Patient?name=Smith
If the search returns results, the client knows a patient named "Smith" exists — even though the name was removed from the response. The same applies to any searchable field that is property-filtered.
Even without a direct search filter, a client can use _sort to infer redacted values:
GET /fhir/Patient?_sort=name
The response order reveals the relative ordering of redacted names across patients — grouping identical values and exposing alphabetical rank — even though the names themselves are not shown.
Similarly, if Patient.generalPractitioner is filtered out, a client can use _include to reveal the relationship:
GET /fhir/Patient?_include=Patient:general-practitioner
The included Practitioner resource in the response bundle reveals the filtered reference.
The simplest and most reliable way to use property filters is to not grant search access at all. If a client only has read and/or graphql-read permissions, there is no search surface to exploit and property filters are fully effective without any additional configuration.
Only add search rules for property-filtered roles when you have a genuine business requirement and are prepared to maintain a complete and audited blocked-search-params list.
Blocking Search Parameters
The blocked-search-params field on a validation rule lists FHIR search parameter names that must be rejected. When a client uses any of these parameters, the server returns 403 Forbidden.
The mapping from FHIRPath properties to search parameters is many-to-many, so blocked parameters must be listed explicitly. There is no automatic derivation — this is intentional, because the mapping between FHIRPath expressions and FHIR search parameters is complex and context-dependent. An incorrect automatic mapping would create a false sense of security.
| FHIRPath property | Search parameters to block |
|---|---|
name | name, family, given, phonetic |
telecom | telecom, phone, email |
address | address, address-city, address-state, address-postalcode, address-country |
birthDate | birthdate |
Consult the FHIR search parameter registry for the resource type you are filtering to identify all search parameters that correspond to the properties you are redacting.
Blocking Includes
The blocked-includes field lists _include and _revinclude directives in ResourceType:searchparam format. These are only needed when property filters target reference fields (since _include follows references).
blocked-includes:
- "Patient:general-practitioner"
- "Patient:link"
What Happens on Violation
When a blocked parameter or include is used, the server returns:
HTTP 403 Forbidden
Search parameter 'name' is not permitted for this operation
The error message does not reveal why the parameter is blocked, to avoid leaking filter configuration details.
_sort, _filter, _has, _text, and _content Parameters
When blocked-search-params is configured, the following parameters are automatically handled:
_sort— Each sort key is checked against the blocked list. Sorting by a blocked parameter is rejected because the response order leaks relative values of the redacted field. Multi-key sort values (e.g.,_sort=birthdate,-name) are parsed individually; the request is rejected if any key is blocked._filter,_has,_text,_content— These parameters are rejected entirely on both REST and GraphQL search requests. They support arbitrary or full-text search criteria that could reference any blocked parameter, making fine-grained analysis fragile. The_hasreverse-chaining parameter is particularly dangerous because it embeds search parameter names deep inside a colon-delimited chain (e.g.,_has:Patient:link:name=Smith) where they are invisible to normal parameter inspection. The server uses a fail-closed approach across all search paths.
GraphQL and HFQL Coverage
GraphQL read operations (inline reference expansion) are the most secure access pattern for property-filtered data. When a GraphQL query expands a reference (e.g., Observation { subject { name } }), the server resolves the reference by resource ID — not through a client-controlled search. Property filters apply to all resolved resources automatically. The client has no way to inject search criteria into the resolution path, making this side-channel free.
HFQL takes a fail-closed approach: when blocked-search-params is configured for a resource type, all HFQL queries on that type are rejected outright. HFQL WHERE clauses use FHIRPath expressions rather than search parameter names, making fine-grained analysis complex. This fail-safe may be relaxed in a future version.
GraphQL search operations (e.g., PatientList(name: "Smith")) have the same risk profile as REST search. GraphQL search arguments are mapped to FHIR search parameters and checked against blocked-search-params in the same way. The _sort, _filter, _has, _text, and _content parameters are also rejected on GraphQL search when blocked params are configured, mirroring the REST behavior. You need the same blocking configuration for graphql-search rules as for REST search rules.
Summary of access patterns by risk level:
| Access pattern | Side-channel risk | Additional config needed |
|---|---|---|
read (REST) | None | No |
graphql-read (inline expansion) | None | No |
HFQL (with blocked-search-params) | None (fail-closed) | blocked-search-params triggers rejection |
search (REST) | High — requires careful blocking | blocked-search-params, blocked-includes |
graphql-search | High — same as REST search | blocked-search-params, blocked-includes |
Full Example: Clinical Study Access
The configuration below is provided as a reference. In a production deployment, you should audit every search parameter for the resource types you are filtering and validate that no search vector is left open. Consider starting with read-only access and adding search only if there is a concrete business requirement.
A clinical study needs practitioners to access observation data linked to patients, but patient-identifying information must be hidden:
fire-arrow:
authorization:
rules:
# Study practitioners can read patients but without identifying info
- client-role: Practitioner
resource: Patient
operation: read
validator: OrganizationCompartment
identity-filter: "meta.tag.where(system = 'http://example.org/role' and code = 'study-practitioner').exists()"
property-filters:
- property: "name"
filter: RandomFilter
- property: "telecom"
filter: NullFilter
- property: "address"
filter: NullFilter
- property: "birthDate"
filter: NullFilter
# Study practitioners can search patients — with blocked search params
- client-role: Practitioner
resource: Patient
operation: search
validator: OrganizationCompartment
identity-filter: "meta.tag.where(system = 'http://example.org/role' and code = 'study-practitioner').exists()"
property-filters:
- property: "name"
filter: RandomFilter
- property: "telecom"
filter: NullFilter
- property: "address"
filter: NullFilter
- property: "birthDate"
filter: NullFilter
blocked-search-params:
- "name"
- "family"
- "given"
- "phonetic"
- "telecom"
- "phone"
- "email"
- "address"
- "address-city"
- "address-state"
- "address-postalcode"
- "address-country"
- "birthdate"
# Study practitioners can read observations normally
- client-role: Practitioner
resource: Observation
operation: read
validator: OrganizationCompartment
identity-filter: "meta.tag.where(system = 'http://example.org/role' and code = 'study-practitioner').exists()"
# Regular practitioners see everything
- client-role: Practitioner
resource: Patient
operation: read
validator: PractitionerCompartment
In this setup, study practitioners see patient resources with randomized names and no contact information or birth dates, while regular practitioners with a compartment relationship see the full patient record. Study practitioners cannot probe redacted values through search, _sort, _include, _filter, _has, GraphQL arguments, or HFQL queries.
Related
- Automatic Anonymization - end-to-end recipe for setting up anonymized access in clinical study scenarios.
- Multi-Tenancy Access Control - combining property filters with organizational isolation.
- Identity Filters - FHIRPath conditions on the client's identity resource.
- Authorization Concepts - how the rule-based authorization system works.