The http_request tool unconditionally blocked all private/LAN hosts with
no opt-out, preventing legitimate use cases like calling a local Home
Assistant instance or internal APIs. This adds an `allow_private_hosts`
config flag (default: false) under `[http_request]` that, when set to
true, skips the private-host SSRF check while still enforcing the domain
allowlist.
Closes#3568
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When max_response_size is set to 0, the condition `text.len() > 0` is
true for any non-empty response, causing all responses to be truncated
to empty strings. The conventional meaning of 0 for size limits is
"no limit" (matching ulimit, nginx client_max_body_size, curl, etc.).
Add an early return when max_response_size == 0 and update the doc
comment to document this behavior.
- Add markdown_to_telegram_html() to TelegramChannel: converts **bold**,
*italic*, `code`, ```blocks```, [text](url) links, and ## headers
to Telegram HTML tags (<b>, <i>, <code>, <pre>, <a href>)
- Switch send_text_chunks() and finalize_draft() from parse_mode=Markdown
to parse_mode=HTML — more reliable and supports richer formatting
- Update channel_delivery_instructions() for Telegram: guide model to use
bold, emoji, and concise style (mirrors OpenClaw SOUL.md approach)
- Add wildcard support to http_request allowlist: allowed_domains=["*"]
now bypasses domain filtering entirely
- Expand system prompt URL fetching guidance: jina.ai reader-mode proxy
as fallback for paywalled/403 content
- add scope-aware proxy schema and runtime wiring for providers/channels/tools
- add agent callable proxy_config tool for fast proxy setup
- standardize docs system with index, template, and playbooks
Closes#607
The http_request tool validated the initial URL against the domain
allowlist and private-host rules, but reqwest's default redirect policy
followed redirects automatically without revalidating each hop. This
allowed SSRF via redirect chains from allowed domains to internal hosts.
Set redirect policy to Policy::none() so 3xx responses are returned
as-is. Callers that need to follow redirects must issue a new request,
which goes through validate_url again.
Severity: High — SSRF/allowlist bypass via redirect chains.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test(security): add HTTP hostname canonicalization edge-case tests
Document that Rust's IpAddr::parse() rejects non-standard IP notations
(octal, hex, decimal integer, zero-padded) which provides defense-in-depth
against SSRF bypass attempts. Tests only — no production code changes.
Closes#515
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* style: apply rustfmt to providers/mod.rs
Fix pre-existing formatting issue from main.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
sanitize_headers was replacing sensitive header values with
***REDACTED*** before passing them to the actual HTTP request,
breaking any authenticated API call. Split into parse_headers
(preserves original values for the request) and
redact_headers_for_display (returns redacted copy for output/logging).
Closes#348
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>