From 56331632fd4535ab46ba75937bfc308f525a2fb6 Mon Sep 17 00:00:00 2001 From: Christian Nennemann Date: Wed, 1 Apr 2026 07:56:19 +0200 Subject: [PATCH] feat(fapp): add security model + profile_url for verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/specs/fapp-security.md: - Full threat model for patient protection - 3-level verification roadmap (transparency → endorsements → registry) - UI warning mockups - Technical implementation plan - Honest assessment of limitations SlotAnnounce changes: - Added profile_url field for therapist verification - New with_profile() constructor - profile_url included in signature docs/specs/fapp-protocol.md: - Added Security & Anti-Fraud section - Link to full security spec --- crates/quicprochat-p2p/src/fapp.rs | 49 +++++++ docs/specs/fapp-protocol.md | 37 ++++- docs/specs/fapp-security.md | 211 +++++++++++++++++++++++++++++ 3 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 docs/specs/fapp-security.md diff --git a/crates/quicprochat-p2p/src/fapp.rs b/crates/quicprochat-p2p/src/fapp.rs index 96c9fc4..e69630c 100644 --- a/crates/quicprochat-p2p/src/fapp.rs +++ b/crates/quicprochat-p2p/src/fapp.rs @@ -94,6 +94,12 @@ pub struct TimeSlot { /// /// Propagates through the mesh like [`MeshAnnounce`](crate::announce::MeshAnnounce), /// cached by relay nodes with `CAP_FAPP_RELAY`. +/// +/// # Security Note +/// +/// Patients should verify therapists before booking. The `profile_url` field +/// allows cross-referencing with official sources (Jameda, KBV, practice website). +/// See `docs/specs/fapp-security.md` for the full security model. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SlotAnnounce { /// Unique announcement ID. @@ -112,6 +118,10 @@ pub struct SlotAnnounce { pub slots: Vec, /// SHA-256 of the therapist's Approbation number. pub approbation_hash: [u8; 32], + /// Optional URL to therapist's public profile for verification. + /// Examples: Jameda profile, KBV listing, practice website. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub profile_url: Option, /// Monotonically increasing per therapist (dedup/supersede). pub sequence: u64, /// Time-to-live in hours (default 168 = 7 days). @@ -148,6 +158,34 @@ impl SlotAnnounce { slots: Vec, approbation_hash: [u8; 32], sequence: u64, + ) -> Self { + Self::with_profile( + identity, + fachrichtung, + modalitaet, + kostentraeger, + location_hint, + slots, + approbation_hash, + sequence, + None, + ) + } + + /// Create and sign a new slot announcement with optional profile URL. + /// + /// The `profile_url` allows patients to verify the therapist's identity + /// against official sources (Jameda, KBV, practice website). + pub fn with_profile( + identity: &MeshIdentity, + fachrichtung: Vec, + modalitaet: Vec, + kostentraeger: Vec, + location_hint: String, + slots: Vec, + approbation_hash: [u8; 32], + sequence: u64, + profile_url: Option, ) -> Self { let pk = identity.public_key(); let therapist_address = compute_address(&pk); @@ -170,6 +208,7 @@ impl SlotAnnounce { location_hint, slots, approbation_hash, + profile_url, sequence, ttl_hours: DEFAULT_TTL_HOURS, timestamp, @@ -219,6 +258,16 @@ impl SlotAnnounce { buf.push(0xFF); buf.extend_from_slice(&self.approbation_hash); + + // profile_url is signed to prevent tampering. + if let Some(ref url) = self.profile_url { + buf.push(0x01); // present marker + buf.extend_from_slice(url.as_bytes()); + } else { + buf.push(0x00); // absent marker + } + buf.push(0xFF); + buf.extend_from_slice(&self.sequence.to_le_bytes()); buf.extend_from_slice(&self.ttl_hours.to_le_bytes()); buf.extend_from_slice(&self.timestamp.to_le_bytes()); diff --git a/docs/specs/fapp-protocol.md b/docs/specs/fapp-protocol.md index 81667e3..b40d67a 100644 --- a/docs/specs/fapp-protocol.md +++ b/docs/specs/fapp-protocol.md @@ -136,14 +136,39 @@ Therapist confirms or rejects a reservation. - `duration_minutes: u16` — Duration (typically 50 or 25 minutes) - `slot_type: SlotType` — Type of appointment -## Anti-Spam +## Security & Anti-Fraud -1. **Approbation hash binding.** The `approbation_hash` field contains SHA-256 of the therapist's Approbation number. While mesh nodes cannot verify this against a registry, it creates accountability — a therapist's identity is tied to a real credential. +> **See [fapp-security.md](fapp-security.md) for the full security model.** + +### Patient Protection + +Patients are vulnerable. FAPP must protect against fraudulent "therapists": + +| Threat | Mitigation | +|--------|------------| +| Fake Therapist | `profile_url` for cross-verification, UI warnings | +| Impersonation | Ed25519 signatures, endorsement system (planned) | +| Data Harvesting | Anonymous queries, no patient identity in protocol | +| Financial Fraud | "Never pay upfront" warnings, reputation (planned) | + +### Verification Levels + +| Level | Mechanism | Trust | +|-------|-----------|-------| +| 0 | None — only mesh signature | Low | +| 1 | Endorsement by trusted relay | Medium | +| 2 | Registry verification (KBV) | High | + +**Current implementation:** Level 0 with `profile_url` for transparency. + +### Anti-Spam + +1. **Approbation hash binding.** The `approbation_hash` field contains SHA-256 of the therapist's Approbation number. Creates accountability — therapist identity tied to real credential. 2. **Signature verification.** All SlotAnnounces are Ed25519-signed. Relay nodes reject unsigned or invalid announcements. -3. **Rate limiting.** Relay nodes enforce a maximum announcement rate per therapist address (e.g., max 10 SlotAnnounces per hour per therapist_address). -4. **Sequence-based dedup.** Each therapist maintains a monotonic sequence counter. Relay nodes only accept announces with sequence >= last seen for that therapist. -5. **TTL enforcement.** Expired announcements are garbage collected. Default TTL is 7 days. -6. **Hop limit.** SlotAnnounces have a max_hops field (default 8) to prevent infinite propagation. +3. **Rate limiting.** Relay nodes enforce max 10 SlotAnnounces per hour per therapist_address. +4. **Sequence-based dedup.** Monotonic counter; relays only accept sequence >= last seen. +5. **TTL enforcement.** Expired announcements are garbage collected. Default 7 days. +6. **Hop limit.** max_hops field (default 8) prevents infinite propagation. ## Wire Format diff --git a/docs/specs/fapp-security.md b/docs/specs/fapp-security.md new file mode 100644 index 0000000..b424ff4 --- /dev/null +++ b/docs/specs/fapp-security.md @@ -0,0 +1,211 @@ +# FAPP Security Model — Protecting Patients from Fraud + +## Threat Model + +### Who are we protecting? + +**Patients** seeking psychotherapy are in a vulnerable state. They may be: +- Desperate after months of searching +- Unfamiliar with the healthcare system +- Willing to pay out-of-pocket if GKV slots are scarce +- Trusting of anyone who appears professional + +### What are the threats? + +| Threat | Description | Severity | +|--------|-------------|----------| +| **Fake Therapist** | Attacker poses as licensed therapist, collects patient data | CRITICAL | +| **Phishing** | Fake slots lead to malicious contact forms | HIGH | +| **Financial Fraud** | "Therapist" demands upfront payment | HIGH | +| **Data Harvesting** | Collect patient health queries for profiling | MEDIUM | +| **Spam Flooding** | Overwhelm mesh with fake announces | MEDIUM | +| **Impersonation** | Clone a real therapist's identity | CRITICAL | + +## Current Protections (v1) + +| Protection | Mechanism | Weakness | +|------------|-----------|----------| +| Approbation Hash | SHA-256 of credential number | **Cannot be verified** — attacker can invent hash | +| Ed25519 Signature | Proves control of mesh key | Doesn't prove real-world identity | +| Sequence Dedup | Prevents replay | Doesn't prevent new fake announces | +| Rate Limiting | Max announces/hour | Attacker can use multiple keys | + +**Honest assessment:** Current protections prevent spam but **do not prevent fraud**. + +## Proposed Security Enhancements + +### Level 1: Transparency (Low Trust, No Verification) + +**Concept:** Make it easy for patients to verify therapists themselves. + +1. **Therapist Profile URL** + - SlotAnnounce includes optional `profile_url: String` + - Points to therapist's website, Jameda profile, or KV listing + - Patient can cross-check before booking + +2. **Approbation Display** + - Show first 4 digits of Approbation hash in UI + - Patient can ask therapist to confirm during Erstgespräch + - Social verification, not cryptographic + +3. **Warning Labels** + - UI shows "Unverified Therapist" prominently + - Patient must acknowledge risk before reserving + +**Implementation:** ~2 days, no infrastructure changes. + +### Level 2: Web-of-Trust (Medium Trust) + +**Concept:** Trusted nodes vouch for therapists. + +1. **Endorsement Messages** + - Trusted relays (e.g., run by patient advocacy groups) sign endorsements + - `TherapistEndorsement { therapist_address, endorser_signature, reason }` + - Patients can filter by "endorsed by [Patientenberatung]" + +2. **Reputation Scores** + - After appointments, patients can rate (anonymously) + - Aggregate scores propagate through mesh + - New therapists start with "No ratings yet" + +3. **Blocklists** + - Community-maintained blocklists of known fraudsters + - Relay nodes can subscribe and filter + +**Implementation:** ~2 weeks, requires gossip protocol for endorsements. + +### Level 3: Registry Integration (High Trust) + +**Concept:** Verify against official sources. + +1. **KV-Registry Lookup** + - Germany: KBV Arztsuche API (https://www.kbv.de/html/arztsuche.php) + - Therapist provides Lebenslange Arztnummer (LANR) or BSNR + - Gateway node queries registry, signs attestation + +2. **eHBA Integration** (long-term) + - Electronic Health Professional Card + - Therapist proves identity via qualified electronic signature + - Strongest guarantee, but requires card reader + +3. **Chamber Verification** + - Psychotherapeutenkammer publishes member lists + - Automated scraping + attestation (legally gray) + +**Implementation:** 1-2 months, requires trusted gateway infrastructure. + +## Recommended Roadmap + +### Phase 1: Ship with Warnings (Now) + +``` +┌─────────────────────────────────────────────────┐ +│ ⚠️ UNVERIFIED THERAPIST │ +│ │ +│ This therapist has not been verified. │ +│ Before booking: │ +│ • Check their website or Jameda profile │ +│ • Verify Approbation during first contact │ +│ • Never pay upfront without meeting │ +│ │ +│ [I understand the risks] [Cancel] │ +└─────────────────────────────────────────────────┘ +``` + +- Add `profile_url` field to SlotAnnounce +- Prominent warnings in UI +- Educational content about verification + +### Phase 2: Endorsements (Q2 2026) + +- Partner with 2-3 patient advocacy groups +- They run relay nodes with endorsement capability +- "Endorsed by Unabhängige Patientenberatung" badge + +### Phase 3: Registry (Q4 2026) + +- Build KBV gateway (if API access granted) +- Or: manual verification service (humans check credentials) +- Verified badge with expiry + +## Technical Implementation + +### SlotAnnounce v2 + +```rust +pub struct SlotAnnounce { + // ... existing fields ... + + /// Optional URL to therapist's public profile (Jameda, website, KV listing). + pub profile_url: Option, + + /// Optional LANR (Lebenslange Arztnummer) for registry lookup. + pub lanr: Option, + + /// Verification level (0 = none, 1 = endorsed, 2 = registry-verified). + pub verification_level: u8, + + /// Endorsement signatures from trusted nodes. + pub endorsements: Vec, +} + +pub struct Endorsement { + /// Address of the endorsing node. + pub endorser_address: [u8; 16], + /// Ed25519 signature over (therapist_address, timestamp). + pub signature: [u8; 64], + /// Unix timestamp of endorsement. + pub timestamp: u64, + /// Human-readable reason. + pub reason: String, +} +``` + +### Patient-Side Verification Flow + +``` +1. Patient receives SlotAnnounce +2. UI shows verification_level: + - 0: "⚠️ Unverified" (red) + - 1: "✓ Endorsed by [name]" (yellow) + - 2: "✓✓ Registry Verified" (green) +3. Patient can click to see: + - Profile URL + - Endorsement details + - Verification expiry +4. Before SlotReserve, patient confirms risk acknowledgment +``` + +## What We Cannot Prevent + +Even with Level 3 verification: + +1. **Licensed but Unethical Therapist** — Credential is real, behavior is not +2. **Session Quality** — Verification proves license, not competence +3. **Availability Lies** — Therapist might not actually have slots +4. **Price Gouging** — "Selbstzahler" with inflated rates + +**These require reputation systems and patient reviews** — can't be solved cryptographically. + +## Comparison to Existing Systems + +| System | Verification | Privacy | Decentralized | +|--------|--------------|---------|---------------| +| **Doctolib** | KV registry | Low (tracks searches) | No | +| **Jameda** | None (self-reported) | Low | No | +| **KBV Arztsuche** | Official | Medium | No | +| **FAPP v1** | None | High | Yes | +| **FAPP + Level 2** | Endorsements | High | Yes | +| **FAPP + Level 3** | Registry | High | Mostly | + +## Conclusion + +FAPP's strength is **patient privacy**. We should not sacrifice that for centralized verification. + +**Recommended approach:** +1. Ship with strong warnings and profile URLs (transparency) +2. Build endorsement network (web-of-trust) +3. Add optional registry verification for therapists who want it +4. Let patients choose their trust level + +The mesh provides the infrastructure. Trust is a social problem that requires social solutions.