Skip to main content

Binary Security Context

FHIR Binary resources require special attention when implementing access controls in Medplum. Unlike other FHIR resources, Binary resources cannot use standard compartment-based access policies and must rely on the securityContext element for proper access control.

Critical Point: Binary resources without a securityContext fall back to project-level access controls, potentially exposing sensitive data to any authenticated user with Binary resource permissions.

Security Model

Standard FHIR Resources

Most FHIR resources (Patient, Observation, DiagnosticReport, etc.) can be secured using compartment-based access policies:

{
"resourceType": "AccessPolicy",
"resource": [
{
"resourceType": "Patient",
"criteria": "Patient?_compartment=%patient"
}
]
}

Binary Resources - Special Case

Binary resources cannot use compartment-based access controls. They require explicit securityContext declaration:

{
"resourceType": "Binary",
"id": "example-binary",
"contentType": "application/pdf",
"securityContext": {
"reference": "Patient/homer-simpson"
}
}

Risk Assessment

Without securityContext

  • Access Control: Falls back to basic project permissions
  • Risk Level: Medium to High (depending on data sensitivity)
  • Exposure: Any authenticated user with { "resourceType": "Binary" } permissions can access if they know the UUID
  • UUID Security: While 128-bit UUIDs provide practical obscurity, they can be exposed through:
    • Application logs
    • Network traffic
    • Browser history/developer tools
    • Error messages
    • Database exports
    • Accidentally shared URLs

With securityContext

  • Access Control: Inherits permissions from referenced resource
  • Risk Level: Low (follows established access patterns)
  • Exposure: Only users with access to the referenced resource (e.g., Patient) can access the Binary

Implementation Best Practices

1. HTTP API Binary Upload (Most Common)

Most developers create Binary resources by uploading raw binary content via HTTP POST:

# Recommended - Using X-Security-Context header
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/pdf" \
-H "X-Security-Context: Patient/homer-simpson" \
--data-binary "@patient-report.pdf" \
https://api.medplum.com/fhir/R4/Binary
# Not recommended - Missing security context
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/pdf" \
--data-binary "@patient-report.pdf" \
https://api.medplum.com/fhir/R4/Binary
# This creates a Binary without securityContext!

The X-Security-Context header is the FHIR standard method for setting Binary security context during upload.

Best Practice: Use MedplumClient.createMedia() when possible, as it automatically handles security context:

// Recommended - createMedia automatically sets securityContext
const media = await medplum.createMedia({
data: fileData,
filename: 'patient-report.pdf',
contentType: 'application/pdf',
subject: { reference: `Patient/${patientId}` },
});

// The associated Binary resource will automatically have:
// securityContext: { reference: "Media/[media-id]" }

Benefits of using createMedia:

  • Automatically creates both Media and Binary resources
  • Binary gets securityContext pointing to the Media resource
  • Media resource inherits compartment-based access controls
  • Provides better metadata and searchability than standalone Binary

3. Medplum React Components

Medplum React components that handle file uploads automatically use secure patterns:

AttachmentButton Component

import { AttachmentButton } from '@medplum/react';

<AttachmentButton
securityContext={createReference(resource)}
onUpload={createMedia}
onUploadStart={onUploadStart}
onUploadProgress={onUploadProgress}
onUploadError={onUploadError}
>
{(props) => (
<ActionIcon {...props} radius="xl" color="blue" variant="filled">
<IconCloudUpload size={16} />
</ActionIcon>
)}
</AttachmentButton>;

AttachmentInput Component

import { AttachmentInput } from '@medplum/react';

<AttachmentInput
securityContext={createReference(patient)}
onChange={(attachment) => {
// Uploaded files automatically have proper securityContext
console.log('New attachment:', attachment);
}}
/>;

Both components use MedplumClient.createMedia() internally, ensuring proper security context.

4. Direct FHIR Resource Creation (Advanced)

When creating Binary resources directly as FHIR resources (less common):

// Good - Always include securityContext
const binary = await medplum.createResource({
resourceType: 'Binary',
contentType: 'application/pdf',
securityContext: {
reference: `Patient/${patientId}`,
},
});

// Bad - Missing securityContext
const binary = await medplum.createResource({
resourceType: 'Binary',
contentType: 'application/pdf',
// Missing securityContext!
});

5. Choose Appropriate Security Context

Use CaseRecommended securityContext
Patient documents (lab results, reports)Patient/[patient-id]
Provider-specific contentPractitioner/[practitioner-id]
Organization documentsOrganization/[org-id]
Encounter-specific filesEncounter/[encounter-id]
Media-associated filesMedia/[media-id] (automatic with createMedia)

6. Bot Development Guidelines

When creating Bots that generate Binary resources, prefer the HTTP API or createMedia approaches shown above. If creating Binary resources directly:

// Good - Always include securityContext
const binary = await medplum.createResource({
resourceType: 'Binary',
contentType: 'application/pdf',
securityContext: {
reference: `Patient/${patientId}`,
},
});

// Better - Use createMedia instead
const media = await medplum.createMedia({
data: pdfData,
filename: 'report.pdf',
contentType: 'application/pdf',
subject: { reference: `Patient/${patientId}` },
});

7. DiagnosticReport Integration

For DiagnosticReports with Binary attachments:

{
"resourceType": "DiagnosticReport",
"subject": {
"reference": "Patient/homer-simpson"
},
"presentedForm": [
{
"contentType": "application/pdf",
"url": "Binary/report-binary-id"
}
]
}

Ensure the Binary has matching securityContext:

{
"resourceType": "Binary",
"id": "report-binary-id",
"contentType": "application/pdf",
"securityContext": {
"reference": "Patient/homer-simpson"
}
}

Audit and Remediation

Finding Binary Resources Without securityContext

Since Binary resources are not searchable, you can identify them through related resources:

  1. Through DiagnosticReports:
GET /DiagnosticReport?_include=DiagnosticReport:presented-form
  1. Through DocumentReferences:
GET /DocumentReference?_include=DocumentReference:content
  1. Through Media resources:
GET /Media?_include=Media:content

Remediation Script Example

// Find DiagnosticReports and update associated Binaries
const diagnosticReports = await medplum.searchResources('DiagnosticReport');

for (const report of diagnosticReports) {
if (report.presentedForm) {
for (const form of report.presentedForm) {
if (form.url && form.url.includes('/Binary/')) {
const binaryId = form.url.split('/Binary/')[1];
const binary = await medplum.readResource('Binary', binaryId);

if (!binary.securityContext) {
await medplum.updateResource({
...binary,
securityContext: {
reference: report.subject.reference,
},
});
}
}
}
}
}

For more details on how Binary resources are used in FHIR, see the Binary Data docs.

Remember: Every Binary resource containing sensitive data should have a securityContext. When in doubt, err on the side of more restrictive access controls.