Dashboard

Security & Compliance

Disclaimer. This page is provided for informational purposes only and does not constitute legal, compliance, or security advice. Organizations must conduct their own assessments with qualified professionals to determine obligations under HIPAA, state privacy laws, and other applicable regulations. FHIRfly makes no representations or warranties regarding the suitability of any configuration for specific compliance requirements.

Zero-Knowledge Architecture

The SMART Health Links SDK uses a zero-knowledge encryption architecture: the FHIRfly server never possesses the decryption key. All PHI is encrypted client-side before upload, and the key exists only in the shlink:/ URL held by the patient or sharing party.

Data Server sees Server does NOT see
Encrypted JWE blob Yes (opaque ciphertext) Plaintext health data
Manifest structure File count, content types File contents
Metadata Expiration, access count, passcode hash Passcode plaintext, decryption key
Decryption key Never Embedded in shlink:/ URL only
Patient identity Never Name, DOB, identifiers (all encrypted)

How It Works

  1. Client generates a 256-bit AES key using crypto.randomBytes(32) (Node.js CSPRNG)
  2. Client encrypts the FHIR Bundle using AES-256-GCM with a fresh 12-byte IV per encryption
  3. Encrypted JWE is uploaded to storage (FhirflyStorage or your own S3/Azure/GCS bucket)
  4. Server stores only the opaque ciphertext — it cannot decrypt without the key
  5. The decryption key is encoded into the shlink:/ URL — it never touches the server
  6. Recipient scans the QR code, which contains the full URL including the key
  7. Decryption happens in the recipient's browser or app — the key is never transmitted to the server

This means a breach of the storage layer (S3, database, or FHIRfly's infrastructure) would expose only encrypted blobs that are computationally infeasible to decrypt without the key.

Encryption Details

All encryption uses Node.js built-in crypto module only — no third-party cryptography dependencies.

Property Value
Algorithm AES-256-GCM (authenticated encryption with associated data)
Key length 256 bits (32 bytes)
Key generation crypto.randomBytes(32) — OS-level CSPRNG
IV length 96 bits (12 bytes), fresh per encryption
IV generation crypto.randomBytes(12) — never reused
Authentication GCM tag (128-bit), AAD set to JWE protected header
Compression DEFLATE before encryption (zip: "DEF" per JWE spec)
Format JWE Compact Serialization (RFC 7516)
JWE algorithm alg: "dir" (direct key agreement — no key wrapping)
JWE encryption enc: "A256GCM"
Key in URL Base64url-encoded in the shlink:/ URL fragment
Storage encryption JWE + S3 SSE-AES256 (double encryption for FhirflyStorage)

Why AES-256-GCM?

  • Authenticated encryption: GCM provides both confidentiality and integrity. Tampering with the ciphertext is detected via the authentication tag.
  • No padding oracle: GCM is a stream cipher mode, eliminating padding oracle attacks that affect CBC.
  • NIST approved: AES-256-GCM is approved for use with sensitive government data (FIPS 140-2/3 compliant when using a validated module).
  • Performance: Hardware-accelerated AES-NI on modern processors.

Why Not Key Wrapping?

The alg: "dir" (direct key agreement) approach means the encryption key is used directly — no intermediate key encryption key (KEK). This is correct for this use case because:

  • The key is generated per-SHL and never shared between SHLs
  • There is no need for key rotation (each SHL has its own ephemeral key)
  • Fewer cryptographic operations means fewer potential failure modes

Access Controls

Passcode Protection

SHLs can optionally require a passcode before revealing the manifest. Passcodes are:

  • Hashed with SHA-256 before storage on the FHIRfly platform (plaintext is never persisted)
  • Compared using crypto.timingSafeEqual() to prevent timing side-channel attacks
  • Rate-limited: 5 attempts per 5 minutes per IP per SHL, plus 50 failed attempts per hour globally per SHL

Passcode strength is the implementer's responsibility. Recommendations:

Risk Level Minimum Passcode Use Case
Low 4-digit numeric PIN ER triage, short-lived links (hours)
Medium 6+ alphanumeric Patient portal sharing (days/weeks)
High 8+ mixed characters Long-lived records, sensitive diagnoses

For high-risk use cases (mental health, substance abuse, HIV status), consider short expiration windows and single-use access counts in addition to strong passcodes.

Expiration

SHLs support time-based expiration (expiresAt). After expiration:

  • The manifest endpoint returns 410 Gone
  • Content download is blocked
  • A nightly cleanup job deletes expired encrypted content from S3

Important: Expiration prevents new access to the encrypted content. Anyone who previously decrypted the content retains their local copy. Expiration does not "un-share" data that has already been decrypted.

Access Count Limits

SHLs support count-based limits (maxAccessCount). Each successful manifest retrieval increments an atomic counter. When the limit is reached, further access returns 410 Gone.

Access count increments use MongoDB's atomic $inc operator on the FHIRfly platform, preventing race conditions from concurrent requests.

Revocation

SHLs can be revoked immediately via SHL.revoke() or the API. Revocation:

  • Sets a revoked flag on the SHL record
  • Causes all subsequent requests to return 404 Not Found
  • Is immediate — no propagation delay

Rate Limiting

Public SHL endpoints enforce three tiers of rate limiting to prevent abuse:

Tier Scope Limit Purpose
1. IP rate limit All public SHL endpoints 60 req/min/IP Prevent scraping and DoS
2. Passcode per-IP Manifest endpoint (passcode-protected SHLs) 5 attempts/5 min/IP/SHL Slow brute-force from single source
3. Passcode global Manifest endpoint (passcode-protected SHLs) 50 failed attempts/hour/SHL Defend against distributed attacks

Rate limit headers are included on all responses:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
Retry-After: 42          (only on 429 responses)

Brute-Force Analysis

With rate limiting in place, brute-force attacks on passcode-protected SHLs are computationally impractical:

Passcode Type Combinations Single IP (5/5min) Distributed (50/hr)
4-digit numeric 10,000 ~7 days ~8 days
6-digit numeric 1,000,000 ~1.9 years ~2.3 years
4-char alphanumeric 1,679,616 ~3.2 years ~3.8 years

HIPAA Considerations

Is FHIRfly a Business Associate?

Under the zero-knowledge architecture, FHIRfly does not access, maintain, or transmit PHI when using FhirflyStorage. The encrypted JWE blobs stored in FHIRfly's infrastructure are opaque ciphertext that FHIRfly cannot decrypt.

Whether this constitutes "maintenance" of PHI under HIPAA is a legal determination that depends on your specific facts and circumstances. Organizations should consult qualified HIPAA counsel.

Key factors for your assessment:

  • FHIRfly never possesses the decryption key
  • Encrypted blobs cannot be decrypted without the key in the shlink:/ URL
  • FHIRfly cannot identify patients from the encrypted data
  • S3 storage is additionally encrypted with SSE-AES256

BYOS (Bring Your Own Storage) Users

If you use S3Storage, AzureStorage, or GCSStorage with your own infrastructure, FHIRfly's SDK runs entirely in your environment. The SDK is a client-side library — no data flows to FHIRfly's servers. Your existing BAA with your cloud provider (AWS, Azure, GCP) covers the storage layer.

Business Associate Agreements

If your organization requires a BAA for HIPAA-covered use of FhirflyStorage, contact us at support@fhirfly.io. Self-hosted deployments using S3Storage, AzureStorage, or GCSStorage do not require a BAA with FHIRfly since no PHI passes through our infrastructure.

De-Identification

SHLs containing de-identified data (per 45 CFR 164.514) are not PHI. If your IPS bundles contain only de-identified data, HIPAA's security requirements may not apply to the SHL content. Verify with your privacy officer.

Implementer Compliance Checklist

Use this checklist when preparing for a security review or compliance assessment.

Encryption & Key Management

  • Encryption keys are generated using crypto.randomBytes(32) (CSPRNG)
  • Each SHL has a unique encryption key (no key reuse)
  • Decryption keys are distributed only via the shlink:/ URL
  • QR codes containing shlink:/ URLs are treated as sensitive (not stored in logs, databases, or analytics)
  • Debug mode (debug: true) is disabled in production — it stores unencrypted bundles

Access Controls

  • Passcode-protected SHLs use passcodes appropriate for the data sensitivity
  • Expiration is set on all SHLs (no indefinite links for production use)
  • Access count limits are set when appropriate
  • Revocation capability is integrated into your application

Infrastructure (BYOS)

  • S3 bucket has ServerSideEncryption enabled (SSE-S3 or SSE-KMS)
  • S3 bucket has Block Public Access enabled
  • Azure Blob Storage uses service-level encryption
  • GCS bucket uses Google-managed or customer-managed encryption keys
  • Storage access credentials use least-privilege IAM roles

Application Security

  • SHL URLs are transmitted over HTTPS only
  • Server endpoints have TLS 1.2+ (handled by CloudFront/ALB for FhirflyStorage)
  • Application logs do not contain shlink:/ URLs or decryption keys
  • Error responses do not leak internal state or stack traces

Organizational

  • Data classification for SHL content is documented
  • Incident response plan covers potential SHL key exposure
  • Staff with access to infrastructure are trained on zero-knowledge architecture
  • Retention and deletion policies are defined for expired SHLs

Shared Responsibility Model

Responsibility FhirflyStorage BYOS (S3/Azure/GCS)
Encryption algorithm (AES-256-GCM) SDK SDK
Key generation (CSPRNG) SDK SDK
Client-side encryption SDK SDK
Server-side storage encryption FHIRfly (SSE-AES256) You (configure SSE)
Rate limiting FHIRfly (3-tier) You (implement)
TLS termination FHIRfly (CloudFront) You (your infra)
Access logging FHIRfly (access counts) You (S3/CloudTrail)
Passcode hashing FHIRfly (SHA-256) SDK (depends on handler)
Data residency US (us-east-1) Your choice
Backup/recovery FHIRfly You
Key management (shlink:/ URLs) You (always) You (always)
Passcode strength policy You (always) You (always)
Expiration/access count policy You (always) You (always)

Specifications & Standards

Standard Reference
SMART Health Links HL7 SMART Health Links IG
International Patient Summary IPS Implementation Guide
JWE (JSON Web Encryption) RFC 7516
AES-GCM NIST SP 800-38D
AES Key Sizes NIST FIPS 197
HIPAA Security Rule 45 CFR Part 164, Subpart C
HIPAA De-Identification 45 CFR 164.514