Skip to content

fix(security): phone-verify customer PII endpoints + rate-limit (P0)#208

Merged
arigatoexpress merged 1 commit into
mainfrom
fix/security-pii-hardening
Jun 19, 2026
Merged

fix(security): phone-verify customer PII endpoints + rate-limit (P0)#208
arigatoexpress merged 1 commit into
mainfrom
fix/security-pii-hardening

Conversation

@arigatoexpress

Copy link
Copy Markdown
Owner

P0 security: phone-verify customer PII endpoints

From the holistic live-site review (Mark Willcott). Three customer-facing endpoints exposed PII to anyone who knew or guessed an opaque id — a deal_id/appointment_id is a capability, not a credential.

Fixed

  • /api/v1/customer/deal/{id} + /download/{note_id} (Secure Hub portal): a deal_id alone returned the buyer's name + document list, then minted a 15-minute GCS signed URL to the closing PDF (SSN/DOB). Now requires phone-last-4 verification against the deal's buyer/co-buyer phone before any data or signed URL. Non-existent deal and wrong phone return the same 403 (no existence oracle). Rate-limited 20/min. Also fixed a bare except Exception that was converting HTTPExceptions into 500s.
  • /api/appointments/{id}: returned name/phone/email unauthenticated; now requires phone-last-4 (matching the existing cancel endpoint). Rate-limited 20/min.
  • /api/chat/session/{id}: rate-limited 30/min to blunt enumeration of the anon session ids (customers paste PII into chat). Per-browser session binding tracked as a follow-up.
  • SecureHub.jsx: phone-last-4 verification gate before any PII loads; passes phone to deal + download. a11y label, numeric input, disabled-until-valid.

Verification

  • New tests/test_pii_endpoint_hardening.py (5 tests: helper + endpoint auth + no-oracle).
  • Backend 1098 passed, ruff clean. Frontend build + eslint + 48 vitest green.

No template/pypdf changes. First of the prioritized remediation batches (P0 security → P1 doc/data → P2 ops → P3 frontend UX).

Live-site security review (Mark Willcott). Customer-facing endpoints exposed PII
to anyone who knew/guessed an opaque id — a deal_id/appointment_id is a
capability, not a credential.

- /api/v1/customer/deal/{id} + /download/{note_id}: a deal_id alone returned the
  buyer's name + document list and minted a 15-min signed URL to the closing PDF
  (SSN/DOB) in GCS. Now require phone-last-4 verification against the deal's
  buyer/co-buyer phone BEFORE any data or signed URL; non-existent deal and wrong
  phone return the SAME 403 (no existence oracle). Rate-limited 20/min. Fixed the
  bare 'except Exception' that was converting HTTPExceptions into 500s.
- /api/appointments/{id}: returned name/phone/email unauthenticated; now requires
  phone-last-4 (matching the existing cancel endpoint). Rate-limited 20/min.
- /api/chat/session/{id}: rate-limited 30/min to blunt enumeration of the anon
  session ids (customers sometimes paste PII into chat). Full per-browser session
  binding tracked as a follow-up.
- SecureHub.jsx: phone-last-4 verification gate before any PII loads; passes phone
  to deal + download. a11y label, numeric input, disabled-until-valid.

New regression guard tests/test_pii_endpoint_hardening.py (5 tests). Backend 1098
passed, ruff clean; frontend build + eslint + 48 vitest green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@arigatoexpress arigatoexpress merged commit b0b6672 into main Jun 19, 2026
2 checks passed
@arigatoexpress arigatoexpress deleted the fix/security-pii-hardening branch June 19, 2026 23:01

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc0e05ca83

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread main.py
async def get_secure_hub_deal(deal_id: str):
"""Fetch deal status and documents for the customer portal."""
@limiter.limit("20/minute")
async def get_secure_hub_deal(deal_id: str, request: Request, phone: str = ""):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep phone verification digits out of URLs

Because phone is accepted as a GET query parameter here, every successful Secure Hub verification and document download puts the phone last-4—the factor that gates access to buyer PII and signed documents—into the request URL. In production those URLs are commonly captured by access logs, proxies, and browser/network tooling, so the verifier is leaked anywhere the request URL is recorded; use a POST body/header or issue a short-lived verified session/token instead of carrying the digits in the URL.

Useful? React with 👍 / 👎.

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