Skip to main content

Automatic Anonymization of Data

Use case

A pharmaceutical company sponsors a multi-site clinical trial. Patient enrollment, treatment, and observations are recorded in a shared Fire Arrow Server instance. Site investigators — the treating physicians at each hospital — need full access to patient records so they can deliver care. The study sponsor's clinical monitors, however, must be able to review the clinical data (lab results, vital signs, adverse events) without ever seeing who the patients are. Regulations require that the sponsor cannot access Protected Health Information (PHI): no names, no addresses, no birth dates, no phone numbers.

Today, many organizations solve this by exporting data, running it through a de-identification pipeline, and uploading the scrubbed dataset to a separate system. This creates delay, operational complexity, and a risk of stale data. With Fire Arrow Server, the same FHIR server can serve both audiences simultaneously — full data to the site investigator, anonymized data to the sponsor — with no export step, no second system, and no delay.

Other scenarios this recipe applies to

  • Public health reporting: A regional health authority queries hospital data to track disease trends. Epidemiologists need clinical observations and conditions but must not see patient identities. A dedicated public-health practitioner role receives anonymized Patient resources while hospitals retain full access.
  • Real-world evidence studies: A research institution analyzes treatment outcomes across multiple hospitals. Researchers access Observations, MedicationRequests, and Conditions linked to patients, but the Patient resources themselves are stripped of identifying information to comply with GDPR or HIPAA.
  • AI/ML model training: A data science team extracts clinical datasets to train predictive models. The training pipeline authenticates with a service account that receives anonymized data, ensuring no PHI enters the model training environment.

What you will build

Both users access the same Patient resources in the same database. The authorization engine matches each request to a set of rules. Rules for the study-sponsor role include property filters that transform the response before it reaches the client. Rules for the site-investigator role have no property filters, so the full resource is returned.

Prerequisites

Understanding the anonymization model

The two-role pattern

Fire Arrow Server's authorization rules support practitioner-role-system and practitioner-role-code fields. These restrict a rule so it only applies to practitioners who hold a PractitionerRole with the specified code. By creating two sets of rules for the same resources and operations — one with property filters and one without — you create two access tiers from a single configuration:

Role codeSees Patient.nameSees Patient.addressSees ObservationsCan write
site-investigatorReal nameReal addressYesYes
study-sponsorRandomized nameHiddenYesNo

Both roles use the LegitimateInterest validator, so each user only sees data within their organizational scope. The difference is what happens to the data after authorization succeeds: the sponsor's rules apply property filters that strip identifying information.

How identity filters add precision

Identity filters let you add a FHIRPath condition on the practitioner's own FHIR resource. This is useful when role codes alone are not granular enough. For example, a single organization might have both blinded and unblinded monitors. Instead of creating separate role codes for each, you can tag practitioners and use an identity filter:

identity-filter: "meta.tag.where(system = 'http://example.org/trial' and code = 'blinded').exists()"

This rule only applies to practitioners whose Practitioner resource carries the blinded tag. An unblinded monitor at the same organization, without the tag, skips this rule and falls through to a rule without property filters. The Extending with identity filters section below shows a complete example.

How property filters transform responses

Property filters are the mechanism that performs the actual anonymization. Each filter targets a FHIR element path and applies a transformation:

  • NullFilter removes the property entirely. The field does not appear in the response.
  • RandomFilter replaces the property with randomly generated data of the same type. For HumanName, it generates a plausible random name. For ContactPoint, it generates a random phone number or email. The resource structure is preserved, but the values are meaningless.

Property filters are applied after the authorization validator grants access. They do not affect search behavior — the server still uses real patient references internally for compartment evaluation and search narrowing. This means the underlying data remains fully indexed and searchable even when fields are redacted from responses. See the warning below about the implications for search access.

Search access alongside property filters is difficult to secure

Property filters only transform response bodies. The underlying data remains fully indexed and searchable. A determined client can reverse-engineer redacted values through targeted searches (Patient?name=Johnson), _sort ordering, _include / _revinclude directives, _filter expressions, _has reverse chaining, 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.

Read access is safe. If the anonymized role only has read (and/or graphql-read) permission — no search rules — property filters are fully effective without any additional configuration. There is simply no search surface to exploit.

If you must allow search, you need to configure blocked-search-params and blocked-includes on every search rule that co-exists with property filters. This is a security-critical exercise: FHIR has a large and complex search surface (direct parameters, chained parameters, _include, _filter, _has, _sort, _text, _content), and missing a single search parameter from the blocked list can allow a client to circumvent anonymization entirely. Treat this configuration as a security audit, not a quick checkbox.

See Property Filters — Preventing Search-Based Data Leakage for the full threat model, worked examples, and a table mapping FHIRPath properties to the search parameters you must block.

As a rule of thumb: prefer read + GraphQL read over search for anonymized roles. Only add search rules when there is a concrete business requirement and you are prepared to maintain a complete, audited blocked-search-params list.

Which FHIR resources and properties to anonymize

The HIPAA Safe Harbor method requires removing 18 categories of identifiers. When these identifiers appear as structured properties in FHIR resources, property filters can target them directly. The following table maps the most relevant identifiers to FHIR resource properties and recommends a filter for each.

Patient

The Patient resource is the primary target for anonymization. It concentrates most direct identifiers.

PropertyPHI categoryRecommended filterRationale
nameNamesRandomFilterPreserves the resource structure (given/family/prefix) with plausible fake values. Useful when downstream systems expect a name to be present.
telecomPhone, fax, emailNullFilterNo analytical value in most study contexts. Remove entirely.
addressGeographic dataNullFilterStreet addresses, cities, and ZIP codes are direct identifiers. Remove entirely.
birthDateDatesNullFilterFull birth dates are identifiers under Safe Harbor. If you need age-based analysis, compute age brackets before anonymization or in your analytics pipeline.
identifierMRN, SSN, insurance numbersNullFilterMedical record numbers and government IDs are direct identifiers.
photoFull-face photographsNullFilterBiometric identifier. Remove entirely.

RelatedPerson

RelatedPerson resources (emergency contacts, guardians) carry the same demographic fields as Patient and can indirectly identify the patient.

PropertyRecommended filter
nameRandomFilter
telecomNullFilter
addressNullFilter

Clinical resources with free-text fields

Resources like Observation, Condition, DiagnosticReport, and MedicationRequest may contain note or text fields where clinicians type free-form narratives. These narratives sometimes include patient names, locations, or other identifiers embedded in prose.

ResourcePropertyRecommended filter
ObservationnoteNullFilter
ConditionnoteNullFilter
DiagnosticReportconclusionNullFilter
MedicationRequestnoteNullFilter
Any resourcetext (narrative)NullFilter
tip

Most clinical resources (Observation, Condition, Encounter, MedicationRequest) do not contain PHI in their structured fields — the identifying information lives in the subject reference, which points to a Patient. If the Patient resource is anonymized, the clinical data is already de-linked from a real identity. Focus your property filters on Patient and RelatedPerson first, then add note/narrative filters if your data contains free-text entries.

Complete authorization configuration

The following application.yaml excerpt configures a clinical trial scenario with two practitioner roles. The site-investigator role has full read/write access. The study-sponsor role has read-only access with property filters that anonymize patient data.

This example includes search rules — review carefully

The configuration below grants search access to the study-sponsor role for convenience (e.g., listing patients at a trial site). Because property filters co-exist with search rules, blocked-search-params is configured on the Patient search rule to prevent search-based de-anonymization. If your anonymized role does not need to search patients, remove the Patient search rule entirely — this is the safest option. See Property Filters — Preventing Search-Based Data Leakage for the full threat model.

fire-arrow:
authorization:
default-validator: Forbidden
validation-rules:

# =========================================================
# SITE INVESTIGATORS - Full clinical access (no anonymization)
# =========================================================

- client-role: Practitioner
resource: Patient
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Patient
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Patient
operation: update
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator

- client-role: Practitioner
resource: Observation
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Observation
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Observation
operation: create
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator

- client-role: Practitioner
resource: Condition
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Condition
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Condition
operation: create
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator

- client-role: Practitioner
resource: MedicationRequest
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: MedicationRequest
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: MedicationRequest
operation: create
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator

- client-role: Practitioner
resource: Encounter
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: Encounter
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator

- client-role: Practitioner
resource: DiagnosticReport
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator
- client-role: Practitioner
resource: DiagnosticReport
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: site-investigator

# =========================================================
# STUDY SPONSORS - Read-only access with anonymized patients
# =========================================================
# ⚠️ The search rules below include blocked-search-params to
# prevent clients from probing redacted values. If you do not
# need search access for the anonymized role, removing the
# search rules entirely is the safest option. See the warning
# above about search-based data leakage.

# Patient: anonymized (name randomized, PHI stripped)
- client-role: Practitioner
resource: Patient
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "name"
filter: RandomFilter
- property: "telecom"
filter: NullFilter
- property: "address"
filter: NullFilter
- property: "birthDate"
filter: NullFilter
- property: "identifier"
filter: NullFilter
- property: "photo"
filter: NullFilter
- client-role: Practitioner
resource: Patient
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "name"
filter: RandomFilter
- property: "telecom"
filter: NullFilter
- property: "address"
filter: NullFilter
- property: "birthDate"
filter: NullFilter
- property: "identifier"
filter: NullFilter
- property: "photo"
filter: NullFilter
blocked-search-params:
- "name"
- "family"
- "given"
- "phonetic"
- "telecom"
- "phone"
- "email"
- "address"
- "address-city"
- "address-state"
- "address-postalcode"
- "address-country"
- "birthdate"
- "identifier"

# Clinical data: read-only, notes stripped
- client-role: Practitioner
resource: Observation
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "note"
filter: NullFilter
- client-role: Practitioner
resource: Observation
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "note"
filter: NullFilter

- client-role: Practitioner
resource: Condition
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "note"
filter: NullFilter
- client-role: Practitioner
resource: Condition
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "note"
filter: NullFilter

- client-role: Practitioner
resource: MedicationRequest
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "note"
filter: NullFilter
- client-role: Practitioner
resource: MedicationRequest
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "note"
filter: NullFilter

- client-role: Practitioner
resource: Encounter
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
- client-role: Practitioner
resource: Encounter
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor

- client-role: Practitioner
resource: DiagnosticReport
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "conclusion"
filter: NullFilter
- client-role: Practitioner
resource: DiagnosticReport
operation: search
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
property-filters:
- property: "conclusion"
filter: NullFilter

# =========================================================
# ALL PRACTITIONERS - Identity verification
# =========================================================

- client-role: Practitioner
resource: Practitioner
operation: me
validator: Allowed
Why duplicate property filters on read and search?

Property filters are defined per rule, and read and search are separate operations that require separate rules. The same property filter list must appear on both rules if you want anonymization to apply regardless of how the client retrieves the resource. Note that blocked-search-params is only needed on search rules — it has no effect on read rules since read operations do not accept search parameters.

Step-by-step instructions

Step 1: Create the trial organization hierarchy

Create the organizations that represent the trial sites and the sponsoring organization.

# Create the sponsor organization
curl -X PUT http://localhost:8080/fhir/Organization/pharma-corp \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "Organization",
"id": "pharma-corp",
"name": "PharmaCorp International",
"active": true
}'

# Create a trial site (hospital)
curl -X PUT http://localhost:8080/fhir/Organization/trial-site-a \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "Organization",
"id": "trial-site-a",
"name": "City General Hospital - Trial Site A",
"active": true
}'

Step 2: Create practitioners with role-specific PractitionerRoles

Create the site investigator and the study sponsor as practitioners, then assign them roles that link them to their organizations.

# Site investigator at Trial Site A
curl -X PUT http://localhost:8080/fhir/Practitioner/dr-investigator \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "Practitioner",
"id": "dr-investigator",
"name": [{ "family": "Martinez", "given": ["Elena"], "prefix": ["Dr."] }],
"active": true
}'

curl -X POST http://localhost:8080/fhir/PractitionerRole \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "PractitionerRole",
"practitioner": { "reference": "Practitioner/dr-investigator" },
"organization": { "reference": "Organization/trial-site-a" },
"code": [{
"coding": [{
"system": "http://example.org/trial-roles",
"code": "site-investigator",
"display": "Site Investigator"
}]
}],
"active": true
}'

