Fire Arrow Server 1.11.0
Fire Arrow Server 1.11.0 has been released.
- (breaking) Token generation, binary upload, and CarePlan materialize operations no longer return distinct HTTP codes for unknown or deleted resource IDs
- (security) CareTeam-scoped access no longer includes patients outside the practitioner's actual teams
- (feature) New CarePlan
$materializeand$dematerializeoperations to start and stop task materialization without Subscription write access - (bugfix) Token generation honors every matching
practitioner-role-coderule, not only the first one in configuration - (maintenance) HAPI FHIR 8.10.0 platform upgrade (HAPI CLI database migration required) and dependency updates including Spring Security 6.5.10 (CVE-2026-22732)
Consistent 403 Responses on Protected Operations
Before running certain operations, the server checks that the caller is allowed to access the target resource — the same kind of check that applies when issuing an API token for a specific Patient, uploading a binary for a resource, or changing a CarePlan through $materialize or $dematerialize.
Until now, those checks could answer differently depending on whether the ID existed. A token request for a non-existent Patient/{id} often returned HTTP 400 with "Resource not found", while the same request for a patient the caller was not permitted to use returned HTTP 403 Forbidden. A deleted resource could return HTTP 410 Gone. Clients that tried many IDs could infer which ones existed from the status code alone.
Starting with 1.11.0, an unknown or deleted target returns the same HTTP 403 as a genuine permission denial, with one message shape:
Access denied: insufficient permissions for <operation> on <ResourceType>/<id>
The response no longer reveals whether the ID exists or has been deleted.
Breaking Change
Do not use HTTP status codes or response text on these operations to tell whether a resource ID exists or has been deleted. Treat every 403 as “not allowed,” and do not branch on 400, 410, or "not found".
| What you call | Examples |
|---|---|
| API token generation | Patient/$generate-durable-token, Patient/$generate-one-time-token (and other identity resource types) |
| Binary upload | Upload paths that authorize against a specific resource instance |
| CarePlan scheduling | CarePlan/{id}/$materialize, CarePlan/{id}/$dematerialize |
If an onboarding flow used token generation to probe whether a patient ID exists (for example, before showing a QR code), use a separate confirmation step instead of interpreting the operation response.
CareTeam-Scoped Access Could Include the Wrong Patients
Deployments that use the CareTeam validator on practitioner (or related) roles intend a simple rule: a user may only see data for patients on CareTeams they belong to — the specialist sees their shared caseload, not the whole hospital population.
Under certain conditions the server could apply that rule too loosely. After looking up CareTeams for the practitioner, it treated every active team returned by the search as valid, even when the practitioner was not listed on that team's participant list. That most often surfaced when search indexing was misconfigured or out of date (for example advanced_lucene_indexing: true with an incomplete index, where participant filters on CareTeam?participant=… could be ignored and the query effectively returned all active teams). The authorization bug itself was independent of indexing: any over-broad team list could widen access the same way.
What downstream applications could show
A practitioner session that should see three shared patients might instead receive authorization for every patient linked to any active CareTeam on the server, including:
Patientsearch and list screens — rosters, typeahead, and "my patients" views populated with records outside the intended caseload.- Reads and writes on clinical data —
Observation,Condition,MedicationRequest,CarePlan,Task, and other resources for patients the user was not actually assigned to, as long as those rules used the CareTeam validator. - Token and onboarding flows —
$generate-durable-tokenor$generate-one-time-tokenunder a CareTeam-scoped rule could be issued for patients the practitioner should never have been able to act on.
Roles that relied on LegitimateInterest or compartment validators alone were not affected by this particular issue; it applied where access was explicitly scoped through CareTeam membership.
Risk
This is unauthorized access to protected health information: users could open, export, or act on the wrong person's record in apps that trust the FHIR API's authorization decisions. That creates confidentiality breaches (including regulatory exposure where PHI must stay within the care relationship), and clinical-safety risk if workflows display another patient's medications, results, or care plans to the wrong user.
Fix in 1.11.0
The server now checks each candidate CareTeam and grants access to its subject patient only if the practitioner (or their role or organization, per existing matching rules) appears on that team's participant list. Teams returned by a faulty search no longer add patients to the accessible set. Practitioners who were already correctly limited see no change.
If you use advanced Lucene indexing, keep the index complete and current; a healthy index avoids noisy searches, but 1.11.0 enforces membership even when search results are wrong.
CarePlan $materialize and $dematerialize
Starting and stopping CarePlan task materialization previously relied on $subscribe-due-events and $unsubscribe-due-events, which create or disable Subscription resources. Clients without Subscription write permission could not use that path, and stopping materialization depended on finding subscriptions visible in the caller's scope — which was not always predictable.
1.11.0 adds two CarePlan operations (available when CarePlan events are enabled in server configuration):
| Operation | Effect |
|---|---|
POST /CarePlan/{id}/$materialize | Opts the CarePlan into server-side task materialization by adding the scheduling tag (https://firearrow.io/fhir/careplan-scheduling / scheduled). Does not create a Subscription. |
POST /CarePlan/{id}/$dematerialize | Removes the scheduling tag only. Does not delete existing Tasks or turn off Subscriptions already in place. |
Authorization uses a new operation: "materialize" rule on CarePlan (wildcards such as resource: "*" are rejected at startup, as for token-generation rules). The caller must also be allowed to read and update that CarePlan — token-style checks run against the specific instance. Granting materialize without a CarePlan read rule still results in 403 Forbidden, because the server reads the CarePlan before applying the tag.
Example configuration:
fire-arrow:
authorization:
validation-rules:
- client-role: Device
resource: CarePlan
operation: read
validator: Allowed
- client-role: Device
resource: CarePlan
operation: materialize
validator: Allowed
$subscribe-due-events and $unsubscribe-due-events remain for webhook and polling setups. Use the new operations when a client only needs to turn materialization on or off without managing Subscriptions.
A successful call returns Parameters with status=success. Calling $materialize again on an already opted-in CarePlan does not trigger duplicate scheduling work.
Bug Fixes
Token generation denied when another role's rule was listed first
Configuration can define several token-generation rules for one client role, each scoped with practitioner-role-code (for example, separate entries for physiotherapists and orthopedists). Search and read rules already combine every entry that applies to the caller; token generation should behave the same way.
Previously, token generation stopped after the first matching line in the file. If an orthopedist rule appeared above a physiotherapist rule, a physiotherapist was denied on $generate-one-time-token and $generate-durable-token even when a later line would have allowed them.
1.11.0 evaluates all matching token-generation rules before allowing or denying the request. Order in the YAML file no longer matters. Deployments with only one rule per role see no change.
Dependency Upgrades
1.11.0 includes a routine platform refresh (FHIR server core 8.8.1 → 8.10.0) and patch updates across the server and administration UI. Spring Security 6.5.10 addresses CVE-2026-22732.
If you override binary storage settings manually, note the renamed threshold property binary_storage_minimum_binary_size (replacing inline_resource_storage_below_size). A new optional JPA setting allow_database_validation_override is available for advanced database validation scenarios.
Database migration required
Upgrading the embedded HAPI FHIR platform from 8.8.1 to 8.10.0 includes new HAPI JPA schema migrations. Per the HAPI FHIR upgrade guide, you must run the HAPI CLI migrate-database command against your PostgreSQL database before starting 1.11.0 on the new image — do not rely on the server process to apply HAPI schema changes on first boot.
Recommended sequence:
- Back up the database.
- Stop the running server instance.
- Run
hapi-fhir-cli migrate-databasewith a CLI build matching 8.10.0, your JDBC URL, and dialectPOSTGRES_9_4(see CLI migrate-database docs). - Start Fire Arrow Server 1.11.0.
Fire Arrow–specific tables (V1_* Flyway scripts under db/migration) are unchanged in this release and continue to apply automatically on startup when Flyway is enabled. This step is only for the shared HAPI FHIR JPA schema tracked in FLY_HFJ_MIGRATION.
Deployments using the Azure PostgreSQL migration helper can run scripts/azure-postgres-migration.sh (set --hapi-cli-version to 8.10.0 so the CLI matches the server).
Other upgrade actions
Beyond the HAPI database migration and the authorization breaking change above, no further action is required unless you relied on distinct HTTP status codes to probe resource existence.