fix: add rate limiting and feature flag guard to isSurveyResponsePresentAction (ENG-942)

- Apply IP rate limit (10/min) to isSurveyResponsePresentAction to prevent
  email-enumeration oracle attacks against the single-response-per-email feature
- Guard the action behind a server-side isSingleResponsePerEmailEnabled check so
  response presence cannot be probed on surveys where the feature is disabled
- Apply IP rate limit (10/min) to validateSurveyPinAction to prevent brute-force
  PIN guessing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matti Nannt
2026-05-21 10:45:21 +02:00
parent c834587c8d
commit 37c6439c76
2 changed files with 20 additions and 0 deletions
@@ -30,6 +30,16 @@ export const rateLimitConfigs = {
allowedPerInterval: 10,
namespace: "action:send-link-survey-email",
}, // 10 per hour
isSurveyResponsePresent: {
interval: 60,
allowedPerInterval: 10,
namespace: "action:survey-response-present",
}, // 10 per minute — prevents email-enumeration oracle
validateSurveyPin: {
interval: 60,
allowedPerInterval: 10,
namespace: "action:validate-survey-pin",
}, // 10 per minute — prevents brute-force PIN guessing
licenseRecheck: { interval: 60, allowedPerInterval: 5, namespace: "action:license-recheck" }, // 5 per minute
},
+10
View File
@@ -37,6 +37,8 @@ const ZValidateSurveyPinAction = z.object({
export const validateSurveyPinAction = actionClient
.inputSchema(ZValidateSurveyPinAction)
.action(async ({ parsedInput }) => {
await applyIPRateLimit(rateLimitConfigs.actions.validateSurveyPin);
// Get survey data which includes pin information
const survey = await getSurveyWithMetadata(parsedInput.surveyId);
if (!survey) {
@@ -62,5 +64,13 @@ const ZIsSurveyResponsePresentAction = z.object({
export const isSurveyResponsePresentAction = actionClient
.inputSchema(ZIsSurveyResponsePresentAction)
.action(async ({ parsedInput }) => {
await applyIPRateLimit(rateLimitConfigs.actions.isSurveyResponsePresent);
const survey = await getSurveyWithMetadata(parsedInput.surveyId);
if (!survey.isSingleResponsePerEmailEnabled) {
throw new InvalidInputError("SINGLE_RESPONSE_PER_EMAIL_NOT_ENABLED");
}
return await isSurveyResponsePresent(parsedInput.surveyId, parsedInput.email)();
});