Budibase/budibase · error · BadRequestError

No secret key provided

Error message

No secret key provided

What it means

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.

Source

Thrown at packages/worker/src/api/controllers/global/configs.ts:273

      if (!existingConfig) {
        continue
      }
      if (c.clientSecret === PASSWORD_REPLACEMENT) {
        c.clientSecret = existingConfig.clientSecret
      }
    }
  }
}

export async function processRecaptchaConfig(
  config: RecaptchaInnerConfig,
  existingConfig?: RecaptchaInnerConfig
) {
  if (!(await pro.features.isRecaptchaEnabled())) {
    throw new ForbiddenError("License does not allow use of recaptcha")
  }
  if (config.secretKey === PASSWORD_REPLACEMENT && !existingConfig) {
    throw new BadRequestError("No secret key provided")
  }
  if (config.secretKey === PASSWORD_REPLACEMENT && existingConfig) {
    config.secretKey = existingConfig.secretKey
  }
}

function prepareTranslationsConfig(
  ctx: UserCtx,
  config?: TranslationsConfigInner
): TranslationsConfigInner {
  const defaultLocale = config?.defaultLocale || "en"
  const locales: TranslationsConfigInner["locales"] = {}
  const now = new Date().toISOString()
  const updatedBy = ctx.user?._id

  Object.entries(config?.locales || {}).forEach(([locale, localeConfig]) => {
    locales[locale] = {
      label: localeConfig?.label,

View on GitHub (pinned to a81a902e9a)

Solutions

  1. Send the real reCAPTCHA secret key in secretKey on first-time setup
  2. Only use the masked placeholder when updating an existing config whose secret should be preserved
  3. Ensure the existingConfig is located/loaded so the placeholder resolves to the stored secret
  4. Clear the masked value in the form so the user is forced to type a fresh secret

Example fix

// before
{ "type": "recaptcha", "config": { "secretKey": "__REPLACEMENT__" } } // first save
// after
{ "type": "recaptcha", "config": { "secretKey": "<real-recaptcha-secret>" } }
Defensive patterns

Strategy: validation

Validate before calling

if (cfg.secretKey === PASSWORD_REPLACEMENT && !existingConfig) {
  throw new Error("Provide a real reCAPTCHA secret key on first setup")
}

Type guard

function hasSecretKey(c: { secretKey?: string }): c is { secretKey: string } {
  return typeof c.secretKey === "string" && c.secretKey.length > 0 && c.secretKey !== PASSWORD_REPLACEMENT
}

Try / catch

try {
  await configApi.save(recaptchaConfig)
} catch (err) {
  if (err.status === 400 && err.message.includes("No secret key")) {
    // clear the masked field and require user input
  } else { throw err }
}

Prevention

When it happens

Trigger: Saving a reCAPTCHA config with secretKey === PASSWORD_REPLACEMENT while no existing reCAPTCHA config is passed (first-time creation).

Common situations: 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.

Related errors


AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29). Data as JSON: /api/errors/db23e6f476f09d42. Report an issue: GitHub.