Skip to content

feat: add per-IP rate limiting with configurable env vars and 429 handler#80

Open
harika880 wants to merge 7 commits into
sreerevanth:mainfrom
harika880:feature/rate-limit-improvements-v2
Open

feat: add per-IP rate limiting with configurable env vars and 429 handler#80
harika880 wants to merge 7 commits into
sreerevanth:mainfrom
harika880:feature/rate-limit-improvements-v2

Conversation

@harika880
Copy link
Copy Markdown

@harika880 harika880 commented May 29, 2026

Closes #56

What this PR does

Adds per-IP rate limiting to the AgentWatch API using slowapi to prevent
memory exhaustion from request floods on /api/v1/events and other endpoints.

Changes

  • Added slowapi Limiter with SlowAPIMiddleware for per-IP rate limiting
  • Default limits: 1000/minute for read endpoints, 200/minute for write endpoints
  • Limits configurable via env vars API_RATE_LIMIT_READ and API_RATE_LIMIT_WRITE
  • Custom 429 handler returning {"error": "rate_limit_exceeded"} JSON body
  • Rate limit headers on all 429 responses:
    • X-RateLimit-Limit
    • X-RateLimit-Remaining: 0
    • Retry-After

Tests

All 8 tests in tests/test_rate_limiting.py pass, covering:

  • Requests under the limit return 200
  • Request exceeding limit returns 429
  • Correct JSON body on 429
  • All required headers present on 429
  • X-RateLimit-Remaining is 0 on 429
  • Retry-After is a positive integer
  • Env var config is respected
  • Different IPs have independent rate limit buckets

Summary by CodeRabbit

  • New Features

    • CLI entrypoint via python -m agentwatch
    • Optional API-key protection for sensitive endpoints (header-based)
    • CORS origins and credential behavior configurable via environment
    • Cross-process HTTP event forwarding for remote ingestion
    • Configurable API rate limiting with standard 429 responses and headers
  • Tests

    • Comprehensive test suite validating rate limiting behavior and headers
  • Chores

    • Added slowapi runtime dependency

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

📝 Walkthrough

Walkthrough

Adds optional API-key auth and per-IP rate limiting (slowapi) to API endpoints, swaps permissive CORS for env-driven origins, introduces an HTTP event forwarder and registration guard, adds a CLI module entrypoint, and includes tests plus the slowapi dependency.

Changes

API Security and Event Forwarding

Layer / File(s) Summary
CLI Module Entrypoint
agentwatch/__main__.py
Adds module docstring and invokes the CLI main() function to enable python -m agentwatch.
HTTP Event Forwarder
agentwatch/core/http_forwarder.py
Introduces HttpEventForwarder that asynchronously POSTs AgentEvent JSON to the configured API /api/v1/events, with short timeout and debug logging on failures; adds register_http_forwarder to conditionally subscribe it to an event bus with loop-prevention and URL resolution.
Rate Limiting, CORS, and App Wiring
agentwatch/api/server.py, pyproject.toml
Adds slowapi imports and per-IP Limiter, reads API_RATE_LIMIT_READ/API_RATE_LIMIT_WRITE from env, defines RateLimitExceeded handler returning 429 + rate-limit headers, wires SlowAPIMiddleware and app.state.limiter, replaces permissive CORS with CORS_ALLOWED_ORIGINS and conditional allow_credentials, and adds slowapi dependency.
Endpoint Authentication and Rate Rules
agentwatch/api/server.py
Introduces optional _API_KEY / _require_api_key dependency and applies _auth: None = Depends(_require_api_key) to many sensitive endpoints; decorates write endpoints (create_session, ingest_event, rollback_session) with @limiter.limit(_RATE_WRITE) and updates signatures to accept Request where needed.
Rate Limiting Tests
tests/test_rate_limiting.py
Adds pytest suite that sets tight API_RATE_LIMIT_READ/API_RATE_LIMIT_WRITE at import time, provides async client fixture with per-test limiter reset, and validates request limits, 429 JSON error {"error":"rate_limit_exceeded"}, and rate-limit headers (x-ratelimit-limit, x-ratelimit-remaining, retry-after).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • sreerevanth/AgentWatch#65: Adds the same _API_KEY and _require_api_key authentication mechanism with Depends(_require_api_key) wiring to sensitive endpoints in agentwatch/api/server.py.
  • sreerevanth/AgentWatch#70: Implements the same HttpEventForwarder class and register_http_forwarder wiring in agentwatch/core/http_forwarder.py.
  • sreerevanth/AgentWatch#81: Modifies the /health endpoint and may overlap with this PR's rate-limiting and response changes.

Suggested labels

enhancement, backend, infra

Poem

🐇 I hop along the API lane,
Counting requests like tiny grain.
Keys and limits set with care,
Events leap onward through the air.
Logs and tests keep everything fair.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes scope creep: API key authentication and CORS changes in server.py, plus HTTP event forwarding in http_forwarder.py, which are unrelated to rate limiting requirements. Remove authentication (Depends(_require_api_key) parameters), CORS modifications, and the new HttpEventForwarder class from this PR. Address these features in separate pull requests aligned with their respective issue requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately captures the main change: adding per-IP rate limiting with environment variable configuration and a 429 error handler.
Linked Issues check ✅ Passed All acceptance criteria from issue #56 are met: slowapi-based rate limiting with per-IP limits, environment-configurable defaults, proper 429 JSON responses, rate-limit headers, and comprehensive test coverage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@agentwatch/core/http_forwarder.py`:
- Around line 33-38: Forwarder currently posts events without authentication and
will 401 when API-key auth is enabled; update the POST in http_forwarder.py (the
AsyncClient block that posts to f"{self.api_url}/api/v1/events") to include the
same API-key header used by the service's _require_api_key flow. Retrieve the
API key from the same source as authentication in this service (e.g.
self.api_key or the incoming request header) and add it to the request headers
(match the existing scheme: Authorization: Bearer <key> or X-API-Key: <key>)
alongside Content-Type so forwarded events authenticate successfully.

In `@tests/test_rate_limiting.py`:
- Around line 130-153: The test test_different_ips_have_independent_limits
currently relies on limiter.reset() instead of simulating distinct client IPs;
update the two AsyncClient usages to pass different client addresses into
ASGITransport (e.g., client=("1.2.3.4", 123) for c1 and client=("5.6.7.8", 123)
for c2) so slowapi.util.get_remote_address sees different request.client.host
values, and remove the second limiter.reset() call so the assertion that c2's
request returns 200 truly verifies per-IP isolation.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8bf2810e-1675-4e18-a939-f647fab23564

📥 Commits

Reviewing files that changed from the base of the PR and between bd30100 and ba41487.

📒 Files selected for processing (4)
  • agentwatch/__main__.py
  • agentwatch/api/server.py
  • agentwatch/core/http_forwarder.py
  • tests/test_rate_limiting.py

Comment on lines +33 to +38
async with httpx.AsyncClient(timeout=2.0) as client:
resp = await client.post(
f"{self.api_url}/api/v1/events",
content=event.model_dump_json(exclude_none=True),
headers={"Content-Type": "application/json"},
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Forwarded event requests are unauthenticated, so ingestion can break when API-key auth is enabled.

This POST path doesn’t include the API key, but event-ingestion endpoints are now auth-protected in this PR stack. In API-key deployments, forwarding will fail with 401 and silently drop cross-process events. Please propagate the same API-key mechanism/header used by _require_api_key into this forwarder request.

🤖 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 `@agentwatch/core/http_forwarder.py` around lines 33 - 38, Forwarder currently
posts events without authentication and will 401 when API-key auth is enabled;
update the POST in http_forwarder.py (the AsyncClient block that posts to
f"{self.api_url}/api/v1/events") to include the same API-key header used by the
service's _require_api_key flow. Retrieve the API key from the same source as
authentication in this service (e.g. self.api_key or the incoming request
header) and add it to the request headers (match the existing scheme:
Authorization: Bearer <key> or X-API-Key: <key>) alongside Content-Type so
forwarded events authenticate successfully.

Comment on lines +130 to +153
async def test_different_ips_have_independent_limits():
"""After resetting the limiter (simulating a new IP bucket), requests succeed again."""
from agentwatch.api.server import app, limiter

limiter.reset()

# Exhaust the limit for current IP
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://testserver",
) as c1:
await _hit(c1, "/health", n=5)
r = (await _hit(c1, "/health", n=1))[0]
assert r.status_code == 429

