Skip to content

Test gap: no direct read verification for non-patient-compartment resources with patient/ scopes #751

@timharsch

Description

@timharsch

Summary

The g(10) test kit requests patient/-prefixed scopes for non-patient-compartment resources (e.g., patient/Practitioner.read, patient/Organization.read) and validates that the authorization server accepts these scopes, but never verifies that the server actually honors them for direct reads (GET /Practitioner/{id}, GET /Organization/{id}, etc.).

This allows an EHR to pass certification while returning HTTP 403 on direct reads of referenced non-patient-compartment resources — a behavior we have encountered in production with a certified EHR vendor.

What IS tested

Mechanism What it covers
Scope string check (unrestricted_resource_type_access_group.rb) Verifies OAuth scope strings mention the resource type
NON_PATIENT_COMPARTMENT_RESOURCES list (scope_constants.rb) Accepts either patient/ or user/ prefixed scopes
Patient-scoped search (resource_access_test.rb) GET /Resource?patient=X — verifies search access
US Core inherited tests Search + reference resolution for must-support elements

What is NOT tested

  • Direct reads (GET /Practitioner/{id}, GET /Organization/{id}, GET /Location/{id}, etc.) — no fhir_read calls exist for any non-patient-compartment resources
  • The server actually honoring patient/Practitioner.read (or .rs) for a GET by ID

Why this matters

Patient-facing applications routinely need to resolve references from clinical resources to directory resources. For example, an Observation.performer reference to Practitioner/abc-123 requires either:

  1. A direct read: GET /Practitioner/abc-123
  2. An _include on the original search

The US Core Server CapabilityStatement (STU3.1.1) specifies Conformance Expectation SHALL for the read interaction on Practitioner and Organization, with Reference Policy: resolves. Returning 403 on a direct read when the client holds patient/Practitioner.read is inconsistent with this requirement.

While _include is a viable workaround for some use cases, it does not cover all scenarios (e.g., resolving a reference from a previously cached or stored resource). The CapabilityStatement mandates the read interaction precisely for this reason.

Observed behavior in the wild

We integrate with multiple certified EHR vendors. The direct read approach (GET /Practitioner/{id} with a patient/Practitioner.read token) works correctly on ModMed, athenahealth, and Epic. One certified vendor returns HTTP 403, citing patient compartment restrictions — yet passes Inferno certification because no test exercises this code path.

Suggested addition

Add fhir_read test(s) for non-patient-compartment resources listed in NON_PATIENT_COMPARTMENT_RESOURCES when the granted scopes include a read permission for those resource types. For example, after a successful search that returns a referenced Practitioner ID, perform GET /Practitioner/{id} and verify a 200 response.

References

  • US Core STU3.1.1 CapabilityStatement — Practitioner, Organization: SHALL support read
  • FHIR Patient Compartment Definition: https://build.fhir.org/compartmentdefinition-patient.html
  • 45 CFR § 170.315(g)(10)(i)(A) — requires compliance with US Core mandatory capabilities
  • SMART App Launch v2.2.0 — scopes specification

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions