{"record":{"id":"db23e6f476f09d42","repo":"Budibase/budibase","slug":"no-secret-key-provided","errorCode":null,"errorMessage":"No secret key provided","messagePattern":"No secret key provided","errorType":"validation","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"packages/worker/src/api/controllers/global/configs.ts","lineNumber":273,"sourceCode":"      if (!existingConfig) {\n        continue\n      }\n      if (c.clientSecret === PASSWORD_REPLACEMENT) {\n        c.clientSecret = existingConfig.clientSecret\n      }\n    }\n  }\n}\n\nexport async function processRecaptchaConfig(\n  config: RecaptchaInnerConfig,\n  existingConfig?: RecaptchaInnerConfig\n) {\n  if (!(await pro.features.isRecaptchaEnabled())) {\n    throw new ForbiddenError(\"License does not allow use of recaptcha\")\n  }\n  if (config.secretKey === PASSWORD_REPLACEMENT && !existingConfig) {\n    throw new BadRequestError(\"No secret key provided\")\n  }\n  if (config.secretKey === PASSWORD_REPLACEMENT && existingConfig) {\n    config.secretKey = existingConfig.secretKey\n  }\n}\n\nfunction prepareTranslationsConfig(\n  ctx: UserCtx,\n  config?: TranslationsConfigInner\n): TranslationsConfigInner {\n  const defaultLocale = config?.defaultLocale || \"en\"\n  const locales: TranslationsConfigInner[\"locales\"] = {}\n  const now = new Date().toISOString()\n  const updatedBy = ctx.user?._id\n\n  Object.entries(config?.locales || {}).forEach(([locale, localeConfig]) => {\n    locales[locale] = {\n      label: localeConfig?.label,","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/worker/src/api/controllers/global/configs.ts#L255-L291","documentation":"Recaptcha secret keys are masked with a PASSWORD_REPLACEMENT placeholder on read. processRecaptchaConfig only accepts the placeholder when an existing config with a stored secret exists; on first-time setup there is nothing to fall back to, so BadRequestError('No secret key provided') is thrown.","triggerScenarios":"Saving a reCAPTCHA config with secretKey === PASSWORD_REPLACEMENT while no existing reCAPTCHA config is passed (first-time creation).","commonSituations":"A frontend form pre-populated with the masked value submitted unchanged on initial setup; API clients echoing back the redacted secret from a GET response on creation.","solutions":["Send the real reCAPTCHA secret key in secretKey on first-time setup","Only use the masked placeholder when updating an existing config whose secret should be preserved","Ensure the existingConfig is located/loaded so the placeholder resolves to the stored secret","Clear the masked value in the form so the user is forced to type a fresh secret"],"exampleFix":"// before\n{ \"type\": \"recaptcha\", \"config\": { \"secretKey\": \"__REPLACEMENT__\" } } // first save\n// after\n{ \"type\": \"recaptcha\", \"config\": { \"secretKey\": \"<real-recaptcha-secret>\" } }","handlingStrategy":"validation","validationCode":"if (cfg.secretKey === PASSWORD_REPLACEMENT && !existingConfig) {\n  throw new Error(\"Provide a real reCAPTCHA secret key on first setup\")\n}","typeGuard":"function hasSecretKey(c: { secretKey?: string }): c is { secretKey: string } {\n  return typeof c.secretKey === \"string\" && c.secretKey.length > 0 && c.secretKey !== PASSWORD_REPLACEMENT\n}","tryCatchPattern":"try {\n  await configApi.save(recaptchaConfig)\n} catch (err) {\n  if (err.status === 400 && err.message.includes(\"No secret key\")) {\n    // clear the masked field and require user input\n  } else { throw err }\n}","preventionTips":["Never submit masked placeholder values on initial creation","Pre-populate the secret field empty (not masked) for first-time setup","Only send PASSWORD_REPLACEMENT when a stored secret exists","Require the secretKey input in forms when creating a new recaptcha config"],"tags":["recaptcha","configuration","bad-request","secrets"],"backgroundTag":"missing-required-secret","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}