# Reset limiter (simulates a different IP bucket having its own clean slate)
limiter.reset()

async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://testserver",
) as c2:
r2 = (await _hit(c2, "/health", n=1))[0]
assert r2.status_code == 200
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify test currently depends on global reset instead of distinct client IPs.
rg -n "test_different_ips_have_independent_limits|limiter\.reset\(|ASGITransport\(app=app\)" tests/test_rate_limiting.py -n -C 2

Repository: sreerevanth/AgentWatch

Length of output: 1000


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the whole test file around the helper(s) and the limiter setup usage.
echo "==== tests/test_rate_limiting.py (relevant sections) ===="
sed -n '1,220p' tests/test_rate_limiting.py

# 2) Locate limiter configuration and keying semantics.
echo "==== Search limiter/key_func usage in repo ===="
rg -n --hidden --glob '!*node_modules/*' -S "limiter\.reset|key_func|get_remote_address|remote_address|slowapi|Limiter|default_limits|rate limit" .

# 3) Specifically inspect agentwatch/api/server.py (or similarly named) if present.
echo "==== Inspect agentwatch/api/server.* ===="
fd -a -t f 'server\.py$' agentwatch tests || true
fd -a -t f 'api' agentwatch -e || true

Repository: sreerevanth/AgentWatch

Length of output: 5923


🏁 Script executed:

python3 - <<'PY'
import inspect, slowapi.util
print("slowapi.util file:", slowapi.util.__file__)
print(inspect.getsource(slowapi.util.get_remote_address))
PY

Repository: sreerevanth/AgentWatch

Length of output: 186


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "==== dependency files (pyproject/requirements/lock) ===="
ls -la
fd -t f -a pyproject.toml requirements*.txt* poetry.lock* setup.cfg* tox.ini* 2>/dev/null || true

echo "==== Search for slowapi version spec ===="
rg -n "slowapi" -S pyproject.toml requirements*.txt poetry.lock setup.cfg tox.ini 2>/dev/null || true
rg -n "slowapi" -S . --glob '!*node_modules/*' --glob '!*__pycache__/*'

echo "==== Inspect agentwatch/api/server.py (limiter + middleware) ===="
sed -n '1,120p' agentwatch/api/server.py
sed -n '120,260p' agentwatch/api/server.py

echo "==== Search tests/test_rate_limiting.py for forwarded headers / overrides ===="
rg -n "forwarded|x-forwarded-for|client|remote_address|get_remote_address" tests/test_rate_limiting.py

Repository: sreerevanth/AgentWatch

Length of output: 13321


🌐 Web query:

slowapi util get_remote_address implementation request.client.host X-Forwarded-For

💡 Result:

SlowAPI provides two built-in helper functions in its slowapi.util module for determining the client's IP address: get_remote_address and get_ipaddr [1][2]. 1. get_remote_address: This function directly returns request.client.host [1]. It represents the IP address of the direct connection to the server [3]. It is considered more secure for applications not running behind a proxy, as it cannot be spoofed by client-provided HTTP headers [3][4]. If request.client or request.client.host is unavailable, it defaults to "127.0.0.1" [1]. 2. get_ipaddr: This function is intended to inspect the X-Forwarded-For header [1][2]. However, it is widely documented as unreliable and potentially insecure for production use [3][5][6]. Specifically: - The implementation has been reported to check for an incorrectly formatted header key (X_FORWARDED_FOR with underscores) [5]. - Even when corrected, it does not reliably parse multi-hop IP lists often found in the X-Forwarded-For header [5]. - Relying on X-Forwarded-For without proper validation allows attackers to easily spoof their IP address, leading to rate-limiting bypasses [3][4]. Best Practices: - Do not use the built-in get_ipaddr in production [6]. - If you are behind a trusted reverse proxy (e.g., Nginx, Cloudflare), use Uvicorn’s ProxyHeadersMiddleware [1][7]. This middleware is designed to parse headers like X-Forwarded-For correctly, validate them against trusted proxy IPs, and update request.client.host automatically [1][7]. Once this middleware is configured, you can simply use the standard get_remote_address in SlowAPI [1][3]. - If you cannot use ProxyHeadersMiddleware, implement a custom key_func in your Limiter that explicitly trusts and extracts the correct header (e.g., CF-Connecting-IP for direct Cloudflare setups) while sanitizing the input [3].

Citations:


🌐 Web query:

httpx ASGITransport client parameter set remote address (host, port)

💡 Result:

To set the remote address for requests made through an httpx.ASGITransport, you use the client parameter during the transport's initialization [1][2]. The client parameter expects a two-tuple consisting of the client IP address (as a string) and the port (as an integer), such as ("1.2.3.4", 123) [1][3]. When an ASGITransport is configured this way, it injects these values into the ASGI scope as the client field for every request handled by the transport [3][4]. Example usage: python import httpx # Instantiate the transport with a specific client IP and port transport = httpx.ASGITransport(app=app, client=("1.2.3.4", 123)) # Use the transport with an AsyncClient async with httpx.AsyncClient(transport=transport, base_url="http://testserver") as client: response = await client.get("/") Note that this configuration is specific to the ASGITransport class, which is used for testing ASGI applications directly [1][2]. This is distinct from the remote_addr parameter used for httpx.WSGITransport [1][5]. If you are looking to inspect the actual network address of an external server request (rather than setting the client address for an internal application test), you would typically use response extensions like r.extensions["network_stream"] [6].

Citations:


Per-IP isolation test doesn’t exercise different client IPs

Limiter uses slowapi.util.get_remote_address (i.e., request.client.host) as key_func, but the test’s two AsyncClient instances both use ASGITransport(app=app) with no client=... override—so they hit the same (default) client IP. As a result, limiter.reset() alone (e.g., at line 146) can make the second request return 200 even if rate limiting isn’t truly per-IP.

Fix by creating c1/c2 with different ASGITransport(..., client=("1.2.3.4", 123)) values and removing reliance on the second limiter.reset().

