Skip to content

fix(security): harden _get() against SSRF via redirects and unbounded responses#34

Open
DennisTraub wants to merge 1 commit intostrands-agents:mainfrom
DennisTraub:fix/p2-security-hardening
Open

fix(security): harden _get() against SSRF via redirects and unbounded responses#34
DennisTraub wants to merge 1 commit intostrands-agents:mainfrom
DennisTraub:fix/p2-security-hardening

Conversation

@DennisTraub
Copy link
Copy Markdown
Contributor

@DennisTraub DennisTraub commented Mar 12, 2026

Summary

_get() follows redirects without restriction and reads responses without a size limit. A malicious or compromised URL in an llms.txt file could exploit this to redirect requests to unintended hosts or stream unbounded data into memory.

This PR hardens _get() with three measures:

  • URL validation on redirect targets — Redirect destinations are validated with the same checks as initial requests (HTTPS-only, no userinfo, hostname required)
  • Same-host redirect policy — Cross-host redirects are blocked; only redirects within the original hostname are followed
  • Response size capread() is bounded to max_response_bytes + 1 (default 2 MB), preventing memory exhaustion from oversized responses

All changes use stdlib only. No new dependencies.

Changes

  • config.py — Add max_response_bytes field (default 2 MB)
  • doc_fetcher.py — Add _validate_fetch_url(), _SameHostRedirectHandler, bounded read in _get()
  • test_doc_fetcher.py — 13 unit tests covering redirect policy, size cap, and URL validation

Test plan

  • pytest tests/test_doc_fetcher.py — 13/13 passing
  • pytest tests_integ/ — 9/9 integration tests passing against live strandsagents.com
  • Manual MCP server test via live Claude subprocess — 6/6 passing

Manual test protocol

# Test Method Result
1 search_docs(query="how to create an agent", k=3) Returns 3 ranked results with url, title, score, snippet PASS
2 fetch_doc(uri=<url from test 1>) - TOC mode Small doc returns full content; large doc returns sections PASS
3 fetch_doc(uri=<large doc>, section="1") - Section mode Returns section_id, section_title, content PASS
4 fetch_doc(uri="") - Empty URI Returns catalog of 407 URLs with url and title fields PASS
5 fetch_doc(uri="http://169.254.169.254/latest/meta-data/") - SSRF Rejected: "only https://strandsagents.com URLs allowed" PASS
6 fetch_doc(uri="https://strandsagents.com/docs/user-guide/concepts/agents/hooks/index.md") - Legitimate fetch Returns TOC with 9 sections PASS

@DennisTraub DennisTraub requested a review from a team as a code owner March 12, 2026 15:19
…d response size cap

- Add _validate_fetch_url(): HTTPS-only, reject userinfo URLs, require hostname
- Add _SameHostRedirectHandler: block cross-host and scheme-downgrade redirects
- Validate redirect targets with same checks as initial requests
- Add response size cap via bounded read (max_response_bytes + 1)
- Add max_response_bytes config (2 MB default)
- Add 13 tests covering redirect policy, size cap, and URL validation
@DennisTraub DennisTraub force-pushed the fix/p2-security-hardening branch from 61bd4a6 to 976aa8b Compare March 12, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant