{"record":{"id":"e7064a3c8b3673d4","repo":"signalapp/Signal-Server","slug":"invalid-captcha-site-key","errorCode":null,"errorMessage":"invalid captcha site-key","messagePattern":"invalid captcha site-key","errorType":"validation","errorClass":"InvalidCaptchaArgumentException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/captcha/CaptchaChecker.java","lineNumber":105,"sourceCode":"      throw new InvalidCaptchaArgumentException(\"invalid captcha scheme\");\n    }\n\n    final Action parsedAction = Action.parse(action)\n        .orElseThrow(() -> {\n          Metrics.counter(INVALID_ACTION_COUNTER_NAME).increment();\n          return new InvalidCaptchaArgumentException(\"invalid captcha action\");\n        });\n\n    if (!parsedAction.equals(expectedAction)) {\n      Metrics.counter(INVALID_ACTION_COUNTER_NAME, \"action\", action).increment();\n      throw new InvalidCaptchaArgumentException(\"invalid captcha action\");\n    }\n\n    final Set<String> allowedSiteKeys = client.validSiteKeys(parsedAction);\n    if (!allowedSiteKeys.contains(siteKey)) {\n      logger.debug(\"invalid site-key {}, action={}\", siteKey, action);\n      Metrics.counter(INVALID_SITEKEY_COUNTER_NAME, \"action\", action).increment();\n      throw new InvalidCaptchaArgumentException(\"invalid captcha site-key\");\n    }\n\n    final AssessmentResult result = client.verify(maybeAci, siteKey, parsedAction, token, ip, userAgent);\n    Metrics.counter(ASSESSMENTS_COUNTER_NAME,\n            \"action\", action,\n            \"score\", result.getScoreString(),\n            \"provider\", provider)\n        .increment();\n    return result;\n  }\n}\n","sourceCodeStart":87,"sourceCodeEnd":117,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/captcha/CaptchaChecker.java#L87-L117","documentation":"Each captcha client advertises the set of valid site keys per action. verify() checks that the siteKey supplied with the request is in client.validSiteKeys(parsedAction); if not, it logs the rejection, increments the invalid-sitekey metric, and throws InvalidCaptchaArgumentException('invalid captcha site-key').","triggerScenarios":"A request supplies a captcha token and siteKey whose site key is not registered for that action — e.g. the client embeds a site key from a different environment, or the site key was rotated/removed server-side.","commonSituations":"Staging client talking to production server (or vice versa) with mismatched reCAPTCHA site keys, site keys changed in the captcha admin console but clients not updated, or a typo/placeholder site key shipped in a build.","solutions":["Update the client to use the site key configured for this server and action.","Verify the site key is present in the server's captcha configuration (validSiteKeys for the action).","Rotate consistently: when the site key changes in the captcha console, redeploy clients and server config together.","Check for copy/paste or build-flavor errors placing the wrong site key in the client bundle."],"exampleFix":"// before\nString siteKey = \"6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI\"; // test key\n// after\nString siteKey = config.productionRecaptchaSiteKey; // key registered for this action","handlingStrategy":"validation","validationCode":"if (!siteKey || siteKey !== config.recaptchaSiteKey) throw new Error('site key not configured for this environment');","typeGuard":"const isValidSiteKey = (k) => typeof k === 'string' && /^[A-Za-z0-9_-]{20,}$/.test(k);","tryCatchPattern":"try { await call(captcha, siteKey); } catch (e) { if (e.message.includes('invalid captcha site-key')) { siteKey = await fetchCurrentSiteKey(); } throw e; }","preventionTips":["Inject site key from environment config, never hardcode","Sync site-key rotations across server and clients","Separate test vs production site keys per build flavor"],"tags":["captcha","configuration","validation"],"backgroundTag":"invalid-config-value","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}