Article

Building HIPAA-Compliant Analytics

PHI in dashboards. Row-level security. Audit trails. What Metabase gives you and what you have to build yourself. Hard-won lessons from deploying analytics in healthcare.

Cloud Intelligence
Cloud Intelligence Team

You cannot bolt compliance on after the fact

A healthcare provider asks for a dashboard that shows patient outcomes by facility. The engineering team spins up Metabase, connects it to a read replica, builds a few charts, and ships an embed. It works. The charts look clean. The CEO sees the numbers at the Monday standup.

Three weeks later the compliance officer walks into your office. She asks four questions. Who viewed the dashboard? What data did they see? Can you prove a specific user saw only the records they had a right to see? Can you produce the last six months of access logs?

You cannot answer a single one.

You shipped analytics. You did not ship a HIPAA-compliant analytics platform. The difference is the difference between a product and a liability.

What HIPAA demands from your analytics layer

HIPAA compliance for embedded analytics breaks into four requirements. Each one costs more to retrofit than to build from the start.

Business Associate Agreement. If a third party touches your protected health information, you need a BAA. Metabase Cloud signs one. AWS signs one. Your self-hosted Metabase instance sits on infrastructure you control, which means the BAA obligation shifts to your infrastructure provider. You still need the agreement. You still need to verify it covers the services you use.

Encryption in transit and at rest. TLS everywhere. No plaintext database connections. Encrypted volumes. This part is straightforward. Modern PostgreSQL supports TLS. AWS RDS enables encryption at rest with a checkbox. The work is configuration, not architecture.

Access controls. Every user sees only the data their role permits. A physician sees their own patient panel. A practice manager sees the full practice. A regional director sees all practices in the region. A compliance officer sees audit logs but no patient data. This requirement splits into authentication and authorization, and authorization is where the hard work lives.

Audit logs. You must record every access event. Dashboard views. Query executions. Data exports. Filter changes. Who did what, to which records, at what time. You must store these logs in a tamper-evident form. You must produce them on demand during a compliance review or breach investigation.

Most teams handle encryption and sign a BAA without trouble. The access control and audit trail work separates a working prototype from a production system.

Tenant isolation starts at the database

Application-level filtering fails. You add a WHERE facility_id = ? clause to every query and trust that nobody writes a raw SQL question in Metabase that skips the filter. A junior developer removes the clause during a refactor. A A Metabase native query bypasses the application layer. Your filtering becomes a suggestion.

PostgreSQL row-level security solves this at the database engine.

CREATE POLICY facility_isolation ON patient_records
    FOR SELECT
    USING (facility_id = current_setting('app.current_facility_id')::integer);

The database enforces the filter. The application cannot bypass it. The Metabase connection cannot bypass it. Even a SELECT * FROM patient_records returns only the rows the policy permits. You set the runtime parameter at connection time and the database handles the rest.

This pattern requires one policy per table and one parameter per tenant dimension. For multi-tenant deployments, the USING clause references a tenant ID set at the start of each session. For role-scoped access, the policy references a role parameter. You compose multiple policies for overlapping access patterns.

The implementation detail that trips most teams: connection pooling. A pooled connection reuse must reset the runtime parameter between requests. PgBouncer in transaction mode handles this. Session-level pooling does not. You configure the pooler, verify the reset, and test with simultaneous requests from different tenants.

RBAC is not a feature. It is a matrix you maintain.

Role-based access control in healthcare analytics means three dimensions: what data you see, what actions you take, and what dashboards you access.

Role hierarchy. A Provider sees their own patient panel. A Clinical Director sees the provider panel plus aggregate practice metrics. A Regional VP sees all practices in the region but cannot drill to individual patient records. An Auditor sees access logs and configuration but no clinical data. A Super Admin sees everything including the audit trail.

Attribute-based scoping. Role alone is not enough. Two Clinical Directors in different practices must see different data. You attach attributes to each user: facility IDs, region codes, department scopes. The row-level security policy uses these attributes. The application loads them at authentication and passes them to the database session.

Dashboard-level permissions. Not every role sees every dashboard. A billing dashboard shows claim denial rates to finance staff but hides them from clinical users. An operational dashboard shows bed occupancy to practice managers but not to external auditors. You define collections in Metabase and map them to the role hierarchy.

The permission matrix. A spreadsheet works for four roles and six dashboards. It breaks at twenty roles and fifty dashboards. At scale, you store the matrix in a database table. You update it through an admin interface. You test it with automated checks that assert specific users see specific data.

RolePatient DataAggregate MetricsBilling DataAudit LogsAdmin Panel
ProviderOwn panelNoNoNoNo
Clinical DirectorPractice-wideYesNoNoNo
Regional VPNo drill-downYesNoNoNo
Billing AnalystNoNoFull accessNoNo
Compliance OfficerNoNoNoRead onlyNo
System AdminNoNoNoFull accessYes

The matrix above looks simple. The implementation behind it includes the database policies, the application middleware, the Metabase collection permissions, and the token generation that passes attributes to embedded iframes. Each layer must agree on the same permission model.

Every view, every query, every export

HIPAA requires an audit trail. Building one means logging events at three levels and storing them in a structure that survives compliance review.

Dashboard views. When a user opens an embedded dashboard, you log the user ID, the dashboard ID, the timestamp, the IP address, and the session token. This gives you a record of who looked at what and when.

Query execution. When Metabase runs a query against PostgreSQL, you log the SQL, the parameters, the row count returned, and the execution time. Enable log_statement = 'all' in PostgreSQL and ship the logs to a separate audit database. The audit database runs on infrastructure that no application user can access.

Data exports. When a user downloads a CSV or PDF, you log the export event with the same metadata plus the file format and the row count. If your application does not intercept the download event, you cannot audit it. Most embedded analytics setups skip this step.

Tamper-evident storage. Audit logs in the same database as patient data defeat the purpose. A data engineer with write access can modify the audit table. You ship logs to a separate database with append-only permissions. The application service account has INSERT on the audit schema but no UPDATE or DELETE. Even the DBA needs a break-glass procedure to modify audit records.

The audit trail is not a debugging tool. A compliance officer asks for it during a breach investigation. You have forty-eight hours to produce six months of records. If your logs sit in rotating files that clean up after thirty days, you fail the audit.

What Metabase gives you and what you build yourself

Metabase provides a solid foundation. It does not provide a complete HIPAA solution. Understanding the boundary saves you months of building the wrong things.

Metabase handles:

  • TLS termination for the application layer
  • Authentication via SAML, LDAP, JWT, or email
  • Collection-based dashboard permissions
  • Query caching and performance optimization
  • Chart rendering and filter controls

You must build:

  • Row-level security in the database. Metabase has no concept of row-level permissions. Every user with access to a collection sees every row the connected database account can query. You enforce row-level filtering in PostgreSQL with RLS policies.
  • PHI masking and redaction. Metabase renders whatever the query returns. If a chart shows patient names instead of patient IDs, Metabase will not stop it. You apply views or column-level security in PostgreSQL to mask or omit PHI columns from analytics queries.
  • The audit trail. Metabase logs internal events but does not produce a HIPAA-grade audit trail. You build the logging pipeline, the separate audit database, and the reporting interface.
  • Tenant isolation. Metabase treats a database connection as a single data source. Multi-tenant deployments require you to scope data at the database layer. Metabase will not partition data per tenant.
  • The permission matrix. Metabase collections map to groups, but the attribute-based scoping that healthcare requires sits outside Metabase. You build the role hierarchy, the attribute resolution, and the token generation.
  • Session-scoped database credentials. Metabase connects to the database with a static connection string. You need a proxy or middleware that sets the RLS runtime parameter per request.

Our work with Crossroads Health put every item on the right side of this list into production. The PostgreSQL RLS policies scoped provider data to individual facilities. The audit pipeline logged every dashboard view and export. The permission matrix mapped seven roles across three organizational levels. Metabase rendered the charts. The infrastructure around it made them compliant.

HIPAA compliant dashboard

SOC 2 overlaps with HIPAA but does not replace it

Teams often treat SOC 2 as a shortcut to HIPAA compliance. They cover different things. You need both.

HIPAA focuses on protected health information. It mandates specific controls for access, audit, and transmission of PHI. SOC 2 focuses on trust service criteria: security, availability, processing integrity, confidentiality, and privacy. Many SOC 2 controls satisfy HIPAA requirements, but a SOC 2 report does not prove HIPAA compliance.

The practical overlap sits in three areas:

Access controls. Both frameworks require you to restrict data access to authorized users and to review those permissions. Your RBAC implementation satisfies both if you build it once.

Audit logging. HIPAA requires access logs. SOC 2 requires monitoring and alerting on anomalous access. The same logging pipeline serves both if you add detection rules on top of the raw logs. A compliance officer reviewing logs for HIPAA and an auditor reviewing SOC 2 controls look at the same data through different lenses.

Change management. SOC 2 requires documented change procedures. HIPAA requires that changes to systems handling PHI go through a review process. Both benefit from the same CI/CD pipeline with approval gates.

The Crossroads Health Provider Portal achieved both certifications. The analytics infrastructure that satisfied HIPAA also satisfied the SOC 2 security and confidentiality criteria. We built one set of controls and validated them against two frameworks.

The order of operations matters

You build a HIPAA-compliant analytics portal in a specific sequence. Reversing the order multiplies the work.

  1. Database-level security first. RLS policies, encrypted connections, separate audit schema. Nothing after this step works if the database leaks data.

  2. Authentication and identity. SSO integration, JWT token generation, session management. Every subsequent layer depends on knowing who the user is.

  3. Authorization and RBAC. Role hierarchy, attribute resolution, permission matrix. This layer connects identity to data scope.

  4. Application middleware. Token embedding for Metabase, session parameter injection for PostgreSQL, export interception for audit logging.

  5. Dashboards and charts. Metabase configuration, collection permissions, chart design. This step goes last because it is the only layer users see. The previous four layers make it safe.

If you build the dashboards first, you build them twice. The first time you show every user every row. Then you retrofit security and rebuild the dashboards around the new data scopes.

What breaks in production

Every healthcare analytics deployment hits the same failure modes. Knowing them in advance saves you the late-night pages.

Connection pool exhaustion. A dashboard with twelve charts opens twelve database connections. Fifty concurrent users open six hundred connections. PostgreSQL defaults to one hundred. You configure PgBouncer or increase max_connections before launch. You test with production-level concurrency, not with a single user on staging.

Token expiry during long sessions. A physician keeps a dashboard open for an eight-hour shift. The JWT token expires at hour two. The next chart load fails with a cryptic error. You implement silent token refresh or set token lifetimes long enough for clinical workflows.

RLS policy performance. A policy that calls current_setting() on every row scan adds overhead. On a table with ten million rows, a full scan with RLS can degrade from milliseconds to seconds. You test query plans with EXPLAIN ANALYZE under RLS. You add indexes that align with the policy filter columns.

Audit log volume. A busy analytics portal generates millions of audit events per month. You size the audit database for a year of retention. You implement log rotation and archival. You test that the compliance reporting interface runs in under sixty seconds against six months of data.

Metabase version upgrades. A Metabase patch release changes the embedding token format. Your SSO integration breaks. You pin Metabase versions, test upgrades in staging, and monitor the changelog for breaking changes to the embedding API.

What the regulations do not tell you

HIPAA tells you what to protect. It does not tell you how. The regulations specify outcomes: prevent unauthorized access, maintain audit records, ensure data integrity. The implementation choices belong to you.

PostgreSQL RLS is not mentioned in HIPAA. Neither is Metabase. Neither is PgBouncer. You choose the tools. The auditor verifies the outcomes.

This freedom creates a trap. Without concrete implementation guidance, teams default to what the tools provide out of the box. Metabase provides TLS and authentication. Teams ship with those two items checked and assume the rest follows. It does not.

The gap between “we deployed Metabase” and “we deployed a HIPAA-compliant analytics portal” is the five items on the list you build yourself. Each one take weeks. Together they take months. The Crossroads Health deployment took the full time because we built every item before a single provider logged in.

Starting points

If you run an analytics platform that handles PHI, start with the audit trail. Run this query on your current system: can you produce a complete access log for a specific user for the last six months? If the answer is no, nothing else you build matters.

Then check your row-level security. Open a database console. Run SELECT * FROM patient_records. If it returns rows, your application-level filtering is the only gate. It will fail.

Then read How to Embed a Metabase Dashboard for the integration layer. Read What Is Embedded Analytics? for the architectural context. Read the Crossroads Health case study for the full production implementation.

The work is not glamorous. It does not ship features. It keeps your company out of breach notification letters and civil monetary penalties. In healthcare analytics, that is the feature.