# Study sponsor monitor
curl -X PUT http://localhost:8080/fhir/Practitioner/sponsor-monitor \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "Practitioner",
"id": "sponsor-monitor",
"name": [{ "family": "Chen", "given": ["Wei"] }],
"active": true
}'

curl -X POST http://localhost:8080/fhir/PractitionerRole \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "PractitionerRole",
"practitioner": { "reference": "Practitioner/sponsor-monitor" },
"organization": { "reference": "Organization/trial-site-a" },
"code": [{
"coding": [{
"system": "http://example.org/trial-roles",
"code": "study-sponsor",
"display": "Study Sponsor Monitor"
}]
}],
"active": true
}'

The sponsor monitor has a PractitionerRole at trial-site-a so that LegitimateInterest grants access to that site's patients. The role code study-sponsor triggers the authorization rules that include property filters.

Step 3: Enroll a trial patient

curl -X PUT http://localhost:8080/fhir/Patient/trial-patient-001 \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "Patient",
"id": "trial-patient-001",
"identifier": [{
"system": "http://hospital.example.org/mrn",
"value": "MRN-98765"
}],
"name": [{ "family": "Johnson", "given": ["Maria", "Elena"] }],
"gender": "female",
"birthDate": "1978-11-23",
"telecom": [
{ "system": "phone", "value": "+1-555-0142" },
{ "system": "email", "value": "[email protected]" }
],
"address": [{
"line": ["742 Evergreen Terrace"],
"city": "Springfield",
"state": "IL",
"postalCode": "62704"
}],
"managingOrganization": {
"reference": "Organization/trial-site-a"
}
}'

Step 4: Add clinical data for the trial

# Record a lab observation
curl -X POST http://localhost:8080/fhir/Observation \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <admin-token>" \
-d '{
"resourceType": "Observation",
"status": "final",
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "4548-4",
"display": "Hemoglobin A1c"
}]
},
"subject": { "reference": "Patient/trial-patient-001" },
"effectiveDateTime": "2026-04-10T09:30:00Z",
"valueQuantity": {
"value": 6.8,
"unit": "%",
"system": "http://unitsofmeasure.org",
"code": "%"
},
"note": [{
"text": "Patient Maria Johnson reports improved diet adherence since last visit."
}]
}'

Notice the note field contains the patient's name — a common occurrence in clinical notes. The property filters configured for the study-sponsor role will strip this field.

Step 5: Configure the authorization rules

Copy the complete authorization configuration into your application.yaml and restart Fire Arrow Server.

Step 6: Verify anonymized access (study sponsor)

Authenticate as the sponsor monitor and read the patient:

curl -s http://localhost:8080/fhir/Patient/trial-patient-001 \
-H "Authorization: Bearer <sponsor-token>" | jq .

The response will look like this — note the randomized name and missing fields:

{
"resourceType": "Patient",
"id": "trial-patient-001",
"name": [
{
"family": "Kowalski",
"given": ["Thomas", "James"]
}
],
"gender": "female",
"managingOrganization": {
"reference": "Organization/trial-site-a"
}
}

Key observations:

  • name has been replaced with a random name by RandomFilter. Every request generates a different random name.
  • telecom, address, birthDate, identifier, and photo are completely absent — removed by NullFilter.
  • gender and managingOrganization are untouched because they are not targeted by any property filter.

Now read an Observation:

curl -s http://localhost:8080/fhir/Observation?patient=Patient/trial-patient-001 \
-H "Authorization: Bearer <sponsor-token>" | jq '.entry[0].resource'

The Observation is returned with all clinical data intact (code, value, effectiveDateTime), but the note field is absent — the free-text mention of the patient's name has been stripped.

Step 7: Verify full access (site investigator)

Authenticate as the site investigator and read the same patient:

curl -s http://localhost:8080/fhir/Patient/trial-patient-001 \
-H "Authorization: Bearer <investigator-token>" | jq .

The response contains the complete, unmodified resource — real name, birth date, address, phone number, and all identifiers. The site investigator's rules have no property filters.

Step 8: Use debug mode to troubleshoot

If a request returns unexpected results, enable debug mode to see which rules were evaluated:

curl -H "Authorization: Bearer <sponsor-token>" \
-H "X-Fire-Arrow-Debug: true" \
http://localhost:8080/fhir/Patient/trial-patient-001

The debug output shows:

  • Which rules matched the request's role, resource, and operation.
  • Whether the practitioner-role-code filter on each rule matched the practitioner's PractitionerRole.
  • Which property filters were applied to the response.
danger

Do not enable debug mode in production. The debug output exposes your full authorization configuration, including property filter settings.

Extending with identity filters

The role-code approach works well when your access tiers align cleanly with PractitionerRole codes. Sometimes they don't. A sponsor organization might have both blinded monitors (who must not see PHI) and unblinded monitors (who need full access for safety reporting). Both have the same study-sponsor role code, but they need different anonymization behavior.

Identity filters solve this by inspecting properties of the practitioner's own FHIR resource. Tag blinded practitioners with a meta tag:

curl -X PATCH http://localhost:8080/fhir/Practitioner/sponsor-monitor \
-H "Content-Type: application/json-patch+json" \
-H "Authorization: Bearer <admin-token>" \
-d '[{
"op": "add",
"path": "/meta/tag",
"value": [{
"system": "http://example.org/trial-blinding",
"code": "blinded"
}]
}]'

Then split the sponsor rules into blinded (with property filters) and unblinded (without):

fire-arrow:
authorization:
validation-rules:
# Blinded sponsor monitors: anonymized access
- client-role: Practitioner
resource: Patient
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor
identity-filter: "meta.tag.where(system = 'http://example.org/trial-blinding' and code = 'blinded').exists()"
property-filters:
- property: "name"
filter: RandomFilter
- property: "telecom"
filter: NullFilter
- property: "address"
filter: NullFilter
- property: "birthDate"
filter: NullFilter
- property: "identifier"
filter: NullFilter
- property: "photo"
filter: NullFilter

# Unblinded sponsor monitors: full access
- client-role: Practitioner
resource: Patient
operation: read
validator: LegitimateInterest
practitioner-role-system: http://example.org/trial-roles
practitioner-role-code: study-sponsor

The identity filter meta.tag.where(system = '...' and code = 'blinded').exists() is evaluated against the Practitioner resource of the authenticated user. For a blinded monitor, the tag exists, the filter passes, and the rule with property filters applies. For an unblinded monitor, the filter fails, the rule is skipped, and the next matching rule (without property filters) takes effect.

tip

Place rules with identity filters before rules without them. A rule without an identity filter acts as a catch-all for that role/resource/operation combination. If it appears first, the identity-filtered rule will never be reached.

What anonymization does and does not protect against

Property filters are a powerful tool for automated de-identification, but they are not a complete privacy solution on their own. Understanding their boundaries helps you build the right overall architecture.

ProtectionProvided by property filters?Notes
Removing structured PHI fields (name, address, phone)YesNullFilter and RandomFilter handle this directly.
Replacing names with plausible fakesYesRandomFilter generates realistic HumanName values.
Stripping free-text notes that may contain PHIYesNullFilter on note and text fields removes them entirely.
Detecting PHI embedded in coded values or referencesNoIf a patient's name appears in an Observation code.text, property filters will not detect it. Use NullFilter on code.text if this is a concern.
Preventing inference through search parametersPartially — requires explicit configurationProperty filters alone do not prevent search-based probing. You must configure blocked-search-params and blocked-includes on every search rule that co-exists with property filters. The safest approach is to not grant search access at allread and graphql-read are side-channel free. If search is required, every parameter that could reveal a redacted field must be blocked; missing one is enough to break anonymization. See Property Filters — Preventing Search-Based Data Leakage.
Preventing re-identification through small cohortsNoIf your dataset contains only one female patient aged 90+, gender and age range alone may re-identify them. This requires statistical de-identification (k-anonymity) beyond what property filters provide.
Protecting against inference from temporal patternsNoA sequence of Encounters and Observations with precise timestamps could theoretically narrow down a patient's identity. Consider whether date precision needs to be reduced in your analytics pipeline.

For scenarios requiring HIPAA Safe Harbor compliance, property filters address the structured-data identifiers (items 1-6, 8-9, 17 from the Safe Harbor list). Items like vehicle identifiers (12), device serial numbers (13), and biometric identifiers (16) are less commonly stored in FHIR resources but should be audited if present in your data.

Cross-references