Skip to content

[codex] Add password-protected profile sharing#499

Open
elkimek wants to merge 7 commits into
mainfrom
codex/encrypted-profile-share
Open

[codex] Add password-protected profile sharing#499
elkimek wants to merge 7 commits into
mainfrom
codex/encrypted-profile-share

Conversation

@elkimek
Copy link
Copy Markdown
Owner

@elkimek elkimek commented Jun 2, 2026

Summary

  • Add password-protected Share Profile links with client-side encryption, /api/share storage, local dev-server support, deep-link import, active-link listing, and Stop sharing.
  • Surface Share Profile from the desktop header, mobile profile menu, and Settings -> Data with clearer consent copy and icon copy controls.
  • Reuse a single-profile export builder for JSON export and sharing while excluding wearable connection credentials.
  • Complete the report modal/PDF overhaul: richer practitioner overview, editable overview text, fuller profile header data, improved preview/print UX, cleaner lab category tables, and supplement dosage output.
  • Update changelog, version, tests, and developer docs for the new sharing/storage boundary.

Why

Users needed a simple way to share one profile without accounts or manual file handling. The implementation keeps the password out of the link and off the server, stores only encrypted envelopes, and lets the creating browser stop active links.

Validation

  • node --check js/profile-share.js
  • node --check api/share.js
  • node --check dev-server.js
  • node tests/test-profile-share.js
  • npm test
  • git diff --check

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
get-based Ready Ready Preview, Comment Jun 3, 2026 5:27am

@elkimek elkimek marked this pull request as ready for review June 2, 2026 14:01
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 2, 2026

Greptile Summary

This PR adds password-protected profile sharing (client-side AES-256-GCM + PBKDF2) with a Vercel Blob storage backend, surfaces share controls from the header, client list, and Settings → Data, and completes a report modal overhaul with AI practitioner summaries. The cryptographic design is sound — password never touches the server, min-iteration enforcement is consistent between production and dev, and the decompression bomb guard is implemented correctly.

  • Share link flow: buildClientExportObject (refactored from exportClientJSON) produces a wearable-credential-free export, which is gzip+AES-GCM encrypted in the browser and stored as a private blob; recipients decompress with a streaming size cap; creators hold a manageToken in localStorage to delete their links.
  • Rate limiting: slot-based blob-PUT scheme (20 slots/hour/IP-hash) avoids the TOCTOU read-increment-write race of earlier designs.
  • Window exposure: createProfileShare and five other private symbols are added to window but have no cross-module callers; createProfileShare in particular provides a complete silent exfiltration path (encrypt + upload with caller-controlled password, no dialog) that should be removed from the Object.assign(window, …) block.

Confidence Score: 3/5

Safe to merge after removing the unnecessary window exposures; the core share, encrypt, and rate-limit logic is correct.

createProfileShare on window gives any in-page script a one-call, no-dialog path to upload an encrypted copy of the user's entire health profile. The function is self-contained inside the module's event delegation and has no legitimate cross-module callers via window.*, so the exposure is purely additive risk. Removing the five unneeded symbols from Object.assign(window, …) closes this gap. Everything else in the share and report changes — the crypto parameters, the streaming decompression guard, the slot-based rate limiter, the wearable-credential exclusion — is implemented correctly.

js/profile-share.js — the Object.assign(window, …) block at the bottom of the file.

Security Review

  • Silent profile exfiltration via window.createProfileShare (js/profile-share.js:723-734): createProfileShare is exposed on window but is never called via window.* from any other file. Any in-page script (XSS, rogue ad) can call it with a known password to silently upload the entire health profile to /api/share and receive back a retrievable share URL — no browser dialog, no user interaction. Confirmed: no cross-module callers exist for this symbol or for deleteProfileShareEnvelope, encryptProfileShareEnvelope, decryptProfileShareEnvelope, parseProfileShareIdFromLocation, or buildProfileShareUrl.

Important Files Changed

Filename Overview
js/profile-share.js New module implementing client-side AES-256-GCM encryption, share link creation/deletion, and import modal; decompression bomb guard is present; createProfileShare and several other private functions are unnecessarily exposed on window, creating a silent exfiltration path.
api/share.js New Vercel edge function for encrypted envelope storage; enforces minimum KDF iterations, TTL cap, and a slot-based IP rate limit; handleDelete's del call is unguarded (previously flagged).
dev-server.js Adds in-memory /api/share mirror for local development with equivalent validation; streaming size limit and envelope checks match the production handler.
js/export.js Refactors exportClientJSON into a reusable buildClientExportObject helper for JSON export and sharing; buildClientExportObject is correctly NOT exposed on window; adds AI practitioner summary generation for reports.
js/client-list.js Adds "Share Profile" menu item per-client and _clShare handler that opens the share modal; correctly delegates via window.openProfileShareModal.
js/settings.js Adds "Share Profile" button in Settings → Data that opens the share modal; correct pattern.
tests/test-profile-share.js New test file covering crypto round-trip, ID generation, URL building/parsing, and structural checks; does not use window.createProfileShare from tests.

Sequence Diagram

sequenceDiagram
    participant Sharer as Sharer Browser
    participant PSM as profile-share.js
    participant EXP as export.js
    participant API as /api/share (Vercel Blob)
    participant Recipient as Recipient Browser

    Note over Sharer: User clicks Share Profile
    Sharer->>PSM: openProfileShareModal()
    PSM-->>Sharer: Render create form (password pre-filled)

    Note over Sharer: User submits form
    Sharer->>PSM: handleCreateSubmit(form)
    PSM->>EXP: buildClientExportObject(profileId)
    EXP-->>PSM: exportObj (no wearable credentials)
    PSM->>PSM: encryptProfileShareEnvelope(exportObj, password)
    Note over PSM: PBKDF2 (600k iter) → AES-256-GCM
    PSM->>API: "POST /api/share {id, envelope, manageTokenHash}"
    API->>API: enforcePostRateLimit (slot-based, IP-hashed)
    API->>API: normalizeEnvelope (validates min iterations, TTL, size)
    API->>API: Blob PUT (allowOverwrite:false, private)
    API-->>PSM: "201 {id, expiresAt}"
    PSM-->>Sharer: Show link + password (send separately!)
    PSM->>PSM: saveShareRecord(localStorage) — stores manageToken

    Note over Recipient: Opens share URL #share/{id}
    Recipient->>PSM: parseProfileShareIdFromLocation()
    PSM-->>Recipient: openSharedProfileImportModal(id)
    Recipient->>API: "GET /api/share?id={id}"
    API-->>Recipient: "{envelope}"
    Recipient->>PSM: decryptProfileShareEnvelope(envelope, password)
    Note over Recipient: PBKDF2 + AES-GCM decrypt + gzip decompress (capped)
    PSM->>EXP: importDataJSON(file)
    EXP-->>Recipient: Profile imported as new profile

    Note over Sharer: User clicks Stop sharing
    Sharer->>PSM: handleDeleteShare()
    PSM->>API: "DELETE /api/share?id={id} {manageToken}"
    API->>API: "sha256(manageToken) == record.manageTokenHash"
    API->>API: Blob del (currently unguarded)
    API-->>PSM: "200 {ok:true}"
    PSM->>PSM: removeShareRecord(localStorage)
Loading

Reviews (6): Last reviewed commit: "Simplify profile sharing changelog" | Re-trigger Greptile

Comment thread api/share.js
Comment thread api/share.js
Comment thread api/share.js Outdated
Comment thread js/export.js
Comment thread api/share.js Outdated
Comment thread js/profile-share.js
Comment thread api/share.js
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant