Trust pages are full of positive claims. Negative claims are harder to weasel out of, and every one below is verifiable in the open-source code that handles your session audio, transcripts, and notes.
backend/app/jobs/hard_purge_cron.py in the public repo.api.openai.com, no api.anthropic.com, no generativelanguage.googleapis.com. All inference is Vertex AI under our BAA. The weekly pentest probes for any deviation and triggers a release hold on a finding.FORCE ROW LEVEL SECURITY, which fails closed if the per-request user context is unset. (Emergency SRE access to the underlying database exists, audited and rare — that operational reality, and the controls around it, will be detailed in our next disclosure update.)Each commitment above is independently verifiable in the public repo. If you find one that isn't true, tell us — it's a release-blocking finding.
Most health-tech vendors won't tell you their supply chain. We do. Below is every external service that touches Pablo, with the BAA status backing each one. This list is generated from docs/compliance/baa-manifest.yaml in the public repo — the same file the weekly pentest cross-references on every outbound egress. If a vendor handles PHI and is not listed here with a signed BAA, that's a release-blocking finding.
| Vendor | What they touch | BAA | Sub-processors |
|---|---|---|---|
| Google Cloud Platform | All PHI storage, compute, inference (Vertex AI Gemini + Claude), authentication, telemetry | Yes — Google Cloud BAA umbrella | Their list |
| AssemblyAI | Session audio transcription (managed deployments only — self-hosted OSS uses local Whisper and never touches AssemblyAI) | Yes — direct BAA | Their list |
| Plunk | Transactional email (compliance reminders, operator notifications). No PHI in payloads — payloads carry only the user's email and the item type (e.g. "your CAQH is due"), never patient identifiers or clinical content. | Not required — Path A discipline (no PHI passes through) | Their DPA |
| Stripe | Subscription billing only — your billing email and a Stripe customer ID. No clinical content, no patient identifiers. | Not required — no PHI sent | Their list |
| SimplePractice (inbound only) | Read-only EHR import on your behalf. Outbound flow is auth tokens only; inbound flow returns your own patient and calendar data into your Pablo account. | Not required — you hold the SimplePractice BAA directly under § 164.504(e); Pablo's BAA with you covers downstream handling | — |
| Sessions Health (inbound only) | Same read-only EHR import pattern as SimplePractice | Not required — same § 164.504(e) pattern | — |
| GitHub (ghcr.io) | Container registry and source hosting. Code only — no runtime PHI flow. | Not required | — |
| PyPI | Build-time Python package index. No runtime traffic, no PHI. | Not required | — |
The end-to-end Business Associate chain: Pablo Health, LLC → LLL Solutions (Subcontractor BA, executed 2026-05-08) → Google Cloud + AssemblyAI. The same baa-manifest.yaml is what an OCR reviewer would ask for under § 164.308(b)(3) and § 164.314(a).
Encryption posture, tenant isolation, MFA, AI processing under Vertex AI, audit logging, backup and retention, and the egress allowlist all live on the implementation page. Read the technical implementation →
If we find something, you'll see it.
For serious issues: we fix it, ship the patch, and notify operators privately first — then publish full detail within 30 days. Findings that don't meaningfully help an attacker publish same-day. If a vulnerability ever leads to actual unauthorized PHI access — a breach under 45 CFR §164.410 — we notify on confirmation, not on the statutory clock, and inside whichever deadline (federal or state) applies to the affected jurisdiction.
That's deliberately stricter than what HIPAA requires. The Breach Notification Rule (45 CFR §164.400–414) only triggers on actual unauthorized access to PHI — not on the vulnerabilities that could have led there. We publish those anyway. For Critical findings against deployments that can't patch in an hour, a short embargo (typically 7–30 days depending on severity) protects users while patches roll out, consistent with how Django, Rails, and the Linux kernel coordinate security releases.
This isn't a checkbox. We built our own pentest tool — a custom LLM agent (open source, in our repo) that drives the scanners, cross-references our BAA manifest on every external egress, maps each finding to the specific HIPAA Security Rule control it touches, and tracks closure week-over-week.
This is our primary technical evaluation today. As Pablo grows, the program will grow with it.
A week-by-week authorized penetration test runs against the deployed Pablo app. The methodology is mapped to the HHS Office for Civil Rights' expectations for a qualified pentester of a HIPAA-regulated system:
Every finding is tagged with the specific CFR section it affects (a CFR mapping) so operators and auditors can see which control the finding speaks to. Examples: an audit-logging gap maps to §164.312(b); a cross-tenant access issue maps to §164.308(a)(4) and §164.312(a)(1).
The entire pentest methodology ships in the repo as an agent skill, with parallel implementations for Claude Code (.claude/skills/pentest/SKILL.md) and the Gemini CLI (.gemini/skills/pentest/SKILL.md). The same playbook also runs unattended in a hardened container as a scheduled Cloud Run Job — that's what produces the runs summarized below. Two different LLMs running the same playbook surface different things, so we run both deliberately.
If you self-host Pablo, you can run the same owner-authorized test against your own deployment with /pentest — no black box, no "trust us."
Each run produces a dated report, and we publish a redacted summary of it once its findings are resolved — the track record below. The test runs weekly and we usually patch right away, so in practice that's most weeks; but we commit to publishing per run rather than to a calendar, so a quiet stretch means nothing shipped, not that we stopped looking. Open findings are listed by severity and the control they touch, with actionable detail held until they're closed. Through pre-launch development (synthetic data only) the full reports lived in the public repo; now that Pablo handles real patient data, the full reports detail the live attack surface, so they go to customers and auditors on request at security@pablo.health. The methodology and tooling stay fully public.
| Date | Full report | Findings |
|---|---|---|
| 2026-05-31 | On request | 0 CRITICAL · 0 HIGH (actual) · 1 MEDIUM (real) · 4 findings mis-attributed · 1 INFO carry-over. Dynamic controls clean: cross-tenant IDOR block confirmed (404), audit trail verified, no 500s this run. Executive summary “97 HIGH” is a scanner count artifact (96 of those are individual semgrep hits on SQLAlchemy text() calls in migration DDL and test fixtures — not distinct issues); actual distinct findings: 6. PABLO-006 (starlette 1.1.0, PYSEC-2026-161 — DoS via unbounded header processing) — real finding; starlette was bumped from 0.52.1 to 1.1.0 since last week but advisory still fires; tracked as PABLO-bb9 to find the upstream fix and patch. PABLO-001 (JWT unverified decode) — mis-attributed; _unverified_issuer() reads only the iss claim for multi-issuer routing, downstream verifier always re-checks the full signature; not an auth bypass. Semgrep false positive; suppressed and a planned code simplification is tracked as PABLO-8ad. PABLO-002 (96 SQLAlchemy text() hits) — mis-attributed count inflation; hits are migration DDL, tenant provisioning DDL, and integration test fixtures, none reachable from API requests; ORM parameterization intact. Scanner tuned: tests_integration/, tests/, and provisioning files excluded with rationale; §12 of the report template now carries a static-analysis exclusion table for human reviewers. PABLO-003 (logger credential disclosure) — mis-attributed; logged values are PyJWT exception messages and integer LLM token counts, not auth credential values. # nosemgrep annotations added to affected lines. PABLO-004 (dynamic urllib) — mis-attributed; all three usages have strict domain allowlists or target operator-controlled IAM-gated endpoints; not user-input-reachable. # nosemgrep annotations added. PABLO-005 (pip CVEs) — INFO carry-over, accepted risk in VULNERABILITY_EXCEPTIONS.md (build-time only, no PHI reachability). |
| 2026-05-24 | On request | 0 CRITICAL · 0 HIGH · 1 MEDIUM · 1 LOW · 2 INFO. Trend: improving — last week's HIGH outage (PABLO-OUTAGE-500, missing app.routes.subscription module on freshly-provisioned tenants) is closed-this-run; current backend image deploys cleanly and the closed-loop audit probe wrote and read back a row successfully. PABLO-DEP-STARLETTE (starlette 0.52.1, PYSEC-2026-161 / GHSA-86qp-5c8j-p5mr — Host header not validated during URL reconstruction) — Pablo authenticates via JWT bearer tokens, not URL-derived session keys, so PHI nexus is theoretical; pin bump tracked as PABLO-tuo (P0, blocks v1.0.0). PABLO-DEP-WS (ws 8.20.0, GHSA-58qx-3vcg-4xpx uninitialized memory disclosure) — confirmed dev-only in frontend/package-lock.json, not packaged into the runtime container; bump tracked as PABLO-6om (P0, blocks v1.0.0). PABLO-EXC-PIP — INFO carry-over, already accepted risk in VULNERABILITY_EXCEPTIONS.md (no upstream fix, build-time only, no PHI reachability). PABLO-INFO-AUDIT — INFO only; practice.audit_logs showed 0 rows over 24h reflecting natural platform idleness, and the same run's closed-loop POST /api/patients probe successfully wrote and read back an audit row, confirming the pipeline is alive. |
| 2026-05-17 | On request | 0 CRITICAL · 2 HIGH (both resolved or mis-attributed) · 1 LOW · 2 INFO. PABLO-API-500 (POST /api/patients 500 on freshly-provisioned tenants) closed by PR #203 — missing tenant database configuration. PABLO-AUDIT-01 (audit_logs reported empty for 24h) mis-attributed — query-side issue, no audit-log outage. PABLO-TLS-01 (CBC ciphers offered on TLS endpoint) — will be fixed next run. |
| 2026-05-11 | On request | 0 CRITICAL · 2 HIGH (HIPAA impact: None — both are transitive dependency CVEs with no PHI request path; patching anyway in the next dependency sweep) · 1 INFO carry-over · 2 prior findings resolved-this-run. PABLO-004 (urllib3 2.6.3, CVE-2026-44432 + CVE-2026-44431) — transitive only; the deployed application's HTTP stack is httpx (which doesn't sit on urllib3) and backend/app/ contains zero direct import urllib3. Upgrading to 2.7.0+. PABLO-005 (mako 1.3.11, CVE-2026-44307) — transitive via alembic; the CVE specifically requires Windows backslash resolution and Pablo runs on Linux Cloud Run, and there is zero import mako in application code. Upgrading to 1.3.12+. PABLO-001 (pip 26.0.1) — INFO carry-over, already accepted risk in VULNERABILITY_EXCEPTIONS.md (no upstream fix, build-time only). PABLO-002 (05-10 audit pipeline) closed by PR #145 — root cause was deployed-schema drift, not the AuditService component named in the 05-10 report; this run's closed-loop check confirms the audit pipeline is operational. PABLO-003 (missing exception log, from the 05-11 01:49Z degraded run) closed by PRs #147 + #148 — VULNERABILITY_EXCEPTIONS.md is now baked into the pentest image and read correctly. |
| 2026-05-10 | On request | 1 HIGH (mis-attributed — PABLO-002 reported HTTP 500 on POST /api/patients and blamed AuditService; the real cause was deployed-schema drift on the patients table. Fixed: migrations re-applied and the prod DB reconciled to head) · 1 MEDIUM (PABLO-001 carry-over — pip 26.0.1 CVE-2026-3219 / -6357, accepted risk: no upstream fix exists, build-time only, no PHI reachability — documented in VULNERABILITY_EXCEPTIONS.md) |
| 2026-05-03 | On request | 1 HIGH (false positive — PABLO-002 flagged audit_logs empty over 24h, but the same run's closed-loop test successfully wrote and read back an audit row, so the pipeline is alive; this was an idle-deployment signal, not a real § 164.312(b) failure — see the report's Erratum block) · 1 MEDIUM (PABLO-001 vulnerable deps — closed same day in PR #108) · narrator-prompt fix to prevent recurrence in PR #111 |
| 2026-04-30 | On request | 1 MEDIUM (PABLO-001 vulnerable deps — closed 2026-05-03 by PR #108) · 1 LOW from 04-23 closed |
| 2026-04-23 | On request | 1 LOW open · 5 of 6 from 04-16 closed |
| 2026-04-16 | On request | 6 findings (2H/3M/1L) · 5 closed by 04-23 · 1 LOW carried forward |
Methodology: HIPAA Pentest Guide (what OCR expects, what we do, CFR mapping, severity rubric, rules of engagement).
Pablo is AGPL — you can (and many will) run it on your own infrastructure. Your security posture is your own, but we'll do everything we can to make it defensible:
If you find something in Pablo — the hosted product, the OSS codebase, or a deployment you're running — email security@pablo.health. We read it.
What helps:
We'll acknowledge within 72 hours, work a fix with you, and publish per the policy above. We don't currently run a paid bug bounty — but we do credit every valid report by name in our published findings summaries and release notes.