Date: 2026-09-06
Scope: Auth, XSS, injection, secrets, PHI, LLM proxy, consent grants
Live: cadence-healthcare.vercel.app
This is an educational / research prototype. It is not a medical device, not HIPAA-certified, and must not store real patient records.
Repos stay public until deliberately made private. Honest demo threat model — not a bank-grade guarantee. No real PHI — demo / fictional data only.
| Area | Risk | Notes |
|---|---|---|
| Authentication | N/A (by design) | No login on the public UI |
| Authorization (local Python) | Medium (by design) | ConsentStore gates clinician read paths; not a hospital IAM |
| Authorization (public UI) | Browser-local only | Share codes live in localStorage on one device |
| XSS | Low (hardened) | Companion renderer now HTML-encodes user/model text |
| Injection (SQL) | Low | Parameterized SQLite; no string-built queries |
| Secrets in git | None found | .env, data/, *.db, *.pkl gitignored |
| Public LLM proxy | Accepted residual | Chat text is sent to Groq; env var is named OPENAI_API_KEY |
| PHI | High if misused | Do not enter real identifiers. Demo data only |
| Pickle vectors | Accepted residual | data/vectors.pkl is local-only; never load untrusted pickles |
CORS on /api/chat |
Hardened | Same-origin or explicit allowlist (Cadence Vercel + localhost); no * |
| Rate limits | Best-effort | In-memory per-IP on /api/chat; resets per serverless isolate |
Overall (public Vercel demo): Low-to-medium residual risk if used as a demo. High if anyone pastes real clinical data into the live chat.
Findings
- Public Cadence UI has no accounts, cookies, or JWT.
- Clinician “share code” (
CAD-XXXXXX) is generated in the browser and stored undercadence_grants_v1inlocalStorage. - The local Python path (
ConsentStore) uses UUID grants, expiry, revoke, and an audit table. That store is not wired to Vercel.
Verdict: Do not claim HIPAA, NextAuth, or hospital SSO. Browser grants are a UX demo of consent scopes, not a security boundary.
Findings
- Companion messages, timeline rows, and share metadata are injected via
innerHTML. escapeHtmlpreviously replaced characters with themselves (no encoding). That is now fixed (& < > " '→ entities).- Tailwind is loaded from
cdn.tailwindcss.com(supply-chain residual for a demo).
Hardening applied
- HTML entity encoding before any
innerHTMLof user or model text.
Findings
ConsentStoreandDeepMemoryuse parameterized?placeholders.- No
os.system, no shell-out, no eval of model output.
Verdict: Low on the local Python path.
Findings
web/api/chat.jsis a Vercel serverless function. It readsprocess.env.OPENAI_API_KEY(name chosen in the Vercel project) and callshttps://api.groq.com/openai/v1/chat/completionswithllama-3.3-70b-versatile.- The key never ships in the repo.
- Request bodies include the last 8 chat turns. Those turns go to Groq. They are not stored in a Cadence database on Vercel.
- Missing key / Groq errors return
{ fallback: true }; the UI switches to canned demo replies and stays there for the tab session.
Residual
- Env var name
OPENAI_API_KEYis misleading (it holds a Groq key). Renaming would require a Vercel change; left as-is. - In-memory per-IP rate limit on
/api/chat(~20/min) plus Groq’s free tier. - Missing key returns a demo fallback reply (HTTP 200); errors are scrubbed for key-shaped tokens.
- Prompt injection: a user can try to override the “never diagnose” system prompt. Model output is untrusted text.
Local Python (run_patient.py, run_clinician.py)
- SQLite + pickle under
data/(gitignored). Intended to stay on one machine with Ollama.
Public UI
- Timeline and grants: this browser only.
- Companion messages: Groq when live mode succeeds.
Never put real names, MRNs, or identifiable health data in either path. Reiterated 2026-09-06: no real PHI in the live demo or local agents.
VALID_SCOPES = {profile, timeline, visit_brief, note_source}
- Unknown scopes are dropped.
is_allowedchecks grant existence,activestatus, clinician id, and required scope.- Expiry is evaluated on read and written back as
expired. - Deny/access/create/revoke are appended to
consent_audit.
This is a teaching implementation, not a substitute for an EHR access-control system.
| Package | Role |
|---|---|
ollama |
Local CLI agents only |
sentence-transformers + numpy |
Local semantic memory |
(removed) faiss-cpu |
Never imported |
CI installs pytest + numpy only and injects a dummy embedder so GitHub Actions does not download torch.
Do not run npm audit fix --force here — there is no production Node graph, only Playwright as a devDependency.
- Public chat traffic to Groq.
OPENAI_API_KEYnaming.- Best-effort rate limits (not a distributed WAF).
localStoragegrants (any script on the origin can read them).- Pickle of embedding vectors (local file; never untrusted).
- CDN Tailwind / Google Fonts.
- Educational medical language that a visitor might over-trust — footer + system prompts still say “not a substitute for care.”