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) <noreply@anthropic.com>
This commit is contained in:
Kisa 2026-06-21 14:00:54 -04:00
parent 70c1f28230
commit 25f65326d4
2 changed files with 11 additions and 7 deletions

View file

@ -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"

View file

@ -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",