-
-
Notifications
You must be signed in to change notification settings - Fork 39
Added IP-based rate limiting for Copi (fixes #1877) #1920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Added Copi.RateLimiter (GenServer) and CopiWeb.Plugs.RateLimiter (Plug). Integrate rate limiting into LiveView for game creation and WebSocket connections. Added test and SECURITY.md. Making limits configurable via environment variables. Addresses OWASP#1877.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds IP-based rate limiting to the Copi application to protect against CAPEC 212 (Functionality Misuse) attacks. The implementation includes a GenServer-based rate limiter, a Plug for HTTP requests, LiveView integrations for game creation and WebSocket connections, comprehensive tests, and detailed documentation.
Key Changes:
- Implements
Copi.RateLimiterGenServer for tracking and enforcing rate limits per IP address - Adds rate limiting to game creation and WebSocket connection attempts
- Provides configurable limits via environment variables with sensible defaults
Critical Issues Identified:
The review identified several significant bugs that prevent the rate limiting from working correctly:
- Configuration using
System.get_envat compile-time instead of runtime - Test suite attempting to start duplicate named GenServers with async tests
- The Plug never records actions, making it ineffective
put_in/3usage that will crash for new IP addresses- Security vulnerabilities in X-Forwarded-For header handling and "unknown" IP fallback. Use Fly-Client-IP instead of X-Forwarded-For.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
lib/copi/rate_limiter.ex |
Core GenServer implementing rate limiting logic with sliding window and periodic cleanup |
lib/copi_web/plugs/rate_limiter.ex |
HTTP Plug for rate limiting with IP extraction and response header management |
lib/copi_web/live/game_live/index.ex |
WebSocket connection rate limiting in LiveView mount |
lib/copi_web/live/game_live/create_game_form.ex |
Game creation rate limiting before database operations |
lib/copi_web/endpoint.ex |
Endpoint configuration to expose peer_data and x-forwarded-for to LiveViews |
lib/copi/application.ex |
Application supervisor integration for RateLimiter GenServer |
config/config.exs |
Rate limiter configuration with environment variable defaults |
test/copi/rate_limiter_test.exs |
Unit tests for RateLimiter GenServer functionality |
test/copi_web/plugs/rate_limiter_test.exs |
Integration tests for RateLimiter Plug |
SECURITY.md |
Comprehensive security documentation covering implementation details and deployment considerations |
README.md |
User-facing documentation for rate limiting features and configuration |
Comments suppressed due to low confidence (1)
copi.owasp.org/lib/copi_web/plugs/rate_limiter.ex:40
- The X-Forwarded-For header can be spoofed by malicious clients to bypass rate limiting. An attacker can set arbitrary IP addresses in this header to avoid being rate limited. This is a security vulnerability (IP spoofing) that undermines the entire rate limiting mechanism.
Only trust X-Forwarded-For when the request comes from a known reverse proxy. Consider:
- Checking if the request originates from a trusted proxy IP
- Using the rightmost trusted IP in the X-Forwarded-For chain (not the leftmost, which is client-controlled)
- Alternatively, use a library like
RemoteIporPlugAttackthat handles this securely
For now, use Fly-Client-IP according to the fly.io documentation: https://fly.io/docs/networking/request-headers/#fly-client-ip
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| max_games_per_ip: System.get_env("MAX_GAMES_PER_IP", "10") |> String.to_integer(), | ||
| # Time window in seconds for game creation rate limiting (default: 1 hour) | ||
| game_creation_window_seconds: System.get_env("GAME_CREATION_WINDOW_SECONDS", "3600") |> String.to_integer(), | ||
| # Maximum number of WebSocket connections from a single IP in the time window | ||
| max_connections_per_ip: System.get_env("MAX_CONNECTIONS_PER_IP", "50") |> String.to_integer(), | ||
| # Time window in seconds for connection rate limiting (default: 5 minutes) | ||
| connection_window_seconds: System.get_env("CONNECTION_WINDOW_SECONDS", "300") |> String.to_integer() |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Environment variables should be read at runtime, not compile time. System.get_env/2 in config/config.exs is evaluated at compile time, which means the configuration will be baked into the compiled application and environment variable changes won't take effect without recompilation.
Move this configuration to config/runtime.exs instead, which is evaluated at runtime. This is the standard approach in Phoenix applications for runtime configuration and is already being used for other runtime settings in this codebase (see config/runtime.exs).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@immortal71 have a look at this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is worth implementing.
| defp get_connect_ip(socket) do | ||
| case get_connect_info(socket, :peer_data) do | ||
| %{address: {a, b, c, d}} -> "#{a}.#{b}.#{c}.#{d}" | ||
| %{address: {a, b, c, d, e, f, g, h}} -> | ||
| "#{Integer.to_string(a, 16)}:#{Integer.to_string(b, 16)}:#{Integer.to_string(c, 16)}:#{Integer.to_string(d, 16)}:#{Integer.to_string(e, 16)}:#{Integer.to_string(f, 16)}:#{Integer.to_string(g, 16)}:#{Integer.to_string(h, 16)}" | ||
| _ -> "unknown" | ||
| end | ||
| end |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_connect_ip/1 function is duplicated in three different LiveView modules with identical implementation. This violates the DRY principle and makes the code harder to maintain - any bug fixes or improvements need to be applied in multiple places.
Extract this helper function to a shared module, such as:
- A new
CopiWeb.RateLimitHelpersmodule - The
CopiWebmodule (if other LiveViews need it) - Or make it a function in the
Copi.RateLimitermodule itself
Then import or alias it in the LiveView modules that need it.
| defp get_connect_ip(socket) do | ||
| case get_connect_info(socket, :peer_data) do | ||
| %{address: {a, b, c, d}} -> "#{a}.#{b}.#{c}.#{d}" | ||
| %{address: {a, b, c, d, e, f, g, h}} -> | ||
| "#{Integer.to_string(a, 16)}:#{Integer.to_string(b, 16)}:#{Integer.to_string(c, 16)}:#{Integer.to_string(d, 16)}:#{Integer.to_string(e, 16)}:#{Integer.to_string(f, 16)}:#{Integer.to_string(g, 16)}:#{Integer.to_string(h, 16)}" | ||
| _ -> "unknown" | ||
| end | ||
| end |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_connect_ip/1 function is duplicated in three different LiveView modules with identical implementation. This violates the DRY principle and makes the code harder to maintain.
See the same issue identified in lib/copi_web/live/game_live/index.ex. Extract this to a shared module.
| %{address: {a, b, c, d}} -> "#{a}.#{b}.#{c}.#{d}" | ||
| %{address: {a, b, c, d, e, f, g, h}} -> | ||
| "#{Integer.to_string(a, 16)}:#{Integer.to_string(b, 16)}:#{Integer.to_string(c, 16)}:#{Integer.to_string(d, 16)}:#{Integer.to_string(e, 16)}:#{Integer.to_string(f, 16)}:#{Integer.to_string(g, 16)}:#{Integer.to_string(h, 16)}" | ||
| _ -> "unknown" |
Copilot
AI
Dec 3, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning "unknown" as a fallback IP address allows all clients that don't match the expected IP patterns to share the same rate limit bucket.
See the same issue identified in lib/copi_web/live/game_live/index.ex.
|
@immortal71 First of all, thank you so much for doing this. We really appreciate it. There are some comments from Copilot. Some of the seems like valid concerns, but I haven’t reviewed everything in detail. NB: please feel free to add your name under: https://github.com/OWASP/cornucopia/blob/master/cornucopia.owasp.org/data/website/pages/about/en/index.md#volunteers If you don’t want to give away your identity, send your name to me directly and I will add you at a random time in the near future. You may use a pseudonym as well ofcourse. |
I have added my name !! OWASP#1920
sydseter
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have a look at the comments from Copilot. Some of the seems like valid concerns.
|
@immortal71 please make sure your commits have a signature that can be verified. |
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@immortal71 your commits are lacking a verified signature. |
|
@sydseter |
…ack) -->Added :player_creation action to RateLimiter with separate limits --> Default: 20 players per IP per hour (separate from game creation) --> Applied rate limiting to PlayerLive.FormComponent --> Added comprehensive tests for player creation limits --> Updated documentation with implementation details Fixes OWASP#1877
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.0.0 to 6.1.0. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](actions/setup-node@2028fbc...395ad32) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: 6.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit) from 2.49.0 to 2.49.1. - [Release notes](https://github.com/sveltejs/kit/releases) - [Changelog](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md) - [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@2.49.1/packages/kit) --- updated-dependencies: - dependency-name: "@sveltejs/kit" dependency-version: 2.49.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](actions/checkout@1af3b93...8e8c483) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: 6.0.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 5.45.3 to 5.45.4. - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.45.4/packages/svelte) --- updated-dependencies: - dependency-name: svelte dependency-version: 5.45.4 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Added contributor
Added SQL queries to retrieve monthly game and player statistics.
Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 5.45.5 to 5.45.6. - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.45.6/packages/svelte) --- updated-dependencies: - dependency-name: svelte dependency-version: 5.45.6 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [black](https://github.com/psf/black) from 25.1.0 to 25.12.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](psf/black@25.1.0...25.12.0) --- updated-dependencies: - dependency-name: black dependency-version: 25.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [pytest](https://github.com/pytest-dev/pytest) from 8.3.5 to 9.0.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](pytest-dev/pytest@8.3.5...9.0.2) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [coverage](https://github.com/coveragepy/coveragepy) from 7.10.7 to 7.13.0. - [Release notes](https://github.com/coveragepy/coveragepy/releases) - [Changelog](https://github.com/coveragepy/coveragepy/blob/main/CHANGES.rst) - [Commits](coveragepy/coveragepy@7.10.7...7.13.0) --- updated-dependencies: - dependency-name: coverage dependency-version: 7.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps mvdan/shfmt from `20597e9` to `e414177`. --- updated-dependencies: - dependency-name: mvdan/shfmt dependency-version: e414177e424692cd21a5113216edeeeb56fc76b0ed2e5eb3a6c48404d5548a76 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [black](https://github.com/psf/black) from 25.1.0 to 25.12.0. - [Release notes](https://github.com/psf/black/releases) - [Changelog](https://github.com/psf/black/blob/main/CHANGES.md) - [Commits](psf/black@25.1.0...25.12.0) --- updated-dependencies: - dependency-name: black dependency-version: 25.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [platformdirs](https://github.com/tox-dev/platformdirs) from 4.4.0 to 4.5.1. - [Release notes](https://github.com/tox-dev/platformdirs/releases) - [Changelog](https://github.com/tox-dev/platformdirs/blob/main/CHANGES.rst) - [Commits](tox-dev/platformdirs@4.4.0...4.5.1) --- updated-dependencies: - dependency-name: platformdirs dependency-version: 4.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.1. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.5.0...2.6.1) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps the pip group with 1 update in the / directory: [urllib3](https://github.com/urllib3/urllib3). Updates `urllib3` from 2.5.0 to 2.6.0 - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.5.0...2.6.0) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.0 dependency-type: direct:production dependency-group: pip ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps hexpm/elixir from 1.19-erlang-28.2-debian-bullseye-20251117 to 1.19-erlang-28.3-debian-bullseye-20251208. --- updated-dependencies: - dependency-name: hexpm/elixir dependency-version: 1.19-erlang-28.3-debian-bullseye-20251208 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.10.1 to 25.0.1. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 25.0.1 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [actions/cache](https://github.com/actions/cache) from 4.3.0 to 5.0.0. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](actions/cache@0057852...a783357) --- updated-dependencies: - dependency-name: actions/cache dependency-version: 5.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [swoosh](https://github.com/swoosh/swoosh) from 1.19.8 to 1.19.9. - [Release notes](https://github.com/swoosh/swoosh/releases) - [Changelog](https://github.com/swoosh/swoosh/blob/main/CHANGELOG.md) - [Commits](swoosh/swoosh@v1.19.8...v1.19.9) --- updated-dependencies: - dependency-name: swoosh dependency-version: 1.19.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.1. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](urllib3/urllib3@2.5.0...2.6.1) --- updated-dependencies: - dependency-name: urllib3 dependency-version: 2.6.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [svelte](https://github.com/sveltejs/svelte/tree/HEAD/packages/svelte) from 5.45.6 to 5.45.10. - [Release notes](https://github.com/sveltejs/svelte/releases) - [Changelog](https://github.com/sveltejs/svelte/blob/main/packages/svelte/CHANGELOG.md) - [Commits](https://github.com/sveltejs/svelte/commits/svelte@5.45.10/packages/svelte) --- updated-dependencies: - dependency-name: svelte dependency-version: 5.45.10 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [step-security/harden-runner](https://github.com/step-security/harden-runner) from 2.13.3 to 2.14.0. - [Release notes](https://github.com/step-security/harden-runner/releases) - [Commits](step-security/harden-runner@df199fb...20cf305) --- updated-dependencies: - dependency-name: step-security/harden-runner dependency-version: 2.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [ecto_sql](https://github.com/elixir-ecto/ecto_sql) from 3.13.2 to 3.13.3. - [Changelog](https://github.com/elixir-ecto/ecto_sql/blob/master/CHANGELOG.md) - [Commits](elixir-ecto/ecto_sql@v3.13.2...v3.13.3) --- updated-dependencies: - dependency-name: ecto_sql dependency-version: 3.13.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [phoenix](https://github.com/phoenixframework/phoenix) from 1.8.2 to 1.8.3. - [Release notes](https://github.com/phoenixframework/phoenix/releases) - [Changelog](https://github.com/phoenixframework/phoenix/blob/main/CHANGELOG.md) - [Commits](phoenixframework/phoenix@v1.8.2...v1.8.3) --- updated-dependencies: - dependency-name: phoenix dependency-version: 1.8.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [phoenix_live_reload](https://github.com/phoenixframework/phoenix_live_reload) from 1.6.1 to 1.6.2. - [Changelog](https://github.com/phoenixframework/phoenix_live_reload/blob/main/CHANGELOG.md) - [Commits](phoenixframework/phoenix_live_reload@v1.6.1...v1.6.2) --- updated-dependencies: - dependency-name: phoenix_live_reload dependency-version: 1.6.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit) from 2.49.1 to 2.49.2. - [Release notes](https://github.com/sveltejs/kit/releases) - [Changelog](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md) - [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@2.49.2/packages/kit) --- updated-dependencies: - dependency-name: "@sveltejs/kit" dependency-version: 2.49.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
3b588ff to
b0b9e4c
Compare
|
@sydseter done !! |
|
@immortal71 there are a lot of conflicts. I would clone out the repository into a new folder, than move the files you have changed to the new clone and make a new pull request. |
|
@sydseter noted !! |
--Added Copi.RateLimiter (GenServer) and CopiWeb.Plugs.RateLimiter (Plug).
--Integrated rate limiting into LiveView for game creation and WebSocket connections.
--Added unit and plug tests for the limiter.
--Added copi.owasp.org/SECURITY.md documenting the protection and configuration.
--Limits are configurable via environment variables:
-->MAX_GAMES_PER_IP, GAME_CREATION_WINDOW_SECONDS
-->MAX_CONNECTIONS_PER_IP, CONNECTION_WINDOW_SECONDS