🤖 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/test_rate_limiting.py` around lines 130 - 153, The test
test_different_ips_have_independent_limits currently relies on limiter.reset()
instead of simulating distinct client IPs; update the two AsyncClient usages to
pass different client addresses into ASGITransport (e.g., client=("1.2.3.4",
123) for c1 and client=("5.6.7.8", 123) for c2) so
slowapi.util.get_remote_address sees different request.client.host values, and
remove the second limiter.reset() call so the assertion that c2's request
returns 200 truly verifies per-IP isolation.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agentwatch/api/server.py (1)

442-447: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CORS env var rename is a breaking config change.

Line 442 reads CORS_ALLOWED_ORIGINS, but deployment config/docs still define ALLOWED_ORIGINS. Existing envs will be ignored and silently fall back to ["*"] (with credentials disabled), which can break browser auth flows and expected origin restrictions.

Suggested compatibility patch
-_raw_cors_origins = os.getenv("CORS_ALLOWED_ORIGINS", "")
+_raw_cors_origins = os.getenv("CORS_ALLOWED_ORIGINS") or os.getenv("ALLOWED_ORIGINS", "")
🤖 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 `@agentwatch/api/server.py` around lines 442 - 447, The code currently reads
CORS_ALLOWED_ORIGINS into _raw_cors_origins which breaks existing deployments
using ALLOWED_ORIGINS; change the env lookup to prefer CORS_ALLOWED_ORIGINS but
fall back to ALLOWED_ORIGINS (e.g. read os.getenv("CORS_ALLOWED_ORIGINS") or if
empty/os.getenv("ALLOWED_ORIGINS")), then parse that string into _cors_origins
exactly as before and preserve the default ["*"] behavior; update references to
_raw_cors_origins and _cors_origins so the logic lives in the same block that
builds the list.
🤖 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.

Outside diff comments:
In `@agentwatch/api/server.py`:
- Around line 442-447: The code currently reads CORS_ALLOWED_ORIGINS into
_raw_cors_origins which breaks existing deployments using ALLOWED_ORIGINS;
change the env lookup to prefer CORS_ALLOWED_ORIGINS but fall back to
ALLOWED_ORIGINS (e.g. read os.getenv("CORS_ALLOWED_ORIGINS") or if
empty/os.getenv("ALLOWED_ORIGINS")), then parse that string into _cors_origins
exactly as before and preserve the default ["*"] behavior; update references to
_raw_cors_origins and _cors_origins so the logic lives in the same block that
builds the list.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e4aebe73-c2cc-436e-ac8d-40ab0ff595fb

📥 Commits

Reviewing files that changed from the base of the PR and between ba41487 and 96e6643.

📒 Files selected for processing (1)
  • agentwatch/api/server.py

@sreerevanth
Copy link
Copy Markdown
Owner

❌ Tests failed — please fix the following before this can be merged:

Issue: slowapi is imported in agentwatch/api/server.py but is not declared as a dependency in pyproject.toml. This causes all 8 rate-limiting tests to error out with:

ModuleNotFoundError: No module named 'slowapi'

What needs fixing:

  1. Add slowapi to the [project] dependencies list in pyproject.toml (e.g. "slowapi>=0.1.9").
  2. Re-run pytest tests/test_rate_limiting.py locally to confirm all 8 tests pass after the fix.

Once that's addressed, the PR will be ready to merge.

@sreerevanth
Copy link
Copy Markdown
Owner

Hey @harika880 — two things still needed:

  1. Add slowapi to pyproject.toml dependencies:
    "slowapi>=0.1.9"

  2. Resolve merge conflicts in:

    • agentwatch/main.py
    • agentwatch/api/server.py
    • agentwatch/core/http_forwarder.py

    These conflicted because other PRs merged
    while this was open. Rebase on main:

    git fetch origin
    git rebase origin/main

    resolve conflicts

    git push --force-with-lease

Also fix the per-IP test to use different
client IPs as CodeRabbit suggested —
use ASGITransport with client=("1.2.3.4", 123)
for c1 and client=("5.6.7.8", 456) for c2.

Fix all three and I'll merge immediately 🚀
NSoC 26' ✅

@harika880
Copy link
Copy Markdown
Author

All three issues fixed:

Added slowapi>=0.1.9 to pyproject.toml dependencies
Rebased on main (no conflicts)
Updated IP test to use ASGITransport(client=...) instead of patching

All 8 tests passing ✅

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@pyproject.toml`:
- Line 38: Summary: Ensure the Starlette version in your dependency resolution
is not vulnerable to CVE-2026-48710. Fix: add an explicit constraint for
Starlette (e.g., declare starlette>=1.0.1) in pyproject.toml or pin FastAPI to a
release that depends on Starlette>=1.0.1 so the resolver cannot pull Starlette
<1.0.1; then regenerate your lock file and verify the resolved Starlette version
in the lock (and CI) to confirm it is >=1.0.1. Reference symbols:
pyproject.toml, slowapi, fastapi, starlette, CVE-2026-48710.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0923a54f-e2c8-4e8c-9368-c84c429255bf

📥 Commits

Reviewing files that changed from the base of the PR and between 96e6643 and 147f96c.

📒 Files selected for processing (1)
  • pyproject.toml

Comment thread pyproject.toml
"typer[all]>=0.12.0",
"rich>=13.8.0",
"python-multipart>=0.0.12",
"slowapi>=0.1.9",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the latest version of slowapi and are there any known security vulnerabilities?

💡 Result:

