Skip to content

Mitigate application-level DoS from oversized password payloads on signup#5138

Open
RishabhJain2018 wants to merge 6 commits into
masterfrom
cursor/password-max-length-dos-fix-67ee
Open

Mitigate application-level DoS from oversized password payloads on signup#5138
RishabhJain2018 wants to merge 6 commits into
masterfrom
cursor/password-max-length-dos-fix-67ee

Conversation

@RishabhJain2018

@RishabhJain2018 RishabhJain2018 commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

Addresses the reported signup DoS vector where attackers could submit arbitrarily long passwords to /api/auth/registration/ and consume server resources during validation/hashing.

Changes

Backend

  • Added MaximumLengthValidator (128 characters) to AUTH_PASSWORD_VALIDATORS
  • Introduced CustomRegisterSerializer with max_length on password fields
  • Added SafeObtainExpiringAuthToken with BoundedAuthTokenSerializer on the real login endpoint (/api/auth/login)
  • Added RegistrationIPThrottle (10/hour per IP) on SafeRegisterView
  • Bounded password field length on ChallengeInvitationRegisterSerializer
  • Set REST_FRAMEWORK["NUM_PROXIES"] = 1 in production for trustworthy client IP throttling behind nginx

Frontend

  • Added maxlength / ng-maxlength="128" on password fields across signup, login, password reset/change, and challenge invitation forms

Tests

  • Validator, serializer, throttle, login success, and expired-token refresh coverage added for patch coverage requirements

Notes

  • DATA_UPLOAD_MAX_MEMORY_SIZE remains unchanged at 4 GB.
  • CodeRabbit feedback addressed: gettext-safe help text, preserved AuthTokenSerializer password field options, explicit login post() override, and production NUM_PROXIES configuration.

Slack Thread

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Enforced configurable maximum password length (default 128) across registration, login, password reset/change, and challenge invitations.
    • Added IP-based registration throttling (10/hour per client IP).
    • Introduced a safer expiring-auth-token login endpoint for improved token handling.
  • Bug Fixes
    • Standardized client-side and server-side validation to reliably reject over-limit passwords with consistent error behavior.
  • Tests
    • Expanded unit tests for password-length validation, login/register flows, and registration throttling keying.

Add server-side password max length validation (128 chars), bounded
registration/login serializers, registration IP throttling, frontend
maxlength constraints, and a lower JSON request body size limit.

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds configurable maximum password length enforcement across backend validation, serializers, throttling, URL wiring, frontend form constraints, and tests.

Changes

Password Maximum Length Enforcement

Layer / File(s) Summary
Settings and validator
settings/common.py, settings/prod.py, apps/accounts/validators.py
Adds PASSWORD_MAX_LENGTH, registers MaximumLengthValidator, configures the registration IP throttle rate and registration serializer mapping, and sets the production proxy count.
Serializers, throttle, and URL wiring
apps/accounts/serializers.py, apps/accounts/throttles.py, apps/accounts/views.py, apps/challenges/serializers.py, evalai/urls.py
Adds bounded password fields for account serializers, caps challenge invitation passwords, adds registration IP throttling, and routes login through the safe expiring auth token view.
Frontend maxlength validation
frontend/src/views/web/auth/login.html, frontend/src/views/web/auth/signup.html, frontend/src/views/web/auth/reset-password-confirm.html, frontend/src/views/web/change-password.html, frontend/src/views/web/challenge-invite.html
Adds maximum length constraints and maxlength error messages to password inputs across auth and challenge invitation forms.
Validation tests
tests/unit/accounts/test_validators.py, tests/unit/accounts/test_views.py, tests/unit/accounts/test_serializers.py, tests/unit/accounts/test_throttles.py, tests/unit/challenges/test_serializers.py
Adds unit tests for the new validator, bounded serializers, login and registration rejection paths, throttle key generation, and challenge invitation password length validation.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • Cloud-CV/EvalAI#5111: Also changes apps/challenges/serializers.py password handling for ChallengeInvitationRegisterSerializer, so it touches the same serializer path.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: mitigating DoS risk from oversized signup passwords.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/password-max-length-dos-fix-67ee

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/accounts/throttles.py (1)

44-57: 🔒 Security & Privacy | 🔵 Trivial

IP-based throttle is spoofable unless NUM_PROXIES is configured.

get_cache_key relies on self.get_ident(request), which (per DRF) trusts X-Forwarded-For as-is when NUM_PROXIES is unset/None, only stripping proxy hops when NUM_PROXIES is explicitly configured. An attacker can rotate an arbitrary X-Forwarded-For value per request to bypass this throttle entirely, defeating the DoS-mitigation goal of this PR. DRF's own docs note this throttling should not be treated as a security measure against deliberate abuse unless properly configured behind trusted proxies.

Confirm REST_FRAMEWORK["NUM_PROXIES"] is set appropriately for the deployment topology (or that upstream infra strips client-supplied X-Forwarded-For before forwarding).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/accounts/throttles.py` around lines 44 - 57, The IP-based throttle in
RegistrationIPThrottle is only safe if DRF is configured to derive client IPs
from trusted proxy headers. Verify that REST_FRAMEWORK["NUM_PROXIES"] is set
correctly for your deployment, or that upstream infrastructure strips any
client-supplied X-Forwarded-For before reaching get_cache_key/get_ident; if not,
adjust the deployment/config rather than relying on the throttle alone.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/accounts/validators.py`:
- Around line 28-32: The get_help_text method in the password validator is
interpolating max_length before gettext runs, so the translatable msgid is not
reusable. Update the get_help_text implementation to pass the plain template
string into _() first and apply the % formatting afterward, keeping the message
in the validator class reusable for translation.

In `@settings/common.py`:
- Line 342: The 10 MB request-body limit in DATA_UPLOAD_MAX_MEMORY_SIZE is too
low for the non-file payloads handled by apps/jobs/views.py and the related
challenge update flow, so large JSON/text fields can trigger RequestDataTooBig.
Recheck the expected sizes for payloads like stdout, stderr, environment_log,
result, leaderboard_data, and config data, then raise or scope the limit
appropriately so these update paths can accept the largest legitimate request
bodies. Use DATA_UPLOAD_MAX_MEMORY_SIZE as the main setting to adjust, and
verify the affected handlers in jobs/views.py still work with oversized text
fields.

---

Nitpick comments:
In `@apps/accounts/throttles.py`:
- Around line 44-57: The IP-based throttle in RegistrationIPThrottle is only
safe if DRF is configured to derive client IPs from trusted proxy headers.
Verify that REST_FRAMEWORK["NUM_PROXIES"] is set correctly for your deployment,
or that upstream infrastructure strips any client-supplied X-Forwarded-For
before reaching get_cache_key/get_ident; if not, adjust the deployment/config
rather than relying on the throttle alone.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f2ca1d0b-f4cf-4c0a-abd6-51063dad161c

📥 Commits

Reviewing files that changed from the base of the PR and between f00ddeb and e794c8c.

📒 Files selected for processing (14)
  • apps/accounts/serializers.py
  • apps/accounts/throttles.py
  • apps/accounts/validators.py
  • apps/accounts/views.py
  • apps/challenges/serializers.py
  • frontend/src/views/web/auth/login.html
  • frontend/src/views/web/auth/reset-password-confirm.html
  • frontend/src/views/web/auth/signup.html
  • frontend/src/views/web/challenge-invite.html
  • frontend/src/views/web/change-password.html
  • settings/common.py
  • tests/unit/accounts/test_validators.py
  • tests/unit/accounts/test_views.py
  • tests/unit/challenges/test_serializers.py

Comment thread apps/accounts/validators.py Outdated
Comment thread settings/common.py Outdated
Revert DATA_UPLOAD_MAX_MEMORY_SIZE to 4 GB per review feedback. Apply
password max length on the actual login endpoint via
SafeObtainExpiringAuthToken, fix test import ordering, and point the
login length test at obtain_expiring_auth_token.

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/accounts/serializers.py (1)

356-363: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Preserve the parent password field options. AuthTokenSerializer.password relies on trim_whitespace=False and write_only=True; overriding it with only max_length drops both, so passwords with leading/trailing spaces can fail login and the field loses write-only handling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/accounts/serializers.py` around lines 356 - 363, The
BoundedAuthTokenSerializer.password override is dropping important settings from
AuthTokenSerializer.password, so update the password field definition to
preserve the parent options while adding the bounded length. In
BoundedAuthTokenSerializer, keep the existing write-only handling and
trim_whitespace=False behavior from AuthTokenSerializer.password, and only
extend it with the max_length constraint so login behavior remains unchanged for
passwords with leading or trailing spaces.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/accounts/views.py`:
- Around line 49-55: The bounded serializer is never applied because
ObtainExpiringAuthToken.post() creates AuthTokenSerializer directly, so
SafeObtainExpiringAuthToken.serializer_class has no effect. Update
SafeObtainExpiringAuthToken to override post() or the serializer instantiation
path so it uses BoundedAuthTokenSerializer for /api/auth/login, and keep
safe_obtain_expiring_auth_token pointing to that fixed view.

---

Nitpick comments:
In `@apps/accounts/serializers.py`:
- Around line 356-363: The BoundedAuthTokenSerializer.password override is
dropping important settings from AuthTokenSerializer.password, so update the
password field definition to preserve the parent options while adding the
bounded length. In BoundedAuthTokenSerializer, keep the existing write-only
handling and trim_whitespace=False behavior from AuthTokenSerializer.password,
and only extend it with the max_length constraint so login behavior remains
unchanged for passwords with leading or trailing spaces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a7ddec3-b015-4722-a400-a5e167990565

📥 Commits

Reviewing files that changed from the base of the PR and between e794c8c and 477b9cf.

📒 Files selected for processing (6)
  • apps/accounts/serializers.py
  • apps/accounts/views.py
  • evalai/urls.py
  • settings/common.py
  • tests/unit/accounts/test_validators.py
  • tests/unit/accounts/test_views.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/unit/accounts/test_validators.py
  • tests/unit/accounts/test_views.py

Comment thread apps/accounts/views.py
ObtainExpiringAuthToken hardcodes AuthTokenSerializer in post(), so
setting serializer_class on a subclass had no effect.

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>
@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.23%. Comparing base (1c59607) to head (16ef78a).

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5138      +/-   ##
==========================================
+ Coverage   91.22%   91.23%   +0.01%     
==========================================
  Files         114      114              
  Lines        8852     8874      +22     
==========================================
+ Hits         8075     8096      +21     
- Misses        777      778       +1     
Flag Coverage Δ
backend 93.82% <100.00%> (+0.02%) ⬆️
frontend 87.48% <ø> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Accounts & Authentication 97.50% <100.00%> (+0.10%) ⬆️
Challenges Management 96.23% <100.00%> (ø)
Job Processing 89.82% <ø> (ø)
Participants & Teams 99.54% <ø> (ø)
Challenge Hosts 100.00% <ø> (ø)
Analytics 100.00% <ø> (ø)
Web Interface 100.00% <ø> (ø)
Frontend (Gulp) 87.48% <ø> (-0.03%) ⬇️
All Models 97.36% <ø> (ø)
All Views 100.00% <100.00%> (ø)
All Serializers 98.59% <100.00%> (+0.01%) ⬆️
Utility Functions 97.25% <ø> (ø)
Core Configuration 82.35% <ø> (ø)
Files with missing lines Coverage Δ
apps/accounts/serializers.py 97.53% <100.00%> (+0.09%) ⬆️
apps/accounts/throttles.py 100.00% <100.00%> (ø)
apps/accounts/views.py 100.00% <100.00%> (ø)
apps/challenges/serializers.py 97.57% <100.00%> (ø)
evalai/urls.py 87.50% <ø> (ø)

... and 1 file with indirect coverage changes

Files with missing lines Coverage Δ
apps/accounts/serializers.py 97.53% <100.00%> (+0.09%) ⬆️
apps/accounts/throttles.py 100.00% <100.00%> (ø)
apps/accounts/views.py 100.00% <100.00%> (ø)
apps/challenges/serializers.py 97.57% <100.00%> (ø)
evalai/urls.py 87.50% <ø> (ø)

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 1c59607...16ef78a. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Fix validator gettext usage, preserve AuthTokenSerializer password field
options, document registration throttle proxy requirements, set
NUM_PROXIES in production, and add tests for login token refresh,
serializers, and throttles.

Co-authored-by: Rishabh Jain <rishabhjain2018@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/unit/accounts/test_views.py (1)

330-362: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add boundary-exact coverage for accepted passwords.

Both new tests only exercise max_length + 1. There's no coverage confirming a password of exactly PASSWORD_MAX_LENGTH characters is accepted, which is the actual boundary condition this PR is meant to enforce correctly.

✅ Suggested additional boundary test (registration)
     def test_register_rejects_password_over_max_length(self, mock_resolve):
         """Oversized passwords are rejected before hashing."""
         mock_resolve.return_value = [MagicMock()]
         long_password = "a" * (settings.PASSWORD_MAX_LENGTH + 1)
         response = self.client.post(
             self.url,
             {
                 "username": "longpassuser",
                 "email": "longpass@example.com",
                 "password1": long_password,
                 "password2": long_password,
             },
             format="json",
         )
         self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
         self.assertIn("password1", response.data)
+
+    def test_register_accepts_password_at_max_length(self, mock_resolve):
+        """Passwords at exactly the max length are accepted."""
+        mock_resolve.return_value = [MagicMock()]
+        boundary_password = "Aa1" + "a" * (settings.PASSWORD_MAX_LENGTH - 3)
+        response = self.client.post(
+            self.url,
+            {
+                "username": "boundaryuser",
+                "email": "boundary@example.com",
+                "password1": boundary_password,
+                "password2": boundary_password,
+            },
+            format="json",
+        )
+        self.assertIn(
+            response.status_code,
+            (status.HTTP_201_CREATED, status.HTTP_200_OK),
+        )

As per path instructions, "Check that tests actually assert behavior, cover edge cases, and are not silently passing."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/accounts/test_views.py` around lines 330 - 362, Add boundary-exact
coverage for password length validation in the existing test classes
`LoginPasswordLengthTest` and the registration test method
`test_register_rejects_password_over_max_length`. The current tests only verify
rejection for `PASSWORD_MAX_LENGTH + 1`, so add assertions for a password
exactly `settings.PASSWORD_MAX_LENGTH` characters long to confirm it is accepted
for both the registration flow (`self.client.post` to the signup endpoint) and
the login flow (`obtain_expiring_auth_token`). Keep the new checks near the
existing max-length rejection tests so the boundary behavior is covered in both
directions.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/unit/accounts/test_views.py`:
- Around line 330-362: Add boundary-exact coverage for password length
validation in the existing test classes `LoginPasswordLengthTest` and the
registration test method `test_register_rejects_password_over_max_length`. The
current tests only verify rejection for `PASSWORD_MAX_LENGTH + 1`, so add
assertions for a password exactly `settings.PASSWORD_MAX_LENGTH` characters long
to confirm it is accepted for both the registration flow (`self.client.post` to
the signup endpoint) and the login flow (`obtain_expiring_auth_token`). Keep the
new checks near the existing max-length rejection tests so the boundary behavior
is covered in both directions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7adc310b-b850-4e0c-b18d-58f953f2602a

📥 Commits

Reviewing files that changed from the base of the PR and between 3f21e72 and b31df50.

📒 Files selected for processing (8)
  • apps/accounts/serializers.py
  • apps/accounts/throttles.py
  • apps/accounts/validators.py
  • settings/prod.py
  • tests/unit/accounts/test_serializers.py
  • tests/unit/accounts/test_throttles.py
  • tests/unit/accounts/test_validators.py
  • tests/unit/accounts/test_views.py
✅ Files skipped from review due to trivial changes (1)
  • apps/accounts/validators.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/accounts/throttles.py
  • apps/accounts/serializers.py

@cursor

cursor Bot commented Jul 4, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants