From 25f65326d4de52368c8161ba3d980c8084df9ce3 Mon Sep 17 00:00:00 2001 From: Kisa Date: Sun, 21 Jun 2026 14:00:54 -0400 Subject: [PATCH] security: rotate leaked Signal API key, remove it from frontend + source The backend master SIGNAL_API_KEY had leaked via two paths: - baked into the public Vite bundle via VITE_SIGNAL_API_KEY (Vercel) - hardcoded as a fallback in scripts/signal_e2e_test.py The key has been rotated on Railway (old value now returns 401) and the VITE_SIGNAL_API_KEY var removed from Vercel. This commit removes the two code paths so the master key can never be re-baked or re-committed: - signal-ui/src/lib/api.js: drop the X-API-Key fallback entirely; the frontend now authenticates only with the signed-in user's Clerk token - scripts/signal_e2e_test.py: read SIGNAL_API_KEY from env only; exit if unset Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/signal_e2e_test.py | 6 ++++-- signal-ui/src/lib/api.js | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/scripts/signal_e2e_test.py b/scripts/signal_e2e_test.py index 6247436..ef57225 100644 --- a/scripts/signal_e2e_test.py +++ b/scripts/signal_e2e_test.py @@ -8,7 +8,7 @@ Usage: python3 scripts/signal_e2e_test.py python3 scripts/signal_e2e_test.py --url https://signal-api-production-91c2.up.railway.app -Authentication: API key from env var SIGNAL_API_KEY or hardcoded fallback. +Authentication: API key read from the SIGNAL_API_KEY environment variable (required). """ import argparse @@ -27,7 +27,9 @@ REPO_ROOT = Path(__file__).parent.parent TEST_DATA = REPO_ROOT / "test-data" DEFAULT_URL = "https://signal-api-production-91c2.up.railway.app" -API_KEY = os.getenv("SIGNAL_API_KEY", "4HKamqzn78Md-L-xj2OjZESVwZh2Otzc6iFRyqVZl7U") +API_KEY = os.getenv("SIGNAL_API_KEY", "") +if not API_KEY: + sys.exit("SIGNAL_API_KEY is not set. Export the current backend key before running this test.") PASS = "PASS" FAIL = "FAIL" diff --git a/signal-ui/src/lib/api.js b/signal-ui/src/lib/api.js index e3bf3d2..ad4985b 100644 --- a/signal-ui/src/lib/api.js +++ b/signal-ui/src/lib/api.js @@ -5,7 +5,9 @@ const BACKEND_URL = import.meta.env.VITE_SIGNAL_BACKEND_URL || "https://signal-api-production-91c2.up.railway.app"; -const API_KEY = import.meta.env.VITE_SIGNAL_API_KEY || ""; +// The frontend authenticates only with the signed-in user's Clerk token. +// It must never carry a service API key — that key would be baked into the +// public bundle and grant full backend access to anyone who views the source. /** * Upload a CSV file to the backend scoring endpoint. @@ -17,7 +19,7 @@ export async function uploadToBackend(file, token = null) { formData.append("file", file); const authHeader = token ? { "Authorization": `Bearer ${token}` } - : API_KEY ? { "X-API-Key": API_KEY } : {}; + : {}; try { const resp = await fetch(`${BACKEND_URL}/api/upload`, { method: "POST", @@ -42,7 +44,7 @@ export async function uploadToBackend(file, token = null) { export async function exportFromBackend(records, batchId = null, token = null) { const authHeader = token ? { "Authorization": `Bearer ${token}` } - : API_KEY ? { "X-API-Key": API_KEY } : {}; + : {}; try { const resp = await fetch(`${BACKEND_URL}/api/export`, { method: "POST", @@ -106,7 +108,7 @@ export function apiRecordToLocal(r) { export async function confirmVisit(payload, token = null) { const authHeader = token ? { "Authorization": `Bearer ${token}` } - : API_KEY ? { "X-API-Key": API_KEY } : {}; + : {}; try { const res = await fetch(`${BACKEND_URL}/api/confirm-visit`, { method: "POST", @@ -142,7 +144,7 @@ export async function confirmVisit(payload, token = null) { export async function updateDocStatus(patientId, docType, status, statusDate = null, expiryDate = null, token = null) { const authHeader = token ? { "Authorization": `Bearer ${token}` } - : API_KEY ? { "X-API-Key": API_KEY } : {}; + : {}; try { const resp = await fetch(`${BACKEND_URL}/api/doc-status`, { method: "PUT",