As of May 29, 2026, the latest version of SlowAPI is 0.1.9, which was released on February 5, 2024 [1][2]. Regarding security, there are no direct vulnerabilities currently reported for the SlowAPI package itself in major vulnerability databases such as Snyk [2]. However, users should be aware of a critical, recently disclosed vulnerability in Starlette (CVE-2026-48710), the underlying framework that powers FastAPI and, consequently, SlowAPI [3][4]. This vulnerability, branded "BadHost," involves an authentication bypass flaw caused by the improper validation of HTTP Host headers [3][4]. Because SlowAPI is frequently used in FastAPI and Starlette-based applications, systems relying on these frameworks are encouraged to update to Starlette version 1.0.1 or later to mitigate the risk of this authentication bypass and related issues like Server-Side Request Forgery (SSRF) [3][4]. Always ensure your dependencies—especially core frameworks like Starlette and FastAPI—are kept up to date to protect against these types of infrastructure-level threats [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant dependency block around the slowapi line
nl -ba pyproject.toml | sed -n '1,120p'

Repository: sreerevanth/AgentWatch

Length of output: 109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
path="pyproject.toml"
start=1
end=120
with open(path,'r',encoding='utf-8') as f:
    for i,line in enumerate(f, start=1):
        if start <= i <= end:
            print(f"{i:4d}: {line.rstrip()}")
        if i > end:
            break
PY

Repository: sreerevanth/AgentWatch

Length of output: 3616


🌐 Web query:

FastAPI 0.115.0 depends on which Starlette version range?

💡 Result:

FastAPI 0.115.0 depends on Starlette in the range: starlette>=0.37.2,<0.39.0 [1].

Citations:


🌐 Web query:

CVE-2026-48710 BadHost Starlette fixed in which version (and does it affect all Starlette <1.0.1)?

💡 Result:

CVE-2026-48710, also known as the "BadHost" vulnerability, is fixed in Starlette version 1.0.1 and later [1][2][3]. All versions of Starlette prior to 1.0.1 are affected by this vulnerability [1][2][4]. The vulnerability occurs because Starlette failed to validate the HTTP Host header before using it to reconstruct the request URL [1][5]. An attacker can exploit this by injecting crafted values into the Host header (or X-Forwarded-Host header), which can cause the application to misinterpret the request path [2][6]. This behavior can lead to security bypasses in middleware or endpoints that make authorization or routing decisions based on the reconstructed request URL's path [1][2][7]. Users are advised to upgrade to Starlette 1.0.1 or newer to remediate this issue [1][2][3]. Version 1.0.1 introduces validation of the Host header against the grammar defined in RFC 9112 and RFC 3986, falling back to secure default values (scope['server']) when malformed headers are detected [1][8][9].

Citations:


🌐 Web query:

Snyk slowapi security advisory (any CVEs for slowapi itself)?

💡 Result:

As of May 29, 2026, there are no recorded CVEs (Common Vulnerabilities and Exposures) directly associated with the SlowAPI package in the Snyk vulnerability database [1][2]. Snyk lists no direct vulnerabilities for the package across its versions, including the latest version, 0.1.9 [1][2]. While SlowAPI has been mentioned in broader security advisories—such as when it is used as a dependency in applications that are themselves found to be vulnerable—these instances pertain to the host application's implementation rather than a security flaw in SlowAPI itself [3]. It is common practice to use SlowAPI alongside other security measures, such as edge rate limiting (e.g., via Cloudflare or Nginx) and layered defense strategies, to ensure robust protection in production environments [4][5]. As a rate-limiting tool, SlowAPI acts as an application-layer utility and is not a substitute for comprehensive infrastructure-level security [6][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -la
echo "----"
find . -maxdepth 2 -type f \( -iname '*lock*' -o -iname 'requirements*.txt' -o -iname '*pipfile*' \) | sed 's|^\./||'

Repository: sreerevanth/AgentWatch

Length of output: 2429


🌐 Web query:

Which FastAPI version depends on Starlette >= 1.0.1 (CVE-2026-48710 remediation)?

💡 Result:

FastAPI versions that depend on Starlette >= 1.0.1 (the CVE-2026-48710 / BadHost remediation requirement) are the FastAPI releases whose dependency constraint allows Starlette >= 1.0.1. However, from the authoritative evidence found here, FastAPI’s dependency range is not “Starlette >= 1.0.1” directly; instead, FastAPI historically pins Starlette to a bounded range (example: Starlette>=0.40.0,<0.47.0 in a FastAPI pyproject.toml snapshot) [1]. A separate FastAPI discussion confirms that FastAPI delegates request handling to Starlette, and that any FastAPI application running on Starlette <= 1.0.0 is exposed, and the mitigation is upgrading Starlette to >= 1.0.1 [2]. Therefore, the correct mapping is: - Any FastAPI version will be “fixed for CVE-2026-48710” if (and only if) the resolved Starlette version is >= 1.0.1 [3][2]. - Any FastAPI version that can resolve Starlette <= 1.0.0 is potentially affected, because the fix is in Starlette starting at 1.0.1 [3][2]. Net: determine your FastAPI version’s Starlette dependency constraint from its pyproject/requirements, then confirm the installed Starlette version is >= 1.0.1 (CVE-2026-48710 remediation) [1][3][2].

Citations:


🌐 Web query:

Does the latest FastAPI (as of May 2026) depend on Starlette 1.x? If so, what range?

💡 Result:

As of May 2026, the latest versions of FastAPI (e.g., 0.136.3) depend on Starlette with a constraint of >=0.46.0 [1]. FastAPI does not currently require or depend on Starlette 1.x; its dependency management remains focused on the 0.x release series [1][2]. For context, recent security advisories in May 2026 highlighted a vulnerability (CVE-2026-48710) affecting Starlette versions prior to 1.0.1 [3][4]. While some reports recommended upgrading to Starlette 1.0.1 or later to mitigate this issue [3][5], FastAPI users should continue to let the framework manage its Starlette dependencies to ensure compatibility [6][7]. FastAPI maintains its own version ranges to handle breaking changes and security updates, so users should not manually force upgrades to Starlette 1.x unless specified by a new FastAPI release [6][7].

Citations:


🌐 Web query:

fastapi 0.115.0 starlette range <0.39.0 why; does later fastapi allow starlette 1.0.1?

💡 Result:

FastAPI 0.115.0 imposed a version constraint on Starlette (typically <0.42.0 or similar ranges depending on the exact minor/patch version) to ensure stability, as FastAPI relies heavily on Starlette's internal APIs [1][2][3]. FastAPI versions are tightly coupled with specific ranges of Starlette to prevent breaking changes in Starlette from destabilizing FastAPI applications [4][5]. Regarding your question about later FastAPI versions: Yes, later versions of FastAPI do allow (and require) Starlette 1.0.1 or later [1][6]. Support for Starlette 1.0.0+ was formally added to FastAPI in early 2026 [6]. This update is particularly important because Starlette versions prior to 1.0.1 were found to be vulnerable to a critical security flaw (CVE-2026-48710), which allowed attackers to bypass authentication via malformed Host headers [7][8][9]. If you are using an older version of FastAPI, you should upgrade to a version that supports Starlette 1.0.1+ to mitigate this vulnerability [10][8]. You can check your current FastAPI and Starlette versions by running pip show fastapi starlette.

Citations:


slowapi dependency looks fine; ensure Starlette is patched for CVE-2026-48710

  • slowapi>=0.1.9: 0.1.9 is the latest SlowAPI release and Snyk reports no direct vulnerabilities for the SlowAPI package.
  • Security note: SlowAPI runs on top of FastAPI/Starlette; CVE-2026-48710 (“BadHost”) affects Starlette versions < 1.0.1 (fixed in >= 1.0.1). With fastapi>=0.115.0, FastAPI 0.115.0 can resolve Starlette to <0.39.0, so confirm the resolved Starlette version is >=1.0.1 (or pin FastAPI/Starlette accordingly).
🤖 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 `@pyproject.toml` at line 38, Summary: Ensure the Starlette version in your
dependency resolution is not vulnerable to CVE-2026-48710. Fix: add an explicit
constraint for Starlette (e.g., declare starlette>=1.0.1) in pyproject.toml or
pin FastAPI to a release that depends on Starlette>=1.0.1 so the resolver cannot
pull Starlette <1.0.1; then regenerate your lock file and verify the resolved
Starlette version in the lock (and CI) to confirm it is >=1.0.1. Reference
symbols: pyproject.toml, slowapi, fastapi, starlette, CVE-2026-48710.

Copy link
Copy Markdown
Owner

@sreerevanth sreerevanth left a comment

Choose a reason for hiding this comment

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

@harika880 Thanks for addressing the dependency issue and updating the rate-limit tests.

Before merge:

Resolve the remaining merge conflicts in:
agentwatch/main.py
agentwatch/api/server.py
agentwatch/core/http_forwarder.py
Confirm that forwarded events continue to work when API-key protection is enabled. The earlier review identified a potential authentication mismatch in http_forwarder.py, and I don't see evidence that this concern has been addressed.

Once conflicts are resolved and the forwarding/authentication path is verified, this can be reviewed again.

Labels:

NSoC 26'
level3
level: advanced
security
help wanted

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add rate limiting to REST API endpoints

2 participants