Improve test infrastructure: StubChannel, gateway helpers, security tests, search edge cases#623
Conversation
Adds StubChannel to src/testing.rs alongside StubLlm. Supports message injection via mpsc sender, response/status capture, and configurable health check toggling. Includes handle methods for use after ownership transfer to ChannelManager. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add with_stub_channel() builder method that creates a StubChannel pre-registered in a ChannelManager. Tests can inject messages via the sender and verify routing through the manager. The channel field on TestHarness is Optional, defaulting to None for backward compat. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace silent try_connect() skip pattern with explicit feature gating. cargo test now runs only self-contained tests. cargo test --features integration runs tests requiring PostgreSQL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cover add/start_all stream merging, respond routing, unknown channel errors, health_check_all with mixed health, empty-channels error path, and injection channel merging -- all via StubChannel test double. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Grep-based checks for three architecture boundaries: - Direct database driver usage (tokio_postgres/libsql) outside src/db/ - .unwrap()/.expect() in production code (warning only) - Direct std::env::var reads outside config layer (warning only) The DB driver check is a hard violation; the other two are warnings for gradual cleanup. Run with: bash scripts/check-boundaries.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onfig modes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… protections Add 11 regression tests covering the security controls in skill_tools: ZIP extraction safety: - Valid SKILL.md extraction works correctly - Non-SKILL.md entries are ignored (returns error) - Path traversal entries (../../SKILL.md) do not match - Nested path entries (subdir/SKILL.md) do not match - Oversized entries (>1MB uncompressed) are rejected SSRF prevention: - Loopback addresses (127.0.0.1) are blocked - Private ranges (10.x, 172.16.x, 192.168.x) are blocked - Link-local addresses (169.254.x) are blocked - Public IPs (8.8.8.8, 1.1.1.1) are allowed - IPv4-mapped IPv6 unwrapping logic works correctly - Metadata endpoints and .internal/.local hostnames are blocked - Normal hostnames (github.com, clawhub.dev) are allowed Also documents a known gap: url::Url::host_str() returns bracketed IPv6 addresses that std::net::IpAddr cannot parse, so IPv4-mapped IPv6 URLs currently bypass IP-based checks in validate_fetch_url. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…st duplication Both ws_gateway_integration.rs and openai_compat_integration.rs manually constructed GatewayState with 19+ fields. Extracted to a shared builder in src/channels/web/test_helpers.rs that provides sensible defaults and lets tests override only what they need. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
validate_fetch_url used host_str() which returns bracketed IPv6 (e.g. "[::ffff:7f00:1]") that IpAddr::parse() cannot handle, silently skipping IP-based SSRF checks for all IPv6 URLs. Switch to url::Host enum matching to extract proper IpAddr values without string parsing. IPv4-mapped IPv6 addresses like ::ffff:127.0.0.1 are now correctly unwrapped and blocked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds test_activation_criteria_enforce_limits to verify that enforce_limits() correctly trims excess patterns (>5), keywords (>20), and tags (>10), and filters out short keywords/tags (<3 chars). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 6 tests covering: tool name path separator rejection, empty name rejection, nonexistent file handling, invalid WASM bytes rejection, dotfile discovery behavior, and subdirectory non-recursion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove plan files from repo (ilblackdragon review) - Replace CLAUDE.md test tier rules with pointer to check-boundaries.sh - Add Check 4 to check-boundaries.sh: enforces integration tests are gated behind the 'integration' feature flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Check 5 catches try_connect() and similar silent-skip patterns in integration tests. Tests should use feature gates to fail loudly when prerequisites are missing, not silently return. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge origin/main into the PR branch, resolving merge conflicts in src/testing.rs by keeping both the branch's StubChannel/harness tests and main's database CRUD coverage tests. Fix the Tests (all-features) CI failure caused by workspace_integration tests trying to connect to PostgreSQL when none is available. The --all-features flag enables the integration feature, which gates these tests, but CI has no PostgreSQL service. Changed the CI test matrix to use explicit feature flags instead of --all-features, excluding the integration feature (which requires a running database). Also applied cargo fmt to files touched by the merge. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the project's testing capabilities and security posture. It introduces new test doubles and builders to simplify writing robust tests, particularly for channel management and web gateway interactions. A new script enforces architectural boundaries, promoting code quality and maintainability. Crucially, it addresses several security vulnerabilities and edge cases through dedicated regression tests for skill installation, URL fetching (SSRF), and WASM tool loading, ensuring the system is more resilient against malicious inputs and configurations. The changes also improve the reliability of search functionality by covering various edge cases in reciprocal rank fusion. Highlights
Changelog
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request significantly improves the test infrastructure with StubChannel and TestGatewayBuilder, and introduces a new test tiering system. It also enhances security by addressing an IPv6 SSRF bypass in validate_fetch_url and adding comprehensive regression tests for ZIP extraction and WASM loading. However, a high-severity vulnerability was identified: the SSRF protection remains incomplete as it does not account for domain names resolving to private IP addresses, posing a DNS-based SSRF bypass risk. Additionally, a suggestion was made to improve the robustness of the check-boundaries.sh script.
| let raw_ip = match &host { | ||
| url::Host::Ipv4(v4) => Some(std::net::IpAddr::V4(*v4)), | ||
| url::Host::Ipv6(v6) => Some(std::net::IpAddr::V6(*v6)), | ||
| url::Host::Domain(_) => None, | ||
| }; | ||
| if let Some(raw_ip) = raw_ip { |
There was a problem hiding this comment.
The validate_fetch_url function is vulnerable to SSRF via DNS resolution. While it correctly checks for private/loopback IP addresses when the host is provided as an IP, it skips these checks when a domain name is used (line 407). An attacker can bypass the SSRF protection by using a domain name that resolves to a private IP address (e.g., 127.0.0.1.nip.io).
To remediate this, you should resolve the domain name to its IP addresses and validate each IP against the restricted ranges before allowing the request to proceed. Alternatively, configure the HTTP client with a custom resolver or socket factory that enforces these restrictions at the connection level.
There was a problem hiding this comment.
Addressed in commit ced6b96 (already on the branch before this review round). The build_safe_fetch_client function now performs async DNS resolution via tokio::net::lookup_host and validates all resolved IPs against private/loopback/link-local ranges through validate_resolved_addrs. This prevents DNS-based SSRF bypass via domains like 127.0.0.1.nip.io. The HTTP client is also configured with resolve_to_addrs to pin the connection to the validated IPs, preventing TOCTOU races between resolution and connection.
There was a problem hiding this comment.
Addressed in ced6b96. The skill fetch path now resolves hostnames before connecting, rejects any private/loopback/link-local result, and pins the validated addresses into reqwest with resolve_to_addrs so the request cannot re-resolve to a different target.
| tier_violations="" | ||
| for test_file in tests/*.rs; do | ||
| [ -f "$test_file" ] || continue | ||
|
|
||
| # Check if the file actually connects to a database (imports DB types | ||
| # or calls pool/connect). Mere string references like "DATABASE_URL" | ||
| # in config tests don't count. | ||
| needs_gate=false | ||
| if grep -q 'PgPool\|tokio_postgres::\|create_pool\|\.connect(' "$test_file" 2>/dev/null; then | ||
| needs_gate=true | ||
| fi | ||
|
|
||
| if [ "$needs_gate" = true ]; then | ||
| # Check first 5 lines for the cfg gate | ||
| if ! head -5 "$test_file" | grep -q 'cfg.*feature.*integration' 2>/dev/null; then | ||
| tier_violations="$tier_violations\n $test_file: needs #![cfg(all(feature = \"postgres\", feature = \"integration\"))]" | ||
| fi | ||
| fi | ||
| done | ||
|
|
||
| if [ -n "$tier_violations" ]; then | ||
| echo "VIOLATION: Integration tests missing feature gate:" | ||
| echo -e "$tier_violations" | ||
| echo | ||
| echo "(Tests requiring external services must be gated behind the 'integration' feature)" | ||
| violations=$((violations + 1)) | ||
| else | ||
| echo "OK" | ||
| fi |
There was a problem hiding this comment.
The current method of building the tier_violations string by concatenation is a bit fragile. It can lead to issues if filenames contain special characters, and echo -e has known portability and security concerns compared to printf. Using a bash array to collect violations and printf to print them would be more robust and safer.
Here's a suggested refactoring of this check:
tier_violations=()
for test_file in tests/*.rs; do
[ -f "$test_file" ] || continue
# Check if the file actually connects to a database (imports DB types
# or calls pool/connect). Mere string references like "DATABASE_URL"
# in config tests don't count.
needs_gate=false
if grep -q 'PgPool\|tokio_postgres::\|create_pool\|\.connect(' "$test_file" 2>/dev/null; then
needs_gate=true
fi
if [ "$needs_gate" = true ]; then
# Check first 5 lines for the cfg gate
if ! head -5 "$test_file" | grep -q 'cfg.*feature.*integration' 2>/dev/null; then
tier_violations+=(" $test_file: needs '#![cfg(all(feature = \"postgres\", feature = \"integration\"))]'")
fi
fi
done
if [ ${#tier_violations[@]} -gt 0 ]; then
echo "VIOLATION: Integration tests missing feature gate:"
printf '%s\n' "${tier_violations[@]}"
echo
echo "(Tests requiring external services must be gated behind the 'integration' feature)"
violations=$((violations + 1))
else
echo "OK"
fiThere was a problem hiding this comment.
Addressed in ec6e328. Refactored Check 4 to use a bash array (tier_violations=()) and printf '%s\n' instead of string concatenation with echo -e, as suggested.
|
Known gap: DNS-based SSRF bypass. The fix handles IP literals but a domain like evil.attacker.com that Code Issues
|
…heck Refactor Check 4 in check-boundaries.sh to use bash arrays and printf instead of string concatenation with echo -e. This is more robust with special characters in filenames and avoids portability concerns with echo -e. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Feedback AddressedBoth review comments from gemini-code-assist have been addressed: 1. SSRF DNS bypass (High severity) -
|
|
Hi @zmanian, Thank you for the update and for addressing the feedback! I appreciate you taking the time to implement these improvements. Just a quick clarification: my initial comment (Comment 1) was a summary of the pull request's changes, not a review with specific issues. The detailed review comments you're referring to were provided by @ilblackdragon in Comment 2. Regarding the points you've addressed:
However, there are a few other points from @ilblackdragon's review that still need attention:
Please let me know if you have any questions about these remaining points. |
…ests, search edge cases (nearai#623) * feat(testing): add StubChannel test double for Channel trait Adds StubChannel to src/testing.rs alongside StubLlm. Supports message injection via mpsc sender, response/status capture, and configurable health check toggling. Includes handle methods for use after ownership transfer to ChannelManager. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(testing): wire StubChannel into TestHarnessBuilder Add with_stub_channel() builder method that creates a StubChannel pre-registered in a ChannelManager. Tests can inject messages via the sender and verify routing through the manager. The channel field on TestHarness is Optional, defaulting to None for backward compat. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: gate external-service tests behind integration feature flag Replace silent try_connect() skip pattern with explicit feature gating. cargo test now runs only self-contained tests. cargo test --features integration runs tests requiring PostgreSQL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(channels): add ChannelManager unit tests using StubChannel Cover add/start_all stream merging, respond routing, unknown channel errors, health_check_all with mixed health, empty-channels error path, and injection channel merging -- all via StubChannel test double. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: document test tier separation (unit/integration/live) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add architecture boundary check script Grep-based checks for three architecture boundaries: - Direct database driver usage (tokio_postgres/libsql) outside src/db/ - .unwrap()/.expect() in production code (warning only) - Direct std::env::var reads outside config layer (warning only) The DB driver check is a hard violation; the other two are warnings for gradual cleanup. Run with: bash scripts/check-boundaries.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(search): add RRF edge case tests for empty inputs, limits, and config modes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(security): add regression tests for skill installer ZIP and SSRF protections Add 11 regression tests covering the security controls in skill_tools: ZIP extraction safety: - Valid SKILL.md extraction works correctly - Non-SKILL.md entries are ignored (returns error) - Path traversal entries (../../SKILL.md) do not match - Nested path entries (subdir/SKILL.md) do not match - Oversized entries (>1MB uncompressed) are rejected SSRF prevention: - Loopback addresses (127.0.0.1) are blocked - Private ranges (10.x, 172.16.x, 192.168.x) are blocked - Link-local addresses (169.254.x) are blocked - Public IPs (8.8.8.8, 1.1.1.1) are allowed - IPv4-mapped IPv6 unwrapping logic works correctly - Metadata endpoints and .internal/.local hostnames are blocked - Normal hostnames (github.com, clawhub.dev) are allowed Also documents a known gap: url::Url::host_str() returns bracketed IPv6 addresses that std::net::IpAddr cannot parse, so IPv4-mapped IPv6 URLs currently bypass IP-based checks in validate_fetch_url. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(testing): extract TestGatewayBuilder to eliminate gateway test duplication Both ws_gateway_integration.rs and openai_compat_integration.rs manually constructed GatewayState with 19+ fields. Extracted to a shared builder in src/channels/web/test_helpers.rs that provides sensible defaults and lets tests override only what they need. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add implementation plans for testing batches 1 and 2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): close IPv6 SSRF bypass in validate_fetch_url validate_fetch_url used host_str() which returns bracketed IPv6 (e.g. "[::ffff:7f00:1]") that IpAddr::parse() cannot handle, silently skipping IP-based SSRF checks for all IPv6 URLs. Switch to url::Host enum matching to extract proper IpAddr values without string parsing. IPv4-mapped IPv6 addresses like ::ffff:127.0.0.1 are now correctly unwrapped and blocked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(skills): add activation criteria limits enforcement tests Adds test_activation_criteria_enforce_limits to verify that enforce_limits() correctly trims excess patterns (>5), keywords (>20), and tags (>10), and filters out short keywords/tags (<3 chars). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(wasm): add security regression tests for WASM tool loader Add 6 tests covering: tool name path separator rejection, empty name rejection, nonexistent file handling, invalid WASM bytes rejection, dotfile discovery behavior, and subdirectory non-recursion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: address PR review feedback - Remove plan files from repo (ilblackdragon review) - Replace CLAUDE.md test tier rules with pointer to check-boundaries.sh - Add Check 4 to check-boundaries.sh: enforces integration tests are gated behind the 'integration' feature flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add try_connect silent-skip pattern check to check-boundaries.sh Check 5 catches try_connect() and similar silent-skip patterns in integration tests. Tests should use feature gates to fail loudly when prerequisites are missing, not silently return. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): harden skill fetch SSRF checks * fix(scripts): use bash arrays in check-boundaries.sh tier violation check Refactor Check 4 in check-boundaries.sh to use bash arrays and printf instead of string concatenation with echo -e. This is more robust with special characters in filenames and avoids portability concerns with echo -e. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ests, search edge cases (nearai#623) * feat(testing): add StubChannel test double for Channel trait Adds StubChannel to src/testing.rs alongside StubLlm. Supports message injection via mpsc sender, response/status capture, and configurable health check toggling. Includes handle methods for use after ownership transfer to ChannelManager. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(testing): wire StubChannel into TestHarnessBuilder Add with_stub_channel() builder method that creates a StubChannel pre-registered in a ChannelManager. Tests can inject messages via the sender and verify routing through the manager. The channel field on TestHarness is Optional, defaulting to None for backward compat. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test: gate external-service tests behind integration feature flag Replace silent try_connect() skip pattern with explicit feature gating. cargo test now runs only self-contained tests. cargo test --features integration runs tests requiring PostgreSQL. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(channels): add ChannelManager unit tests using StubChannel Cover add/start_all stream merging, respond routing, unknown channel errors, health_check_all with mixed health, empty-channels error path, and injection channel merging -- all via StubChannel test double. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: document test tier separation (unit/integration/live) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add architecture boundary check script Grep-based checks for three architecture boundaries: - Direct database driver usage (tokio_postgres/libsql) outside src/db/ - .unwrap()/.expect() in production code (warning only) - Direct std::env::var reads outside config layer (warning only) The DB driver check is a hard violation; the other two are warnings for gradual cleanup. Run with: bash scripts/check-boundaries.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(search): add RRF edge case tests for empty inputs, limits, and config modes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(security): add regression tests for skill installer ZIP and SSRF protections Add 11 regression tests covering the security controls in skill_tools: ZIP extraction safety: - Valid SKILL.md extraction works correctly - Non-SKILL.md entries are ignored (returns error) - Path traversal entries (../../SKILL.md) do not match - Nested path entries (subdir/SKILL.md) do not match - Oversized entries (>1MB uncompressed) are rejected SSRF prevention: - Loopback addresses (127.0.0.1) are blocked - Private ranges (10.x, 172.16.x, 192.168.x) are blocked - Link-local addresses (169.254.x) are blocked - Public IPs (8.8.8.8, 1.1.1.1) are allowed - IPv4-mapped IPv6 unwrapping logic works correctly - Metadata endpoints and .internal/.local hostnames are blocked - Normal hostnames (github.com, clawhub.dev) are allowed Also documents a known gap: url::Url::host_str() returns bracketed IPv6 addresses that std::net::IpAddr cannot parse, so IPv4-mapped IPv6 URLs currently bypass IP-based checks in validate_fetch_url. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor(testing): extract TestGatewayBuilder to eliminate gateway test duplication Both ws_gateway_integration.rs and openai_compat_integration.rs manually constructed GatewayState with 19+ fields. Extracted to a shared builder in src/channels/web/test_helpers.rs that provides sensible defaults and lets tests override only what they need. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: add implementation plans for testing batches 1 and 2 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): close IPv6 SSRF bypass in validate_fetch_url validate_fetch_url used host_str() which returns bracketed IPv6 (e.g. "[::ffff:7f00:1]") that IpAddr::parse() cannot handle, silently skipping IP-based SSRF checks for all IPv6 URLs. Switch to url::Host enum matching to extract proper IpAddr values without string parsing. IPv4-mapped IPv6 addresses like ::ffff:127.0.0.1 are now correctly unwrapped and blocked. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(skills): add activation criteria limits enforcement tests Adds test_activation_criteria_enforce_limits to verify that enforce_limits() correctly trims excess patterns (>5), keywords (>20), and tags (>10), and filters out short keywords/tags (<3 chars). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * test(wasm): add security regression tests for WASM tool loader Add 6 tests covering: tool name path separator rejection, empty name rejection, nonexistent file handling, invalid WASM bytes rejection, dotfile discovery behavior, and subdirectory non-recursion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: address PR review feedback - Remove plan files from repo (ilblackdragon review) - Replace CLAUDE.md test tier rules with pointer to check-boundaries.sh - Add Check 4 to check-boundaries.sh: enforces integration tests are gated behind the 'integration' feature flag Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ci: add try_connect silent-skip pattern check to check-boundaries.sh Check 5 catches try_connect() and similar silent-skip patterns in integration tests. Tests should use feature gates to fail loudly when prerequisites are missing, not silently return. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): harden skill fetch SSRF checks * fix(scripts): use bash arrays in check-boundaries.sh tier violation check Refactor Check 4 in check-boundaries.sh to use bash arrays and printf instead of string concatenation with echo -e. This is more robust with special characters in filenames and avoids portability concerns with echo -e. [skip-regression-check] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Implements testing improvements inspired by OpenClaw's test architecture (see #466).
Batch 1: StubChannel + Test Tier Separation
TestHarnessBuilderfor easy test setupintegrationfeature flag, replacing silenttry_connect()skip patternBatch 2: Gateway Helpers, Security Tests, Search Edge Cases
GatewayStatestruct duplication across 2 integration test filesscripts/check-boundaries.sh): detects direct DB driver usage outside db layer, unwrap/expect in production, env var reads outside configBatch 3: SSRF Fix, WASM Loader Tests, Skills Limits Tests
validate_fetch_url()usedhost_str()which returns bracketed IPv6 thatIpAddr::parse()cannot handle. Switched tourl::Hostenum matching for correct IP extractionenforce_limits()trims excess patterns (>5), keywords (>20), and filters short keywords/tags (<3 chars)Test plan
cargo test --lib-- all 1882 tests pass (up from 1848)cargo clippy --all --tests -- -D warnings-- zero warningscargo test --test ws_gateway_integration-- 10 tests passcargo test --test openai_compat_integration-- 16 tests passbash scripts/check-boundaries.sh-- runs, reports known violations as warningsGenerated with Claude Code