Skip to content

Instantly share code, notes, and snippets.

@allan-gar2x
Last active April 13, 2026 05:25
Show Gist options
  • Select an option

  • Save allan-gar2x/ac614a959e4fff8fda8e3dc06d3e5b5b to your computer and use it in GitHub Desktop.

Select an option

Save allan-gar2x/ac614a959e4fff8fda8e3dc06d3e5b5b to your computer and use it in GitHub Desktop.
HYDRA-013: WebSocket URL exposure + emergency-login architecture (#7704)

HYDRA-013 — WebSocket URL Exposure + Emergency-Login Architecture

Issue: #7704
Severity: LOW (P3) · Category: Information Disclosure
Date: 2026-04-13
Related PR (merged): #7708 — acute risks mitigated
Closed by: #7700 (WebSocket URL portion)


Issue Summary

Part 1 — WebSocket URL Exposure (Closed by #7700)

GET /api/settings/serverConfig (unauthenticated) returns websocketUrl to any caller with no token:

{ "websocketUrl": "wss://20rx8hpb43.execute-api.us-east-2.amazonaws.com/$default" }

Risk: LOW — WebSocket handlers require JWT auth for data operations. Blast radius is connection-level probing only.

Resolution: Fully resolved as a side effect of the #7700 serverConfig auth split. No independent work required. See HYDRA-008 gist for implementation steps.

Steps from #7700 that close this:

  • Step 1serverConfig.ts flipped to auth: truewebsocketUrl no longer accessible without a token
  • Step 6WebsocketConfigProvider moved inside UserProvider → URL fetch only happens post-auth
  • Step 7 — No code changes needed in WebsocketConfigProvider.tsx; undefined URL already handled gracefully

Step 0 — Verify websocketUrl Exposure BEFORE Starting

Run on staging to confirm the vulnerability before touching any code. Save the output for the PR comment.

BASE_URL="https://app.staging.bike4mind.com"

echo "=== HYDRA-013 BEFORE: websocketUrl unauthenticated exposure ==="
echo "Target: $BASE_URL"
echo ""

STATUS=$(curl -s -o /tmp/sc_7704_before.json -w "%{http_code}" "$BASE_URL/api/settings/serverConfig")
echo "HTTP Status: $STATUS"
echo ""

WS_URL=$(cat /tmp/sc_7704_before.json | jq -r '.websocketUrl // empty')
if [ -n "$WS_URL" ]; then
  echo "EXPOSED   websocketUrl = $WS_URL"
  echo ""
  echo "Vulnerability confirmed — websocketUrl returned without authentication."
else
  echo "NOT FOUND websocketUrl — may already be patched."
fi

Expected output (confirms vulnerability):

HTTP Status: 200
EXPOSED   websocketUrl = wss://20rx8hpb43.execute-api.us-east-2.amazonaws.com/$default

Step 9 — Confirm websocketUrl is Protected AFTER Deploying PR

Run against the PR preview environment. TOKEN is a valid JWT — grab it from browser DevTools → Application → Local Storage → accessToken.

BASE_URL="https://app.prN.preview.bike4mind.com"
TOKEN="<paste_your_jwt_here>"

echo "=== HYDRA-013 AFTER: Verifying websocketUrl is behind auth ==="
echo "Target: $BASE_URL"
echo ""

# --- Check 1: No token — should NOT get websocketUrl ---
echo "--- [1] GET /api/settings/serverConfig (no token) ---"
STATUS=$(curl -s -o /tmp/sc_7704_noauth.json -w "%{http_code}" "$BASE_URL/api/settings/serverConfig")
echo "HTTP Status: $STATUS"
if [ "$STATUS" = "401" ]; then
  echo "PASS — 401 returned, websocketUrl not accessible without token"
else
  WS=$(cat /tmp/sc_7704_noauth.json | jq -r '.websocketUrl // empty')
  [ -n "$WS" ] && echo "FAIL — websocketUrl still exposed: $WS" || echo "Unexpected status $STATUS but websocketUrl not present"
fi
echo ""

# --- Check 2: serverConfigPublic must NOT contain websocketUrl ---
echo "--- [2] GET /api/settings/serverConfigPublic (no token) ---"
curl -s -o /tmp/sc_7704_pub.json "$BASE_URL/api/settings/serverConfigPublic"
WS=$(cat /tmp/sc_7704_pub.json | jq -r '.websocketUrl // empty')
echo "Fields returned: $(cat /tmp/sc_7704_pub.json | jq -r 'keys | join(", ")')"
[ -n "$WS" ] && echo "FAIL — websocketUrl still in public endpoint: $WS" || echo "PASS — websocketUrl not in serverConfigPublic"
echo ""

# --- Check 3: With token — websocketUrl must still be accessible (WebSocket connect still works) ---
echo "--- [3] GET /api/settings/serverConfig (with token) ---"
STATUS=$(curl -s -o /tmp/sc_7704_auth.json -w "%{http_code}" \
  -H "Authorization: Bearer $TOKEN" \
  "$BASE_URL/api/settings/serverConfig")
echo "HTTP Status: $STATUS"
WS=$(cat /tmp/sc_7704_auth.json | jq -r '.websocketUrl // empty')
[ -n "$WS" ] && echo "PASS — websocketUrl accessible with valid token: $WS" || echo "FAIL — websocketUrl missing with token, WebSocket connect will break"

Expected output (confirms fix):

--- [1] GET /api/settings/serverConfig (no token) ---
HTTP Status: 401
PASS — 401 returned, websocketUrl not accessible without token

--- [2] GET /api/settings/serverConfigPublic (no token) ---
Fields returned: apiUrl, defaultTheme
PASS — websocketUrl not in serverConfigPublic

--- [3] GET /api/settings/serverConfig (with token) ---
HTTP Status: 200
PASS — websocketUrl accessible with valid token: wss://20rx8hpb43.execute-api.us-east-2.amazonaws.com/$default

Post both BEFORE (Step 0) and AFTER (Step 9) outputs as a PR comment before requesting review.
PR description: Closes #7700, closes #7704


Part 2 — Emergency-Login Architectural Question (Open)

From erikbethke's comment on #7704 (2026-04-08):

"The remaining architectural question is whether emergency-login should exist at all, or be gated behind isDevelopment(). Lowering priority since the acute risks are now mitigated."


Current State of emergency-login

File: apps/client/pages/api/admin/emergency-login.ts

Publicly accessible (baseApi({ auth: false })), but hardened by PR #7708:

const handler = baseApi({ auth: false })
  .use(checkBlockedIP())                                     // HYDRA-005
  .use(rateLimit({ limit: 5, windowMs: 10 * 60 * 1000 }))  // HYDRA-006
  .post(asyncHandler(async (req, res) => {
    const { username, password } = EmergencyLoginSchema.parse(req.body); // HYDRA-001/002
    const escapedUsername = escapeRegex(username);           // HYDRA-001
    // bcrypt compare + isAdmin + isBanned checks + audit log
  }));

What PR #7708 Already Fixed

Finding Fix
HYDRA-001 — Regex injection / ReDoS escapeRegex() + ^...$ anchors
HYDRA-002 — NoSQL operator injection EmergencyLoginSchema.parse() via Zod
HYDRA-003 — Username enumeration via logging Minimal logging; uniform "Invalid credentials" for all failures
HYDRA-005 — Blocked IP bypass checkBlockedIP() middleware added
HYDRA-006 — Zero rate limiting rateLimit({ limit: 5, windowMs: 10 * 60 * 1000 })

Verify Current Emergency-Login State on Staging

BASE_URL="https://app.staging.bike4mind.com"

echo "=== Emergency-Login: Confirming current hardening on staging ==="
echo ""

# --- Check 1: Endpoint is reachable (returns 401 for bad credentials) ---
echo "--- [1] Endpoint reachability ---"
STATUS=$(curl -s -o /tmp/el_reach.json -w "%{http_code}" -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"nonexistent_hydra_test","password":"wrongpassword"}' \
  "$BASE_URL/api/admin/emergency-login")
echo "HTTP Status: $STATUS"
[ "$STATUS" = "401" ] && echo "Reachable — returns 401 for bad credentials (expected)" || echo "Status: $STATUS$(cat /tmp/el_reach.json | jq .)"
echo ""

# --- Check 2: Zod validation blocks NoSQL injection ---
echo "--- [2] NoSQL injection (expect 422) ---"
STATUS=$(curl -s -o /tmp/el_nosql.json -w "%{http_code}" -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":{"$gt":""},"password":{"$gt":""}}' \
  "$BASE_URL/api/admin/emergency-login")
echo "HTTP Status: $STATUS"
[ "$STATUS" = "422" ] && echo "PASS — Zod validation blocks NoSQL operators (422)" || echo "FAIL — Expected 422, got $STATUS"
echo ""

# --- Check 3: Rate limiting kicks in after 5 attempts ---
echo "--- [3] Rate limiting (expect 429 by attempt 6) ---"
for i in 1 2 3 4 5 6; do
  STATUS=$(curl -s -o /tmp/el_rate_$i.json -w "%{http_code}" -X POST \
    -H "Content-Type: application/json" \
    -d "{\"username\":\"ratelimit_probe_$i\",\"password\":\"wrongpassword\"}" \
    "$BASE_URL/api/admin/emergency-login")
  echo "  Attempt $i: HTTP $STATUS"
  if [ "$STATUS" = "429" ]; then
    echo "  PASS — Rate limit enforced at attempt $i"
    break
  fi
done

Architectural Assessment

Why isDevelopment() Gate Is Wrong

Gating behind isDevelopment() defeats the purpose. emergency-login is a break-glass mechanism for when production OAuth is unavailable (Google OAuth misconfigured, provider outage). If it only works in development it is useless when you actually need it.

Remaining Risks (Despite Current Hardening)

  1. Permanently discoverable. /api/admin/emergency-login is a predictable path. Route enumeration finds it and reveals a password-based admin auth path exists separate from OAuth.
  2. Slow brute-force is still possible. 5 attempts / 10 min slows but does not stop a patient attacker with a known admin username and a credential dump.
  3. Parallel auth path outside the primary security model. Normal auth runs through Google OAuth — token rotation, session management, and Google's abuse detection all apply. Emergency login bypasses all of that.

Recommendation: SST Feature Flag (Option A)

Add an SST secret EMERGENCY_LOGIN_ENABLED that defaults to false. The endpoint returns 404 (not 403 — don't reveal it exists) unless explicitly enabled. Enable during an incident, use it, disable immediately.

Code change in emergency-login.ts (top of handler, before middleware):

// Gate: endpoint is closed by default. Enable via SST secret during incidents only.
if (process.env.EMERGENCY_LOGIN_ENABLED !== 'true') {
  return res.status(404).end();
}

SST commands to toggle:

# Enable on staging during an incident
npx sst secret set EMERGENCY_LOGIN_ENABLED "true" --stage dev

# Enable on production during an incident
npx sst secret set EMERGENCY_LOGIN_ENABLED "true" --stage production

# Disable immediately after use
npx sst secret set EMERGENCY_LOGIN_ENABLED "false" --stage production
npx sst secret set EMERGENCY_LOGIN_ENABLED "false" --stage dev

# Confirm current value
npx sst secret list --stage production

Verify the flag works after implementing:

BASE_URL="https://app.prN.preview.bike4mind.com"

echo "=== Emergency-Login: Confirming feature flag ==="

# With flag OFF (default) — should return 404
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"test"}' \
  "$BASE_URL/api/admin/emergency-login")
echo "Flag OFF — HTTP Status: $STATUS"
[ "$STATUS" = "404" ] && echo "PASS — endpoint hidden (404)" || echo "FAIL — Expected 404, got $STATUS"

Expected output:

Flag OFF — HTTP Status: 404
PASS — endpoint hidden (404)

Option B — IP Allowlist Middleware (Stronger Security)

Restrict access to known admin IP ranges or a VPN CIDR block. Returns 404 (not 403) for unknown IPs — does not reveal the endpoint exists.

Code change in emergency-login.ts (add before existing middleware):

const ADMIN_IP_ALLOWLIST = (process.env.EMERGENCY_LOGIN_ALLOWED_IPS || '')
  .split(',')
  .map(ip => ip.trim())
  .filter(Boolean);

function adminIPAllowlist() {
  return (req: Request, res: Response, next: NextFunction) => {
    if (ADMIN_IP_ALLOWLIST.length === 0) {
      return res.status(404).end(); // No allowlist configured = closed by default
    }
    const clientIP = req.ip || req.connection.remoteAddress || '';
    if (!ADMIN_IP_ALLOWLIST.includes(clientIP)) {
      return res.status(404).end();
    }
    next();
  };
}

const handler = baseApi({ auth: false })
  .use(adminIPAllowlist())   // ← add this first
  .use(checkBlockedIP())
  .use(rateLimit({ limit: 5, windowMs: 10 * 60 * 1000 }))
  // ...rest unchanged

SST secret to configure allowed IPs:

# Set allowed IPs (comma-separated — use your VPN exit node or office IP)
npx sst secret set EMERGENCY_LOGIN_ALLOWED_IPS "203.0.113.10,203.0.113.11" --stage production
npx sst secret set EMERGENCY_LOGIN_ALLOWED_IPS "203.0.113.10,203.0.113.11" --stage dev

# Confirm
npx sst secret list --stage production

Verify IP allowlist works after implementing:

BASE_URL="https://app.prN.preview.bike4mind.com"

echo "=== Emergency-Login: Confirming IP allowlist ==="

# From an IP NOT in the allowlist — should return 404
STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
  -H "Content-Type: application/json" \
  -d '{"username":"test","password":"test"}' \
  "$BASE_URL/api/admin/emergency-login")
echo "From non-allowlisted IP — HTTP Status: $STATUS"
[ "$STATUS" = "404" ] && echo "PASS — endpoint hidden from non-allowlisted IP" || echo "FAIL — Expected 404, got $STATUS"

When to pick Option B over Option A: If the team has a static VPN exit node or a known office IP, Option B is strictly stronger — it eliminates discoverability entirely for the internet at large, even when the endpoint is "enabled." Option A is simpler and has no IP dependency.


Comparison

Approach Discoverable Production capable Requires deploy to toggle
isDevelopment() gate Hidden in prod No — useless in prod Yes
SST feature flag — Option A (recommended) Hidden by default Yes No
IP allowlist — Option B (strongest) Hidden from non-admin IPs Yes No (SST secret)
Keep as-is Always discoverable Yes

Summary

Sub-issue Status Action
WebSocket URL exposed unauthenticated Closed by #7700 Run verification scripts above, post in PR comment
Emergency-login acute risks (HYDRA-001/002/003/005/006) Fixed in PR #7708 None
Emergency-login architectural question Open Implement SST feature flag — track in a dedicated issue

The emergency-login architectural question should be tracked in its own issue rather than as a comment on #7704 (a WebSocket URL issue).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment