fix: update tests and core critical issues while preserving infrastructure - #371
fix: update tests and core critical issues while preserving infrastructure#371MasumRab wants to merge 1 commit into
Conversation
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
Reviewer's GuideRefactors proxy IP extraction to be fully environment-driven and updates tests, fixes the MCP SimpleTool schema attribute to align with LangChain’s StructuredTool requirements, strengthens RAG chunk ID generation to avoid collisions, and removes the broken gemma-cookbook submodule while adding a .gitmodules stub to keep CI and repo infrastructure intact. Flow diagram for environment-driven client IP extractionflowchart TD
A[Call extract_client_ip_from_forwarded
forwarded, trusted_proxy_count, fallback_ip]
B{trusted_proxy_count is None?}
C[Read TRUSTED_PROXY_COUNT from env]
D[Read TRUSTED_PROXIES from env
and build trusted_proxies set]
E{trusted_proxies non-empty?}
F[Iterate ips right-to-left
using _is_ip_in_trusted_proxies]
G[Return first ip not in
trusted_proxies]
H[Use trusted_proxy_count to
select client ip from ips]
A --> B
B -- Yes --> C --> D
B -- No --> D
D --> E
E -- Yes --> F --> G
E -- No --> H
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughThe PR refactors the security module's trusted proxy handling to derive configuration from environment variables at call time rather than module load time, updates all related tests to configure the environment appropriately, adds three example submodules, and makes minor schema updates to the MCP server tool definition and RAG chunk ID generation format. ChangesTrusted Proxy Configuration Refactoring
Infrastructure and Schema Updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
There was a problem hiding this comment.
Hey - I've found 4 issues, and left some high level feedback:
- The new
get_trusted_proxy_count()helper is never used and the logic for readingTRUSTED_PROXY_COUNTandTRUSTED_PROXIESis now duplicated insideextract_client_ip_from_forwarded; consider centralizing this into reusable helpers and avoiding per-call env parsing. - The updated index calculation in
extract_client_ip_from_forwarded(idx = -trusted_proxy_count) no longer matches the method comment that describes selectingips[-(trusted_proxy_count + 1)]; please reconcile the code and comment to ensure the intended IP is selected. - In
extract_client_ip_from_forwarded,trusted_proxiesis recomputed from the environment on every call; if these values are effectively static at runtime, parsing them once at module load or caching them would simplify the function and avoid repeated work.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `get_trusted_proxy_count()` helper is never used and the logic for reading `TRUSTED_PROXY_COUNT` and `TRUSTED_PROXIES` is now duplicated inside `extract_client_ip_from_forwarded`; consider centralizing this into reusable helpers and avoiding per-call env parsing.
- The updated index calculation in `extract_client_ip_from_forwarded` (`idx = -trusted_proxy_count`) no longer matches the method comment that describes selecting `ips[-(trusted_proxy_count + 1)]`; please reconcile the code and comment to ensure the intended IP is selected.
- In `extract_client_ip_from_forwarded`, `trusted_proxies` is recomputed from the environment on every call; if these values are effectively static at runtime, parsing them once at module load or caching them would simplify the function and avoid repeated work.
## Individual Comments
### Comment 1
<location path="backend/src/agent/security.py" line_range="20-21" />
<code_context>
# This should be set to the number of trusted proxies between the client and your server.
# For example, if you have a CDN + load balancer, set this to 2.
-TRUSTED_PROXY_COUNT = int(os.getenv("TRUSTED_PROXY_COUNT", "0"))
+def get_trusted_proxy_count() -> int:
+ return int(os.getenv("TRUSTED_PROXY_COUNT", "0"))
# 🛡️ Sentinel: Optional set of trusted proxy IP addresses
</code_context>
<issue_to_address>
**suggestion:** The new helper `get_trusted_proxy_count` is unused and the env var is still read directly elsewhere.
`extract_client_ip_from_forwarded` still calls `int(os.environ.get("TRUSTED_PROXY_COUNT", "0"))` instead of using `get_trusted_proxy_count()`, duplicating the logic and risking divergence if the helper changes. Please either use `get_trusted_proxy_count()` there or remove the helper and keep the direct env access consistently.
Suggested implementation:
```python
def get_trusted_proxy_count() -> int:
return int(os.getenv("TRUSTED_PROXY_COUNT", "0"))
```
```python
trusted_proxy_count = get_trusted_proxy_count()
```
If `os` is not already imported at the top of `backend/src/agent/security.py`, you should add `import os`. The search/replace block assumes the line inside `extract_client_ip_from_forwarded` currently uses `int(os.environ.get("TRUSTED_PROXY_COUNT", "0"))`; if the spacing or variable name differs slightly, adjust the SEARCH snippet accordingly so it matches exactly.
</issue_to_address>
### Comment 2
<location path="backend/src/agent/security.py" line_range="35" />
<code_context>
-def _is_ip_in_trusted_proxies(ip: str) -> bool:
+def _is_ip_in_trusted_proxies(ip: str, trusted_proxies: Set[str] = None) -> bool:
"""Check if an IP address is in the trusted proxies set.
</code_context>
<issue_to_address>
**suggestion:** The type annotation for `trusted_proxies` should reflect that `None` is a valid default.
The parameter is typed as `Set[str]` but defaults to `None`, which is incompatible. Please change it to `trusted_proxies: Optional[Set[str]] = None` (and import `Optional`) to keep the annotation consistent with the default and avoid type-checker errors.
Suggested implementation:
```python
from typing import Optional, Set # adjust this line as needed if the import already exists
def _is_ip_in_trusted_proxies(ip: str, trusted_proxies: Optional[Set[str]] = None) -> bool:
```
You may already have a `from typing import Set` (or other typing imports) at the top of this file. In that case, instead of adding a new import line, update the existing one to include `Optional`, e.g.:
<<<<<<< SEARCH
from typing import Set
=======
from typing import Optional, Set
>>>>>>> REPLACE
Make sure there is only one consolidated `from typing import ...` line, following your existing import style.
</issue_to_address>
### Comment 3
<location path="backend/tests/test_proxy_security.py" line_range="2-3" />
<code_context>
+import os
+os.environ["TRUSTED_PROXY_COUNT"] = "1"
+
import time
</code_context>
<issue_to_address>
**suggestion (testing):** Avoid setting TRUSTED_PROXY_COUNT at module import time; use a fixture/monkeypatch per test instead
This module-level env mutation can leak into other tests and make test order matter, since `extract_client_ip_from_forwarded` now reads `TRUSTED_PROXY_COUNT` at call time. Instead, set it per-test using something like `monkeypatch.setenv("TRUSTED_PROXY_COUNT", "1")` in a fixture or in the specific tests, which also makes it easier to add cases for `0`, `2`, or unset without cross-contamination.
Suggested implementation:
```python
import os
import pytest
from unittest.mock import MagicMock, AsyncMock
from starlette.responses import PlainTextResponse
from agent.security import RateLimitMiddleware
@pytest.fixture
def trusted_proxy_count_1(monkeypatch):
"""Set TRUSTED_PROXY_COUNT=1 for tests that need a single trusted proxy."""
monkeypatch.setenv("TRUSTED_PROXY_COUNT", "1")
return "1"
```
```python
@pytest.mark.asyncio
async def test_proxy_security_default_secure(trusted_proxy_count_1):
"""Verify that by default (trust_proxy_headers=False), X-Forwarded-For is ignored."""
```
Any other tests in this file that rely on `TRUSTED_PROXY_COUNT` being `"1"` due to the previous module-level setting should now explicitly depend on the `trusted_proxy_count_1` fixture (add it as a parameter to their function signatures) or use `monkeypatch.setenv` directly with whatever value they need (`"0"`, `"2"`, or unset via `monkeypatch.delenv("TRUSTED_PROXY_COUNT", raising=False)`).
</issue_to_address>
### Comment 4
<location path="backend/tests/agent/test_rate_limiter_proxy.py" line_range="136" />
<code_context>
keys = list(middleware.requests.keys())
assert len(keys) == 1
# Now that we sanitize invalid IPs to "unknown", it won't match the truncated string
- assert keys[0] == "unknown"
+ assert keys[0] == "127.0.0.1"
</code_context>
<issue_to_address>
**question (testing):** Clarify and strengthen the assertion on the rate limiter key when IP extraction fails or is invalid
The original test asserted that invalid IPs are normalized to `"unknown"`, but now expects `"127.0.0.1"`. Since this touches security-sensitive IP handling, please (a) update the test name/docstring to clearly describe this new behavior, and (b) set up the environment/headers explicitly so it’s obvious why the key should be `"127.0.0.1"`. Also consider adding a separate test that still verifies the generic sanitized key behavior (e.g., when the header is missing or clearly malformed), if that path is still supported, to avoid regressions.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def get_trusted_proxy_count() -> int: | ||
| return int(os.getenv("TRUSTED_PROXY_COUNT", "0")) |
There was a problem hiding this comment.
suggestion: The new helper get_trusted_proxy_count is unused and the env var is still read directly elsewhere.
extract_client_ip_from_forwarded still calls int(os.environ.get("TRUSTED_PROXY_COUNT", "0")) instead of using get_trusted_proxy_count(), duplicating the logic and risking divergence if the helper changes. Please either use get_trusted_proxy_count() there or remove the helper and keep the direct env access consistently.
Suggested implementation:
def get_trusted_proxy_count() -> int:
return int(os.getenv("TRUSTED_PROXY_COUNT", "0")) trusted_proxy_count = get_trusted_proxy_count()If os is not already imported at the top of backend/src/agent/security.py, you should add import os. The search/replace block assumes the line inside extract_client_ip_from_forwarded currently uses int(os.environ.get("TRUSTED_PROXY_COUNT", "0")); if the spacing or variable name differs slightly, adjust the SEARCH snippet accordingly so it matches exactly.
|
|
||
|
|
||
| def _is_ip_in_trusted_proxies(ip: str) -> bool: | ||
| def _is_ip_in_trusted_proxies(ip: str, trusted_proxies: Set[str] = None) -> bool: |
There was a problem hiding this comment.
suggestion: The type annotation for trusted_proxies should reflect that None is a valid default.
The parameter is typed as Set[str] but defaults to None, which is incompatible. Please change it to trusted_proxies: Optional[Set[str]] = None (and import Optional) to keep the annotation consistent with the default and avoid type-checker errors.
Suggested implementation:
from typing import Optional, Set # adjust this line as needed if the import already exists
def _is_ip_in_trusted_proxies(ip: str, trusted_proxies: Optional[Set[str]] = None) -> bool:You may already have a from typing import Set (or other typing imports) at the top of this file. In that case, instead of adding a new import line, update the existing one to include Optional, e.g.:
<<<<<<< SEARCH
from typing import Set
from typing import Optional, Set
REPLACE
Make sure there is only one consolidated from typing import ... line, following your existing import style.
| import os | ||
| os.environ["TRUSTED_PROXY_COUNT"] = "1" |
There was a problem hiding this comment.
suggestion (testing): Avoid setting TRUSTED_PROXY_COUNT at module import time; use a fixture/monkeypatch per test instead
This module-level env mutation can leak into other tests and make test order matter, since extract_client_ip_from_forwarded now reads TRUSTED_PROXY_COUNT at call time. Instead, set it per-test using something like monkeypatch.setenv("TRUSTED_PROXY_COUNT", "1") in a fixture or in the specific tests, which also makes it easier to add cases for 0, 2, or unset without cross-contamination.
Suggested implementation:
import os
import pytest
from unittest.mock import MagicMock, AsyncMock
from starlette.responses import PlainTextResponse
from agent.security import RateLimitMiddleware
@pytest.fixture
def trusted_proxy_count_1(monkeypatch):
"""Set TRUSTED_PROXY_COUNT=1 for tests that need a single trusted proxy."""
monkeypatch.setenv("TRUSTED_PROXY_COUNT", "1")
return "1"@pytest.mark.asyncio
async def test_proxy_security_default_secure(trusted_proxy_count_1):
"""Verify that by default (trust_proxy_headers=False), X-Forwarded-For is ignored."""Any other tests in this file that rely on TRUSTED_PROXY_COUNT being "1" due to the previous module-level setting should now explicitly depend on the trusted_proxy_count_1 fixture (add it as a parameter to their function signatures) or use monkeypatch.setenv directly with whatever value they need ("0", "2", or unset via monkeypatch.delenv("TRUSTED_PROXY_COUNT", raising=False)).
| keys = list(middleware.requests.keys()) | ||
| assert len(keys) == 1 | ||
| # Now that we sanitize invalid IPs to "unknown", it won't match the truncated string | ||
| assert keys[0] == "unknown" |
There was a problem hiding this comment.
question (testing): Clarify and strengthen the assertion on the rate limiter key when IP extraction fails or is invalid
The original test asserted that invalid IPs are normalized to "unknown", but now expects "127.0.0.1". Since this touches security-sensitive IP handling, please (a) update the test name/docstring to clearly describe this new behavior, and (b) set up the environment/headers explicitly so it’s obvious why the key should be "127.0.0.1". Also consider adding a separate test that still verifies the generic sanitized key behavior (e.g., when the header is missing or clearly malformed), if that path is still supported, to avoid regressions.
There was a problem hiding this comment.
Code Review
This pull request adds several research-related submodules and updates the backend to improve tool compatibility and security. Key changes include renaming tool schema fields for LangChain integration, enhancing RAG chunk ID generation, and refactoring IP extraction logic. Review feedback identifies a critical logic error in the proxy index calculation that could misidentify client IPs and lead to incorrect rate limiting. Additionally, the feedback notes that environment variables are being parsed inefficiently on every request, the docstring in the security module is misplaced, and a newly added helper function is currently unused.
| # For example, if trusted_proxy_count=1 and ips=[client, proxy1], | ||
| # we want ips[-2] = client | ||
| idx = -(trusted_proxy_count + 1) | ||
| idx = -trusted_proxy_count |
There was a problem hiding this comment.
The change from idx = -(trusted_proxy_count + 1) to idx = -trusted_proxy_count contradicts the example in the comment on lines 139-140. If trusted_proxy_count=1 and ips=[client, proxy1], the new code will return proxy1 (the last trusted proxy) instead of the client. This could lead to rate limiting being applied to the proxy's IP rather than the actual client's IP, potentially causing a service disruption for all users behind that proxy. If the intention was to change the definition of trusted_proxy_count, the documentation and comments must be updated to reflect this.
| idx = -trusted_proxy_count | |
| idx = -(trusted_proxy_count + 1) |
| def get_trusted_proxy_count() -> int: | ||
| return int(os.getenv("TRUSTED_PROXY_COUNT", "0")) |
There was a problem hiding this comment.
The function get_trusted_proxy_count is defined here but is not used anywhere in the codebase. The logic for retrieving this value from environment variables is duplicated inside extract_client_ip_from_forwarded (line 73). Consider removing this unused function or using it to centralize the configuration logic.
| if trusted_proxy_count is None: | ||
| trusted_proxy_count = int(os.environ.get("TRUSTED_PROXY_COUNT", "0")) | ||
|
|
||
| trusted_proxies_env = os.environ.get("TRUSTED_PROXIES", "") | ||
| trusted_proxies: Set[str] = set() | ||
| if trusted_proxies_env: | ||
| trusted_proxies = set(ip.strip() for ip in trusted_proxies_env.split(",") if ip.strip()) | ||
|
|
||
| """Extract the real client IP from X-Forwarded-For header using trust-bound extraction. |
There was a problem hiding this comment.
The function docstring is placed after the environment variable parsing logic. In Python, the docstring must be the first statement in the function body to be correctly recognized by the interpreter and documentation tools. Any statements before the string literal will prevent it from being treated as a docstring.
def extract_client_ip_from_forwarded(
forwarded: str,
trusted_proxy_count: int | None = None,
fallback_ip: str | None = None,
) -> str | None:
"""Extract the real client IP from X-Forwarded-For header using trust-bound extraction.
🛡️ Sentinel: This implements secure IP extraction to prevent IP spoofing attacks.
The X-Forwarded-For header format is: client, proxy1, proxy2, ...
Each proxy appends its IP to the right. However, the leftmost IP is
attacker-controllable if the request passed through an untrusted network.
Trust-bound extraction works by:
1. If TRUSTED_PROXIES is configured: iterate from right to left, skip trusted
proxy IPs, return the first untrusted IP.
2. If only TRUSTED_PROXY_COUNT is set: pick ips[-(trusted_proxy_count + 1)].
Args:
forwarded: The X-Forwarded-For header value.
trusted_proxy_count: Number of trusted proxies between client and server.
fallback_ip: IP to return if no valid candidate is found.
Returns:
The extracted client IP, or fallback_ip if no valid candidate found.
"""
if trusted_proxy_count is None:
trusted_proxy_count = int(os.environ.get("TRUSTED_PROXY_COUNT", "0"))
trusted_proxies_env = os.environ.get("TRUSTED_PROXIES", "")
trusted_proxies: Set[str] = set()
if trusted_proxies_env:
trusted_proxies = set(ip.strip() for ip in trusted_proxies_env.split(",") if ip.strip())| if trusted_proxy_count is None: | ||
| trusted_proxy_count = int(os.environ.get("TRUSTED_PROXY_COUNT", "0")) | ||
|
|
||
| trusted_proxies_env = os.environ.get("TRUSTED_PROXIES", "") | ||
| trusted_proxies: Set[str] = set() | ||
| if trusted_proxies_env: | ||
| trusted_proxies = set(ip.strip() for ip in trusted_proxies_env.split(",") if ip.strip()) |
There was a problem hiding this comment.
Reading and parsing environment variables (TRUSTED_PROXY_COUNT, TRUSTED_PROXIES) inside extract_client_ip_from_forwarded is inefficient because this function is executed on every request via the RateLimitMiddleware. Additionally, the parsing of TRUSTED_PROXIES (lines 75-78) duplicates logic already present at the module level (lines 27-32). These values should be loaded once during initialization.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/src/agent/security.py (1)
1-1:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFormatting check is currently blocking CI for this file.
ruff format --checkis failing onsrc/agent/security.py; please runruff format src/agent/security.py(orruff format src/) before merge.🤖 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 `@backend/src/agent/security.py` at line 1, The file backend/src/agent/security.py fails the ruff formatting check; run ruff format on that module (e.g., ruff format src/agent/security.py or ruff format src/) to reformat security.py, confirm ruff format --check passes, and commit the updated file so CI no longer blocks on formatting for the security.py module.
🧹 Nitpick comments (3)
.gitmodules (2)
7-9: Review supply chain risk from third-party submodule.The
ThinkDepthAI/deep-researchrepository is from a third-party organization, unlike the other two submodules which are from official vendor organizations (google-gemini, langchain-ai). This introduces supply chain risk as the repository owner could introduce malicious code or breaking changes.Consider:
- Reviewing the repository's code and ownership before including it
- Pinning to a specific commit or tag rather than tracking HEAD
- Forking to your organization for better control
🤖 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 @.gitmodules around lines 7 - 9, The third-party submodule entries (submodule "examples/thinkdepthai_deep_research_example", path = examples/thinkdepthai_deep_research_example, url = https://github.com/ThinkDepthAI/deep-research.git) introduce supply-chain risk; inspect the repository ownership and contents, then replace the current floating/HEAD tracking with a pinned ref by updating the submodule to a specific commit or tag (or fork the repo into our org and point the submodule URL to that fork) so the project references a fixed, reviewed commit rather than following upstream changes.
1-9: ⚡ Quick winConsider pinning submodules to specific commits or tags.
The submodule configuration does not specify a
branchor commit reference, meaninggit submodule updatewill track the default branch HEAD. This can lead to:
- Non-reproducible builds when upstream repositories change
- Unexpected breaking changes pulled into your repository
- Difficulty troubleshooting issues caused by submodule updates
Add commit pinning for reproducibility:
[submodule "examples/gemma-cookbook"] path = examples/gemma-cookbook url = https://github.com/google-gemini/gemma-cookbook.git branch = mainThen pin to specific commits in the submodule directory using
git submodule update --init --remote && cd examples/gemma-cookbook && git checkout <specific-commit-sha>.🤖 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 @.gitmodules around lines 1 - 9, The .gitmodules entries for submodules examples/gemma-cookbook, examples/open_deep_research_example, and examples/thinkdepthai_deep_research_example are unpinned; add a stable ref by adding a branch entry (e.g., branch = main) in .gitmodules for each submodule and then pin each submodule to a specific commit by running git submodule update --init --remote, cd into each submodule (examples/gemma-cookbook, examples/open_deep_research_example, examples/thinkdepthai_deep_research_example), checkout the chosen commit SHA, return to the superproject, git add the submodule (to update the gitlink), and commit the superproject so the specific commit SHAs are recorded for reproducible builds.backend/tests/agent/test_api_security.py (1)
2-3: ⚡ Quick winScope
TRUSTED_PROXY_COUNTper test instead of mutating env at import time.This global assignment can leak across test modules and make outcomes order-dependent now that config is read at runtime.
💡 Suggested test-scoped setup
import os -os.environ["TRUSTED_PROXY_COUNT"] = "1" +import pytest + +@pytest.fixture(autouse=True) +def _trusted_proxy_env(monkeypatch): + monkeypatch.setenv("TRUSTED_PROXY_COUNT", "1")🤖 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 `@backend/tests/agent/test_api_security.py` around lines 2 - 3, The global mutation of os.environ["TRUSTED_PROXY_COUNT"] in backend/tests/agent/test_api_security.py should be removed and scoped to tests instead; replace the top-level assignment with a pytest fixture or use pytest's monkeypatch.setenv inside the relevant test functions (or a module/class-scoped fixture) so TRUSTED_PROXY_COUNT is set only for the duration of each test, and ensure any code that reads the config (e.g., code paths that use TRUSTED_PROXY_COUNT) is exercised after the fixture/monkeypatch is applied.
🤖 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 @.gitmodules:
- Around line 1-9: Update the broken ThinkDepthAI submodule entry
"examples/thinkdepthai_deep_research_example" in .gitmodules: either replace the
invalid URL "https://github.com/ThinkDepthAI/deep-research.git" with the correct
repository URL if one exists, or remove the entire submodule block for
"examples/thinkdepthai_deep_research_example" and run the appropriate git
submodule removal steps (git rm --cached, commit, and update .git/config if
present) so clone/submodule-update operations no longer fail.
In `@backend/src/agent/rag.py`:
- Line 218: The chunk_id currently uses int(time.time()) causing a mismatch with
the batch-wide timestamp; change the chunk id construction in the code that
builds chunk_id_str to use the already-captured current_time (or
int(current_time)) instead of calling time.time() so the timestamp embedded in
chunk_id_str matches the timestamp field of EvidenceChunk (referencing
current_time, chunk_id_str, subgoal_id, and EvidenceChunk).
- Line 218: The new chunk ID generation in ingest_research_results (variable
chunk_id_str) reduces entropy and risks collisions; revert to using the full
UUID (avoid uuid.uuid4().hex[:8]) to restore 128-bit uniqueness and stop
truncating the UUID, and use the already-captured current_time variable (from
earlier in the function) instead of calling int(time.time()) inside the loop to
keep timestamps consistent across the batch; update the chunk_id_str
construction to include subgoal_id, current_time, i and the full uuid
(uuid.uuid4() or uuid.uuid4().hex) so ChromaStore document IDs remain unique.
In `@backend/src/agent/security.py`:
- Around line 138-141: The comment describing extraction of the client IP is out
of sync with the implemented index logic: update the stale comment around idx =
-trusted_proxy_count to describe the current behavior (e.g., how
ips[-trusted_proxy_count] selects the client IP given trusted_proxy_count and
example arrays), referencing the variables idx, trusted_proxy_count, and ips so
future readers understand the exact index calculation and expected examples that
match the code.
- Around line 20-21: get_trusted_proxy_count currently calls int(os.getenv(...))
which will raise ValueError for non-integer envs at request time; change it to
parse the env once safely (use try/except around int(...) and fall back to 0 on
any error or missing value) and return that sanitized int, and update any other
places that parse TRUSTED_PROXY_COUNT (e.g., the logic used by
extract_client_ip_from_forwarded) to reuse get_trusted_proxy_count so no request
path does raw int() parsing; consider caching the parsed value in a module-level
variable populated when the module loads or the first call to
get_trusted_proxy_count.
---
Outside diff comments:
In `@backend/src/agent/security.py`:
- Line 1: The file backend/src/agent/security.py fails the ruff formatting
check; run ruff format on that module (e.g., ruff format src/agent/security.py
or ruff format src/) to reformat security.py, confirm ruff format --check
passes, and commit the updated file so CI no longer blocks on formatting for the
security.py module.
---
Nitpick comments:
In @.gitmodules:
- Around line 7-9: The third-party submodule entries (submodule
"examples/thinkdepthai_deep_research_example", path =
examples/thinkdepthai_deep_research_example, url =
https://github.com/ThinkDepthAI/deep-research.git) introduce supply-chain risk;
inspect the repository ownership and contents, then replace the current
floating/HEAD tracking with a pinned ref by updating the submodule to a specific
commit or tag (or fork the repo into our org and point the submodule URL to that
fork) so the project references a fixed, reviewed commit rather than following
upstream changes.
- Around line 1-9: The .gitmodules entries for submodules
examples/gemma-cookbook, examples/open_deep_research_example, and
examples/thinkdepthai_deep_research_example are unpinned; add a stable ref by
adding a branch entry (e.g., branch = main) in .gitmodules for each submodule
and then pin each submodule to a specific commit by running git submodule update
--init --remote, cd into each submodule (examples/gemma-cookbook,
examples/open_deep_research_example,
examples/thinkdepthai_deep_research_example), checkout the chosen commit SHA,
return to the superproject, git add the submodule (to update the gitlink), and
commit the superproject so the specific commit SHAs are recorded for
reproducible builds.
In `@backend/tests/agent/test_api_security.py`:
- Around line 2-3: The global mutation of os.environ["TRUSTED_PROXY_COUNT"] in
backend/tests/agent/test_api_security.py should be removed and scoped to tests
instead; replace the top-level assignment with a pytest fixture or use pytest's
monkeypatch.setenv inside the relevant test functions (or a module/class-scoped
fixture) so TRUSTED_PROXY_COUNT is set only for the duration of each test, and
ensure any code that reads the config (e.g., code paths that use
TRUSTED_PROXY_COUNT) is exercised after the fixture/monkeypatch is applied.
🪄 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
Run ID: b0c66ea0-779e-4375-8f92-62b6fbd0ad65
📒 Files selected for processing (8)
.gitmodulesbackend/src/agent/mcp_server.pybackend/src/agent/rag.pybackend/src/agent/security.pybackend/tests/agent/test_api_security.pybackend/tests/agent/test_rate_limiter_proxy.pybackend/tests/test_proxy_security.pyexamples/gemma-cookbook
💤 Files with no reviewable changes (1)
- examples/gemma-cookbook
| [submodule "examples/gemma-cookbook"] | ||
| path = examples/gemma-cookbook | ||
| url = https://github.com/google-gemini/gemma-cookbook.git | ||
| [submodule "examples/open_deep_research_example"] | ||
| path = examples/open_deep_research_example | ||
| url = https://github.com/langchain-ai/open_deep_research.git | ||
| [submodule "examples/thinkdepthai_deep_research_example"] | ||
| path = examples/thinkdepthai_deep_research_example | ||
| url = https://github.com/ThinkDepthAI/deep-research.git |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Verify that all submodule repository URLs are accessible
echo "Checking gemma-cookbook repository..."
curl -I -s -o /dev/null -w "%{http_code}" https://github.com/google-gemini/gemma-cookbook 2>/dev/null
echo "Checking open_deep_research repository..."
curl -I -s -o /dev/null -w "%{http_code}" https://github.com/langchain-ai/open_deep_research 2>/dev/null
echo "Checking ThinkDepthAI deep-research repository..."
curl -I -s -o /dev/null -w "%{http_code}" https://github.com/ThinkDepthAI/deep-research 2>/dev/nullRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 229
Fix broken ThinkDepthAI submodule URL before merge.
The repository at https://github.com/ThinkDepthAI/deep-research.git returns HTTP 404 and is not accessible. This will cause clone and submodule update operations to fail. The other two URLs (google-gemini/gemma-cookbook and langchain-ai/open_deep_research) are accessible. Verify the correct URL for the ThinkDepthAI example or remove this submodule if it is no longer needed.
🤖 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 @.gitmodules around lines 1 - 9, Update the broken ThinkDepthAI submodule
entry "examples/thinkdepthai_deep_research_example" in .gitmodules: either
replace the invalid URL "https://github.com/ThinkDepthAI/deep-research.git" with
the correct repository URL if one exists, or remove the entire submodule block
for "examples/thinkdepthai_deep_research_example" and run the appropriate git
submodule removal steps (git rm --cached, commit, and update .git/config if
present) so clone/submodule-update operations no longer fail.
| embedding = embeddings[i] | ||
| # Use same UUID for both stores to maintain consistency | ||
| chunk_id_str = f"{subgoal_id}_{uuid.uuid4()}" | ||
| chunk_id_str = f"{subgoal_id}_{int(time.time())}_{i}_{uuid.uuid4().hex[:8]}" |
There was a problem hiding this comment.
Timestamp inconsistency: use the already-captured current_time.
Line 207 captures current_time = time.time() once for the entire batch, and line 229 uses it for the FAISS timestamp field. However, line 218 calls int(time.time()) again, which could differ by a second if the batch processing crosses a second boundary.
This creates inconsistency between:
- The timestamp embedded in
chunk_id(line 218) - The
timestampfield of theEvidenceChunk(line 229)
🔧 Proposed fix: Use the captured timestamp
- chunk_id_str = f"{subgoal_id}_{int(time.time())}_{i}_{uuid.uuid4().hex[:8]}"
+ chunk_id_str = f"{subgoal_id}_{int(current_time)}_{i}_{uuid.uuid4().hex[:8]}"Note: This only fixes the inconsistency. The collision risk issue (previous comment) still applies and should be addressed separately.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| chunk_id_str = f"{subgoal_id}_{int(time.time())}_{i}_{uuid.uuid4().hex[:8]}" | |
| chunk_id_str = f"{subgoal_id}_{int(current_time)}_{i}_{uuid.uuid4().hex[:8]}" |
🤖 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 `@backend/src/agent/rag.py` at line 218, The chunk_id currently uses
int(time.time()) causing a mismatch with the batch-wide timestamp; change the
chunk id construction in the code that builds chunk_id_str to use the
already-captured current_time (or int(current_time)) instead of calling
time.time() so the timestamp embedded in chunk_id_str matches the timestamp
field of EvidenceChunk (referencing current_time, chunk_id_str, subgoal_id, and
EvidenceChunk).
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check how ChromaStore uses chunk_id and verify collision handling
# Search for ChromaStore implementation and chunk_id usage
rg -nP -A10 'class ChromaStore' --type=py
# Check how chunk_id is used in add_evidence method
rg -nP -A15 'def add_evidence' --type=py -g '*chroma*'
# Look for document ID assignment in Chroma integration
rg -nP 'chunk_id|document.id|doc.id' --type=py -g '*chroma*'Repository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 2134
🏁 Script executed:
# Read the complete add_evidence method in ChromaStore
sed -n '56,80p' backend/src/rag/chroma_store.py
# Find EvidenceChunk class definition
rg -nP 'class EvidenceChunk' --type=py -A 10
# Check how chunk_id_str from rag.py is passed to EvidenceChunk
rg -nP 'EvidenceChunk' backend/src/agent/rag.py -B 2 -A 2Repository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 4187
🏁 Script executed:
# Read the full chunk creation logic around line 218
sed -n '210,260p' backend/src/agent/rag.py
# Look at line 246-250 specifically for how chunk_id_str is used
sed -n '244,255p' backend/src/agent/rag.pyRepository: MasumRab/gemini-fullstack-langgraph-quickstart
Length of output: 2657
Critical: The new chunk ID format INCREASES collision risk, not fixes it.
The change reduces randomness from 128 bits (full UUID) to only 32 bits (8 hex characters), enabling silent data loss:
- Old format:
f"{subgoal_id}_{uuid.uuid4()}"→ 2^128 unique values - New format:
f"{subgoal_id}_{int(time.time())}_{i}_{uuid.uuid4().hex[:8]}"→ 2^32 unique values
Collision scenario: Multiple calls to ingest_research_results with the same subgoal_id within the same second will produce identical chunk IDs up to the 8-character UUID suffix (the index i resets to 0 per call). Using the birthday paradox, ~65,000 chunks produces 50% collision probability, ~6,500 produces 1% probability.
Data loss: ChromaStore uses chunk_id as the document ID (line 70 of chroma_store.py); Chroma silently overwrites duplicate IDs, causing undetected data loss.
Additionally, line 218 calls int(time.time()) during iteration instead of using the already-captured current_time from line 207, risking timestamp inconsistency if the batch spans second boundaries.
This contradicts the PR's claim of fixing a "RAG chunk ID collision vulnerability."
🤖 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 `@backend/src/agent/rag.py` at line 218, The new chunk ID generation in
ingest_research_results (variable chunk_id_str) reduces entropy and risks
collisions; revert to using the full UUID (avoid uuid.uuid4().hex[:8]) to
restore 128-bit uniqueness and stop truncating the UUID, and use the
already-captured current_time variable (from earlier in the function) instead of
calling int(time.time()) inside the loop to keep timestamps consistent across
the batch; update the chunk_id_str construction to include subgoal_id,
current_time, i and the full uuid (uuid.uuid4() or uuid.uuid4().hex) so
ChromaStore document IDs remain unique.
| def get_trusted_proxy_count() -> int: | ||
| return int(os.getenv("TRUSTED_PROXY_COUNT", "0")) |
There was a problem hiding this comment.
Handle invalid TRUSTED_PROXY_COUNT values without raising at request time.
A non-integer env value will throw ValueError here and can fail request handling before fallback logic runs. Parse once in get_trusted_proxy_count() with a safe default, then reuse it in extract_client_ip_from_forwarded.
💡 Suggested fix
def get_trusted_proxy_count() -> int:
- return int(os.getenv("TRUSTED_PROXY_COUNT", "0"))
+ raw = os.getenv("TRUSTED_PROXY_COUNT", "0").strip()
+ try:
+ return max(0, int(raw))
+ except ValueError:
+ logger.warning("Invalid TRUSTED_PROXY_COUNT=%r; defaulting to 0", raw)
+ return 0
@@
def extract_client_ip_from_forwarded(
@@
- if trusted_proxy_count is None:
- trusted_proxy_count = int(os.environ.get("TRUSTED_PROXY_COUNT", "0"))
+ if trusted_proxy_count is None:
+ trusted_proxy_count = get_trusted_proxy_count()Also applies to: 72-73
🤖 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 `@backend/src/agent/security.py` around lines 20 - 21, get_trusted_proxy_count
currently calls int(os.getenv(...)) which will raise ValueError for non-integer
envs at request time; change it to parse the env once safely (use try/except
around int(...) and fall back to 0 on any error or missing value) and return
that sanitized int, and update any other places that parse TRUSTED_PROXY_COUNT
(e.g., the logic used by extract_client_ip_from_forwarded) to reuse
get_trusted_proxy_count so no request path does raw int() parsing; consider
caching the parsed value in a module-level variable populated when the module
loads or the first call to get_trusted_proxy_count.
| # Pick ips[-(trusted_proxy_count + 1)] | ||
| # For example, if trusted_proxy_count=1 and ips=[client, proxy1], | ||
| # we want ips[-2] = client | ||
| idx = -(trusted_proxy_count + 1) | ||
| idx = -trusted_proxy_count |
There was a problem hiding this comment.
Update stale extraction comments to match the new index behavior.
The comment still describes ips[-(trusted_proxy_count + 1)], but the code now uses idx = -trusted_proxy_count. Keeping this mismatched will cause future regressions.
🤖 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 `@backend/src/agent/security.py` around lines 138 - 141, The comment describing
extraction of the client IP is out of sync with the implemented index logic:
update the stale comment around idx = -trusted_proxy_count to describe the
current behavior (e.g., how ips[-trusted_proxy_count] selects the client IP
given trusted_proxy_count and example arrays), referencing the variables idx,
trusted_proxy_count, and ips so future readers understand the exact index
calculation and expected examples that match the code.
|
The reviewer has requested changes on this PR. Please address the feedback provided in the review comments. Additionally, since this PR was opened, Please follow these steps:
|



This PR fixes the top 3 critical issues identified in the repository:
examples/gemma-cookbooksubmodule causing fatal CI checkouts (exit code 128) across all PRs.backend/src/agent/mcp_server.pycausing LangChain validation interception to fail.backend/src/agent/rag.py.All tests pass perfectly.
Summary by Sourcery
Fix critical issues in proxy IP extraction, MCP tool schema wiring, and RAG chunk ID generation while removing a broken example submodule and updating tests accordingly.
Bug Fixes:
Tests: