{"record":{"id":"45550d68e61728ee","repo":"koala73/worldmonitor","slug":"webhook-url-must-use-https","errorCode":null,"errorMessage":"Webhook URL must use HTTPS","messagePattern":"Webhook URL must use HTTPS","errorType":"validation","errorClass":"Error","httpStatus":400,"severity":"warning","filePath":"api/_notification-webhook-ssrf.ts","lineNumber":239,"sourceCode":"async function defaultResolveHostname(hostname: string): Promise<string[]> {\n  const records = await Promise.all([\n    resolveDnsJson(hostname, 'A'),\n    resolveDnsJson(hostname, 'AAAA'),\n  ]);\n  return records.flat();\n}\n\n/**\n * Fail fast at registration when the webhook hostname currently resolves to a\n * private or reserved address. Delivery repeats this check (and pins its\n * connection) because DNS can change after registration.\n */\nexport async function assertNotificationWebhookRegistrationUrlSafe(\n  rawUrl: string,\n  resolveHostname: ResolveHostname = defaultResolveHostname,\n): Promise<void> {\n  const staticError = blockedNotificationWebhookUrlReason(rawUrl);\n  if (staticError) throw new Error(staticError);\n\n  const hostname = new URL(rawUrl).hostname.toLowerCase();\n  if (isIpLiteral(hostname)) return;\n  let resolvedAddresses: string[];\n  try {\n    resolvedAddresses = await resolveHostname(hostname);\n  } catch (error) {\n    const message = error instanceof Error ? error.message : String(error);\n    throw new Error(`Webhook URL DNS resolution failed: ${message}`);\n  }\n  if (!resolvedAddresses.length) throw new Error('Webhook URL DNS resolution returned no addresses');\n  if (resolvedAddresses.some(isBlockedNotificationResolvedAddress)) {\n    throw new Error('Webhook URL must not point to a private/local address');\n  }\n}\n","sourceCodeStart":221,"sourceCodeEnd":255,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/api/_notification-webhook-ssrf.ts#L221-L255","documentation":"createApiKey() gates on getCurrentClerkUser() (src/services/clerk.ts:833), which returns the active Clerk session user or null. If no Clerk user is present, key creation stops before any plaintext key is minted and before any Convex call. This is a deliberate UX guard, not a network or backend failure.","triggerScenarios":"Invoking createApiKey(name) while signed out or after the Clerk session expired; calling it during app boot before Clerk finishes loading (race); Clerk publishable key missing so Clerk never yields a user; user signed out in another tab sharing the session.","commonSituations":"API-key settings UI rendered from cached state after logout; dev server started without Clerk env vars; silent token refresh failure leaving getCurrentClerkUser() null while the UI still shows controls.","solutions":["Ensure the user is signed in and Clerk has fully loaded before enabling the create-key action","Check ClerkProvider is mounted with a valid publishable key and the session has not expired","Re-sign-in and retry; if it persists, inspect getCurrentClerkUser() in devtools","Disable the create button when the auth state reports no user so the call is never made"],"exampleFix":"// before\nawait createApiKey(name); // throws 'Sign in to create an API key.' when signed out\n\n// after\nconst user = getCurrentClerkUser();\nif (!user?.id) {\n  openSignIn();\n  return;\n}\nawait createApiKey(name);","handlingStrategy":"validation","validationCode":"const user = getCurrentClerkUser();\nif (!user?.id) { openSignIn(); return; }\nawait createApiKey(name);","typeGuard":"const hasActiveClerkUser = (u: { id: string } | null | undefined): u is { id: string } =>\n  typeof u?.id === 'string' && u.id.length > 0;","tryCatchPattern":"try {\n  await createApiKey(name);\n} catch (e) {\n  if (e instanceof Error && e.message === 'Sign in to create an API key.') openSignIn();\n  else toast(e instanceof Error ? e.message : String(e));\n}","preventionTips":["Drive the create-key UI from live auth state, not cached UI state","Disable key-management actions while Clerk is still loading","Sign-in expiry is the top cause: prompt re-auth instead of letting the call throw"],"tags":["clerk","auth","api-keys","precondition"],"backgroundTag":"user-not-authenticated","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}