Skip to content

Data Flow & Threat Model

Companion to COMPLIANCE.md. Written so its sections can be pasted into a DPIA or a hospital security-review questionnaire.

1. Data-flow diagram

flowchart LR
    subgraph MACHINE["Your machine / your network (trust boundary A)"]
        SRC[("Source data\nCSV / Parquet / DB")] --> ENG["Compute engine\npolars · pandas · spark"]
        ENG --> PROF["Stage 1\nProfile + PHI detect"]
        PROF -->|"opt-in scrub_phi=True"| SCRUB["PHIScrubber\n(value-level)"]
        SCRUB --> S2["Stage 2\nSchema mapping"]
        PROF --> S2
        S2 --> S3["Stage 3\nConcept mapping"]
        KL[("Knowledge layer\nBM25s / FAISS index\n(local files)")] --> S3
        EMB["Embedding\nSapBERT (local HF)"] --> S3
        RR["Reranker\ncross-encoder (local HF)"] --> S3
        S3 --> S4["Stage 4\nETL generation"] --> OUT[("Mapped output\nOMOP / FHIR / ...")]
        S4 --> S5["Stage 5\nValidation (GX)"]
    end

    subgraph REMOTE["Remote LLM vendor (trust boundary B — OPT-IN ONLY)"]
        LLM["openai / azure_openai\nanthropic / bedrock"]
    end

    S2 -. "column names + sample values\n(only if llm.provider is remote)" .-> LLM
    S3 -. "code + description + candidate\nconcept names (verification prompt)" .-> LLM

    HF["HuggingFace Hub\n(model weights, inbound only)"] -. "download weights\n(never sends data)" .-> EMB

Boundary A → B is the only outbound clinical-data path, and it exists only when a remote llm.provider is configured. offline=True removes it structurally; portiere doctor --assert-no-egress proves its absence.

2. Exact egress payloads (BYO-LLM path)

When (and only when) llm.provider ∈ {openai, azure_openai, anthropic, bedrock}:

Stage Payload sent Source in code
Stage 3 concept verification source code string, its description, candidate concept names/IDs, domain hint local/llm_verifier.py (CONCEPT_VERIFICATION_PROMPT, DISAMBIGUATION_PROMPT)
Stage 2 schema mapping (LLM-assisted path) column names, inferred types, sample values stages/stage2_schema.py

Mitigations, in order: don't configure a remote LLM (offline=True enforces — this is the guarantee); or use a local ollama provider. scrub_phi=True is defence-in-depth for profile-derived payloads only: in v0.4.0 the Stage-2 schema-mapping path reads sample values from the raw dataframe and is NOT covered by the scrubber (wiring planned for v0.4.x — see phi-scrubbing.md, "Scope in v0.4.0").

3. Assets

Asset Sensitivity
Source clinical data (may contain PHI) highest
Profile artifacts (examples, top values — PHI fragments) high
Mapped outputs (OMOP/FHIR tables) high
Mapping decisions / review files medium (contain source terms)
Athena vocabulary files licensed content (UMLS/AMA terms), not PHI
Model weights cache public models, not sensitive

4. Actors & threats (STRIDE-abbreviated)

Threat Vector Control
Information disclosure — accidental egress operator enables a remote LLM without realizing prompts carry data portiere doctor posture report; offline=True hard-fail; this document
Information disclosure — PHI in artifacts profile HTML/CSV, review files shared internally carry example values scrub_phi=True (v0.4.0, opt-in); artifact access control (operator)
Information disclosure — model-download side channel HF download reveals which models you fetch, never data pre-download + air-gap (models download, then offline=True, --network none)
Tampering — poisoned vocabulary/index operator loads an untrusted Athena export or index fetch vocabularies only from athena.ohdsi.org under your UMLS licence; index files are local artifacts of that input
Tampering — malicious model weights HF supply chain models are pinned by name; pin revisions + local mirror for hardened deployments (operator control)
Spoofing / network exposure Review UI reachable by others binds 127.0.0.1 by default; --host 0.0.0.0 is explicit + warned
Repudiation — who approved a mapping review decisions lack actor identity in v0.4.0 reviewed files are plain JSON under your VCS/audit regime (operator); richer audit is a Cloud/v1.0 concern
Elevation Portiere runs with the operator's privileges; no daemon, no setuid run as a least-privilege user (operator)

5. Residual risk statement

With offline=True, models pre-cached, and network egress blocked at the OS level, Portiere's remaining attack surface is that of any local Python data tool processing files the operator already controls: local file permissions and the integrity of installed packages. There is no server component, no telemetry, and no credential store.

6. Reviewer quick-check

pip install portiere-health[polars]
portiere doctor --assert-no-egress && echo "no egress possible"
grep -rn "requests\|httpx\|urllib" src/portiere/stages/  # inspect for yourself