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
- 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
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
- 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
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
- No recaptcha config found
- IMAP password is required
- SMTP password is required
- Workspace DB not found - self-host users using cloud don't h
- CouchDB username not set
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/db23e6f476f09d42.
Report an issue: GitHub.