Skip to content
This repository was archived by the owner on Apr 29, 2026. It is now read-only.

⏺ HTTP client implementation (#247) is complete. Here's a summary:#255

Merged
navicore merged 5 commits intomainfrom
i-247
Jan 13, 2026
Merged

⏺ HTTP client implementation (#247) is complete. Here's a summary:#255
navicore merged 5 commits intomainfrom
i-247

Conversation

@navicore
Copy link
Copy Markdown
Owner

Changes Made
#247

Runtime (crates/runtime/):

  • Created http_client.rs with 4 HTTP builtins:
    • http.get - GET request (url -- response-map)
    • http.post - POST request (url body content-type -- response-map)
    • http.put - PUT request (url body content-type -- response-map)
    • http.delete - DELETE request (url -- response-map)
  • Added ureq dependency for HTTP/HTTPS with TLS support
  • Response maps contain: status (Int), body (String), ok (Bool), error (String optional)

Compiler (crates/compiler/):

  • runtime.rs: Added runtime declarations and builtin mappings
  • ast.rs: Registered HTTP builtins
  • builtins.rs: Added type signatures and documentation

Tests & Examples:

  • tests/integration/src/test-http.seq - Integration tests for HTTP operations
  • examples/http-client.seq - Example demonstrating HTTP usage

Documentation:

  • Updated BATTERIES_INCLUDED.md with HTTP client details

API Example

GET request

"https://httpbin.org/get" http.get
dup "ok" map.get drop
if
"body" map.get drop io.write-line else "error" map.get drop "Error: " swap string.concat io.write-line then

POST JSON

"https://api.example.com/users" "{"name":"Alice"}" "application/json" http.post

  Changes Made

  Runtime (crates/runtime/):
  - Created http_client.rs with 4 HTTP builtins:
    - http.get - GET request (url -- response-map)
    - http.post - POST request (url body content-type -- response-map)
    - http.put - PUT request (url body content-type -- response-map)
    - http.delete - DELETE request (url -- response-map)
  - Added ureq dependency for HTTP/HTTPS with TLS support
  - Response maps contain: status (Int), body (String), ok (Bool), error (String optional)

  Compiler (crates/compiler/):
  - runtime.rs: Added runtime declarations and builtin mappings
  - ast.rs: Registered HTTP builtins
  - builtins.rs: Added type signatures and documentation

  Tests & Examples:
  - tests/integration/src/test-http.seq - Integration tests for HTTP operations
  - examples/http-client.seq - Example demonstrating HTTP usage

  Documentation:
  - Updated BATTERIES_INCLUDED.md with HTTP client details

  API Example

  # GET request
  "https://httpbin.org/get" http.get
  dup "ok" map.get drop
  if
    "body" map.get drop io.write-line
  else
    "error" map.get drop "Error: " swap string.concat io.write-line
  then

  # POST JSON
  "https://api.example.com/users" "{\"name\":\"Alice\"}" "application/json" http.post
@navicore navicore mentioned this pull request Jan 12, 2026
7 tasks
@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

Pull Request Review: HTTP Client Implementation (#255)

Overall Assessment

Status: ✅ APPROVED with minor suggestions

This is a well-implemented feature that adds HTTP client functionality to Seq. The implementation is clean, secure, and follows the existing codebase patterns. Great work!


🎯 Strengths

1. Excellent Code Quality

  • Clean, idiomatic Rust with proper error handling
  • Consistent patterns with existing runtime functions (e.g., file.rs, crypto.rs)
  • Good use of the ureq library for HTTP operations
  • Proper C ABI exports with #[unsafe(no_mangle)]

2. Strong Security Practices

  • ✅ TLS support enabled via ureq features
  • ✅ Response body size limit (10 MB) prevents DoS via large responses
  • ✅ Timeout protection (30 seconds) prevents hanging requests
  • ✅ No credential leakage in error messages
  • ✅ Safe error handling without exposing internal details

3. Comprehensive Testing

  • Unit tests for response building logic (http_client.rs:353-447)
  • Integration tests using httpbin.org (test-http.seq)
  • Good example code (examples/http-client.seq)
  • Tests handle network failures gracefully

4. Good API Design

  • Consistent response format (Map with status, body, ok, error)
  • Stack-based API fits the Seq language model perfectly
  • Clear separation between success (2xx) and error responses
  • Proper documentation with stack effects

5. Documentation

  • Excellent module-level documentation
  • Updated BATTERIES_INCLUDED.md appropriately
  • Good examples showing error handling patterns

🔍 Issues Found

Critical Issues: None ✅

High Priority Issues: None ✅

Medium Priority Issues

1. Dependency Version Mismatch

Location: Cargo.toml:67

ureq = { version = "2.10", features = ["tls"] }

Issue: The PR adds ureq 2.12.1 (per Cargo.lock), but Cargo.toml specifies 2.10. This works due to semver compatibility, but could cause confusion.

Recommendation: Update to match actual version:

ureq = { version = "2.12", features = ["tls"] }

2. Code Duplication in HTTP Methods

Location: crates/runtime/src/http_client.rs:186-351

Issue: The four HTTP methods (perform_get, perform_post, perform_put, perform_delete) have significant duplication in:

  • Agent creation with timeout
  • Response handling logic
  • Error handling for Status and Transport errors
  • Body size checking

Recommendation: Consider extracting common logic:

fn execute_request<F>(request_fn: F) -> Value
where
    F: FnOnce(ureq::Agent) -> Result<ureq::Response, ureq::Error>
{
    let agent = ureq::AgentBuilder::new()
        .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
        .build();
    
    handle_response(request_fn(agent))
}

This is not critical but would improve maintainability.

Low Priority Issues

3. Missing Response Headers Access

Location: API design

Observation: The current API only returns status code and body, but many HTTP use cases need headers (e.g., Content-Type, Location for redirects, custom headers).

Suggestion: Consider a future enhancement to include headers in the response map:

response "headers" map.get  # Returns a Map of header name -> value

Not blocking for this PR, but worth considering for future iterations.

4. No Custom Header Support

Location: perform_post, perform_put functions

Observation: POST/PUT only allow setting Content-Type but not custom headers (e.g., Authorization, X-API-Key).

Suggestion: Future enhancement could accept a headers map:

url body content-type headers-map http.post

Again, not blocking - current implementation is fine for v1.


📊 Test Coverage

Strengths:

  • ✅ Unit tests for response building logic
  • ✅ Integration tests for all four HTTP methods
  • ✅ Error handling tests (404, connection errors)
  • ✅ Response structure validation

Suggestions:

  • Consider adding tests for:
    • Large response body (near 10 MB limit)
    • Different content types (not just JSON)
    • Redirect handling behavior
    • Timeout scenarios (current test would be slow, so optional)

🔒 Security Assessment

✅ No security vulnerabilities found

Good practices observed:

  1. Response size limiting prevents memory exhaustion
  2. Timeouts prevent resource exhaustion
  3. TLS enabled by default (good!)
  4. No unsafe string operations or buffer overflows
  5. Error messages don't leak sensitive data
  6. No SSRF vulnerabilities (user controls URL, as expected)

Note: SSRF (Server-Side Request Forgery) is possible since users can specify any URL, but this is expected behavior for an HTTP client. Applications using this should validate URLs if needed.


⚡ Performance Considerations

Strengths:

  • ✅ Reasonable timeout (30s)
  • ✅ Response size limit prevents memory issues
  • ✅ Agent creation per request (simple, correct)

Observations:

  1. Creating a new Agent per request adds slight overhead. For high-throughput scenarios, connection pooling would help, but ureq handles this internally to some extent.
  2. The 30-second timeout is reasonable for most use cases.

Verdict: Performance is appropriate for the use case. No changes needed.


📝 Documentation Quality

Excellent documentation throughout:

  • Clear module-level docs with examples
  • Stack effects documented for all functions
  • Response map structure clearly specified
  • Good inline comments explaining error cases

Small suggestion: Add a note in the documentation about thread safety if Seq supports concurrent HTTP requests.


✨ Recommendations Summary

Must Fix (blocking)

None - this PR is ready to merge!

Should Fix (non-blocking)

  1. Update ureq version in Cargo.toml to 2.12 to match Cargo.lock
  2. Consider refactoring to reduce code duplication between HTTP methods

Nice to Have (future work)

  1. Add header access in response maps
  2. Add custom header support for requests
  3. Add more edge case tests (large bodies, redirects, etc.)

🎉 Conclusion

This is a high-quality implementation that adds valuable functionality to Seq. The code is secure, well-tested, and follows best practices. The minor issues noted above are primarily about maintainability and future enhancements, not correctness.

Recommendation: ✅ Approve and merge after addressing the version mismatch in Cargo.toml (if desired).

Great work on this feature! 🚀


Review generated with ❤️ by Claude Code

…s now compile and pass.

  Fixes made:
  1. examples/http-client.seq - Added dup before "error" map.has? to preserve the map
  2. tests/integration/src/test-http.seq - Multiple fixes:
    - Removed helper function with type annotation issue
    - Changed >=, <=, = to i.>=, i.<=, i.= (Seq integer comparison operators)
    - Fixed stack management throughout

  Test results:
  - 8/8 HTTP integration tests pass
  - All workspace tests pass
  - Example compiles successfully
@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

Pull Request Review: HTTP Client Implementation

Summary

This PR adds HTTP client functionality to patch-seq with GET, POST, PUT, and DELETE operations using the ureq crate. The implementation is well-structured and includes comprehensive tests and examples.

Code Quality: ✅ Good

Strengths

  1. Clean Architecture: Separation between compiler integration (builtins.rs, ast.rs, runtime.rs) and runtime implementation (http_client.rs) is well done
  2. Consistent Error Handling: All HTTP operations follow the same response map pattern with status, body, ok, and error fields
  3. Comprehensive Documentation: Excellent module-level docs with usage examples and API contracts
  4. Good Test Coverage: Unit tests for response building logic and integration tests covering all HTTP methods
  5. Stack Safety: Proper assertions and type checking in all unsafe FFI functions

Code Quality Observations

  • Excellent: Well-structured helper functions (build_response_map, error_response) reduce code duplication
  • Good: Clear separation between public FFI functions and internal implementation functions
  • Good: Consistent naming conventions following Rust and project standards

Potential Issues: ⚠️ Several concerns

1. Code Duplication (Medium Priority)

The four perform functions (perform_get, perform_post, perform_put, perform_delete) have ~90% identical code. Consider refactoring:

fn perform_request(request: ureq::Request) -> Value {
    match request.call() {
        Ok(response) => handle_success(response),
        Err(e) => handle_error(e),
    }
}

fn perform_get(url: &str) -> Value {
    let agent = build_agent();
    perform_request(agent.get(url))
}

Location: crates/runtime/src/http_client.rs:186-351

2. Dependency Version Mismatch (Low Priority)

  • Cargo.toml specifies ureq = "2.10"
  • Cargo.lock shows ureq = "2.12.1"

This mismatch could cause confusion. Consider updating Cargo.toml to 2.12 or use =2.10 for exact pinning.

Location: Cargo.toml:67

3. Missing Headers Support (Enhancement)

The current implementation doesn't allow custom HTTP headers. This limits use cases like:

  • Authentication (Bearer tokens, API keys)
  • Custom User-Agent strings
  • Content negotiation (Accept headers)

Consider adding http.get-with-headers or extending the current API in a future PR.

4. Agent Creation Overhead (Low Priority)

Each HTTP call creates a new agent instance. For high-frequency operations, this could impact performance. Consider:

  • Documenting this behavior
  • Or caching the agent (with thread-safety considerations)

Location: All perform_* functions create agents on line 187, 230, 273, 316

5. Stack Management in Examples (Already Fixed) ✅

The second commit message indicates stack management issues were fixed. Good catch!

Security: ✅ Generally Good

Strengths

  1. TLS Support: Enabled via ureq with rustls (good choice, avoids OpenSSL complexities)
  2. Timeout Protection: 30-second timeout prevents indefinite hangs
  3. Response Size Limit: 10MB max protects against memory exhaustion
  4. No User Input in URLs: URLs come from Seq strings, type-checked by compiler

Security Concerns

1. SSRF Vulnerability (High Priority) ⚠️

The implementation doesn't restrict which URLs can be accessed. This allows Server-Side Request Forgery attacks:

# Attacker could access internal services
"http://localhost:6379/admin" http.get
"http://169.254.169.254/latest/meta-data/" http.get  # AWS metadata
"http://internal-database:5432/" http.get

Recommendation: Add documentation warning about SSRF in untrusted contexts, or implement optional URL filtering.

2. Error Message Information Disclosure (Low Priority)

Error messages might reveal internal network topology:

error_response(format!("Connection error: {}", e))

For production use, consider sanitizing error messages in user-facing contexts.

3. No Certificate Validation Controls (Low Priority)

Currently uses default certificate validation. Consider whether users might need to:

  • Use custom CA certificates
  • Disable validation for testing (with clear warnings)

Performance: ✅ Good

Considerations

  1. Blocking I/O: All operations are synchronous (appropriate for the language design)
  2. Memory Efficient: Streaming response bodies into strings
  3. Reasonable Limits: 10MB max, 30s timeout are sensible defaults

Suggestion

Document that these operations block the calling thread. In future, consider:

  • Making timeouts configurable via environment variables
  • Adding async support (much larger effort)

Test Coverage: ✅ Excellent

Strengths

  1. Unit Tests: Cover response map building logic
  2. Integration Tests: All 8 tests cover real HTTP operations
  3. Error Scenarios: 404, connection errors, timeouts
  4. Examples: Comprehensive demo file with all operations

Minor Gap

No tests for:

  • Very large responses (near 10MB limit)
  • Malformed URLs
  • Redirect handling

Best Practices: ✅ Mostly Following

Following

  • ✅ Proper use of #[cfg(test)] for unit tests
  • ✅ Clear documentation comments
  • ✅ Consistent error handling patterns
  • ✅ Type safety with Value enum

Minor Issues

  1. Cargo.lock in version control: This is correct for applications but creates noise in PRs. (Not an issue, just noting)
  2. unwrap_or_default() on line 213, 259, 302, 341: Good defensive programming
  3. Constant definitions: Well-placed at module top

Documentation: ✅ Excellent

  • Clear module-level documentation with examples
  • Updated BATTERIES_INCLUDED.md
  • Integration test doubles as usage documentation
  • Stack effect comments on all public functions

Recommendations

Must Fix (Before Merge)

None - the code is mergeable as-is.

Should Fix (Near Future)

  1. Add SSRF warning to documentation - Critical for security-conscious users
  2. Refactor code duplication - Improves maintainability
  3. Align ureq version - Update Cargo.toml to match Cargo.lock

Nice to Have (Future PRs)

  1. Custom headers support
  2. Configurable timeouts
  3. Response streaming for large bodies
  4. More comprehensive error types

Conclusion

This is a solid, well-implemented PR that adds valuable functionality to patch-seq. The code quality is high, testing is comprehensive, and the API design is clean and consistent with the rest of the codebase.

Approval: ✅ Recommend Merge

The identified issues are minor and don't block merging. The SSRF consideration should be documented, but it's inherent to any HTTP client and users of patch-seq should be aware of general security practices.

Great work on the implementation! The stack management fixes in the second commit show good attention to detail.


Reviewed by: Claude (AI Code Review)
Review Date: 2026-01-12

🤖 Generated with Claude Code

  1. Code Duplication Fixed (http_client.rs)
  - Extracted build_agent() - creates configured HTTP agent with timeout
  - Extracted handle_response() - handles Result<Response, Error> conversion to Value
  - Reduced ~170 lines of duplicated code to ~75 lines
  - Each perform function is now 1-6 lines instead of ~40 lines

  2. Version Mismatch Fixed (Cargo.toml)
  - Updated ureq from "2.10" to "2.12" to match Cargo.lock

  Before:
  fn perform_get(url: &str) -> Value {
      let agent = ureq::AgentBuilder::new()...  // 40+ lines of duplicated logic
  }
  fn perform_post(...) -> Value { /* same 40 lines */ }
  fn perform_put(...) -> Value { /* same 40 lines */ }
  fn perform_delete(...) -> Value { /* same 40 lines */ }

  After:
  fn perform_get(url: &str) -> Value {
      handle_response(build_agent().get(url).call())
  }
  fn perform_post(url: &str, body: &str, content_type: &str) -> Value {
      handle_response(build_agent().post(url).set("Content-Type", content_type).send_string(body))
  }
  // etc.

  All tests pass.
@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

Pull Request Review: HTTP Client Implementation

Thank you for this comprehensive implementation! This is a well-structured PR that adds HTTP client capabilities to patch-seq.


✅ Strengths

Code Quality

  • Excellent refactoring: The third commit eliminated ~170 lines of duplication by extracting build_agent() and handle_response()
  • Clean architecture: Clear separation between public FFI functions and internal implementation
  • Good documentation: Comprehensive module-level docs with usage examples
  • Consistent error handling: Unified error response format across all operations

Implementation Details

  • Stack safety: Proper assertions and type checking in all FFI functions
  • Defensive programming: MAX_BODY_SIZE limit (10MB) prevents memory exhaustion
  • Reasonable defaults: 30-second timeout is appropriate
  • Good dependency choice: ureq - blocking, minimal dependencies, built-in TLS via rustls

Testing

  • Integration tests: 8 tests covering all HTTP methods and error cases
  • Practical examples: http-client.seq demonstrates real-world usage
  • Network-aware: Tests gracefully handle network failures

🔍 Issues & Recommendations

CRITICAL: Security - SSRF Vulnerability

Location: crates/runtime/src/http_client.rs:232-258

Issue: No URL validation allows Server-Side Request Forgery (SSRF) attacks:

  • Access internal services (http://localhost:6379)
  • Scan internal networks (http://192.168.1.0/24)
  • Access cloud metadata (http://169.254.169.254/latest/meta-data/)

Recommendation: Add URL validation or document risks clearly


HIGH: Missing Header Support

Location: crates/runtime/src/http_client.rs:237-253

Issue: Cannot set custom headers (Authorization, User-Agent, X-API-Key)
Impact: Cannot authenticate with most real-world APIs

Recommendation: Add header parameter or builder pattern


MEDIUM: Performance - Agent Reuse

Location: crates/runtime/src/http_client.rs:186-190

Issue: New Agent created per request, losing connection pooling benefits

Recommendation: Use static agent with LazyLock for connection reuse


MEDIUM: Error Information Loss

Location: crates/runtime/src/http_client.rs:214-222

Issue: Generic error messages for HTTP errors (4xx, 5xx)

Recommendation: Include body excerpt in error message for better debugging


LOW: Test Coverage Gaps

Missing tests:

  • Large response bodies (near MAX_BODY_SIZE)
  • Timeout behavior
  • Redirects handling
  • Non-UTF-8 responses

LOW: Magic Numbers

Location: crates/runtime/src/http_client.rs:39-43

Consider documenting why 30s timeout and 10MB limit were chosen. Consider making configurable.


MINOR: Documentation

Location: docs/BATTERIES_INCLUDED.md

Add security considerations section covering:

  • SSRF risks with user-provided URLs
  • Timeout behavior
  • Body size limits
  • TLS certificate validation

📊 Code Metrics

  • Lines added: 845
  • Files changed: 11
  • Test coverage: 8 integration tests
  • Commits: 3 (with iterative improvements)

🏁 Verdict

Recommendation: Approve with Required Changes

Solid implementation with clean code and good documentation. However, SSRF vulnerability is a blocker.

Required Before Merge:

  1. CRITICAL: Add SSRF protection or document security risks
  2. 🔧 MEDIUM: Use static agent for connection pooling

Recommended for Follow-up:

  1. Add header support (needed for auth)
  2. Improve error messages
  3. Add timeout/large response tests
  4. Document security considerations

💡 Future Enhancements

  • Rate limiting support
  • Binary response support (http.get-bytes)
  • Streaming for large responses
  • Request middleware/hooks

Great work! Once SSRF is addressed, this will be a valuable addition to patch-seq.

Files Reviewed: http_client.rs, ast.rs, builtins.rs, runtime.rs, test-http.seq, http-client.seq, BATTERIES_INCLUDED.md, Cargo files

…eview:

  1. SSRF Security Documentation (CRITICAL)
  - Added comprehensive security section to http_client.rs module docs
  - Added security considerations to BATTERIES_INCLUDED.md
  - Documents risks: internal service access, network scanning, cloud metadata
  - Documents mitigations: URL validation, allowlists, IP blocking, egress filtering

  2. Connection Pooling (MEDIUM)
  - Added static HTTP_AGENT: LazyLock<ureq::Agent> for shared agent
  - All HTTP methods now use the pooled agent
  - Benefits: connection reuse, better performance for multiple requests

  Changes summary:
  - http_client.rs: Added security docs, LazyLock agent, removed per-request agent creation
  - BATTERIES_INCLUDED.md: Added security considerations section
  - Cargo.toml: Updated ureq version to 2.12

  The review also mentioned these as future enhancements (not blocking):
  - Custom header support (needed for auth)
  - Improved error messages with body excerpts
  - Additional tests (large bodies, timeouts, redirects)
@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

PR Review: HTTP Client Implementation (#255)

Summary

This PR implements a comprehensive HTTP client for the Seq language, adding GET, POST, PUT, and DELETE operations with TLS support via the ureq crate. The implementation is well-structured and addresses previous review feedback with security documentation and connection pooling.


✅ Strengths

1. Code Quality & Architecture

  • Excellent refactoring: The code went through multiple iterations to eliminate duplication. The final build_agent() and handle_response() helper functions are clean and DRY.
  • Proper abstraction: The four HTTP methods share common logic effectively, reducing the codebase from ~170 lines of duplication to ~75 lines.
  • Good use of Rust idioms: LazyLock for the global agent, proper error handling with Result<>, and clear separation of concerns.

2. Security Awareness

  • SSRF documentation: Comprehensive warnings about Server-Side Request Forgery vulnerabilities in both the code and documentation (lines 32-46 in http_client.rs).
  • Resource limits: 30-second timeout and 10 MB max body size prevent abuse (lines 50-51).
  • Secure TLS: Uses rustls instead of OpenSSL, avoiding common pitfall dependencies.

3. Testing

  • Integration tests: 8 comprehensive tests in test-http.seq covering success, error, and structure validation.
  • Unit tests: 3 unit tests in Rust for response map building logic.
  • Real-world example: Well-documented example file showing practical usage patterns.

4. Documentation

  • Module-level docs: Excellent rustdoc comments with API examples, response format, and security considerations.
  • Builtin docs: Proper documentation for each builtin function.
  • User documentation: Updated BATTERIES_INCLUDED.md with security warnings.

🔍 Issues & Concerns

1. Security: SSRF Not Mitigated (HIGH PRIORITY)

Location: crates/runtime/src/http_client.rs:257-283

While the documentation warns about SSRF, the implementation does nothing to prevent it. This is a significant security risk if Seq is used in any server-side context.

Issue: User-controlled URLs can access:

  • Internal services: http://localhost:6379 (Redis), http://127.0.0.1:5432 (PostgreSQL)
  • Cloud metadata: http://169.254.169.254/latest/meta-data/ (AWS credentials)
  • Internal networks: http://192.168.1.x/admin

Recommendation: Add URL validation function that blocks internal addresses. Consider making this opt-out via feature flag for development.

@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

2. Error Handling: Body Size Check After Reading (MEDIUM)

Location: crates/runtime/src/http_client.rs:226-231

The body is fully read into memory before checking size. A 100 MB response will consume memory before being rejected.

Fix: Use ureq built-in limits:

static HTTP_AGENT: LazyLock<ureq::Agent> = LazyLock::new(|| {
    ureq::AgentBuilder::new()
        .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
        .max_response_body_size(MAX_BODY_SIZE)
        .build()
});

3. Missing: Custom Headers (MEDIUM)

No way to pass custom headers (especially Authorization). This severely limits API usage with authenticated endpoints. Document this limitation in BATTERIES_INCLUDED.md for now.

4. Testing: Missing Edge Cases

The test coverage is good but could include:


🎯 Performance Considerations

Positive:

  • ✅ Connection pooling via shared agent
  • ✅ Reasonable timeout prevents hangs
  • ✅ Body size limit

Concerns:

  • Blocking I/O: ureq is blocking. Multiple concurrent HTTP requests will block threads.
  • No streaming: Entire response must fit in memory.

@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

🧪 Test Coverage Assessment

Coverage: Good (8 integration tests + 3 unit tests)

Strengths:

  • Tests structure of response maps ✅
  • Tests success and error paths ✅
  • Tests all four HTTP methods ✅
  • Tests connection errors ✅

Gaps: timeout behavior, redirect handling, max body size enforcement, invalid URLs


📝 Documentation Quality

Excellent. The documentation is thorough and includes API examples, response format, security warnings, and resource limits.

Suggestion: Add a "Limitations" section mentioning:

  • Blocking I/O
  • No custom headers (yet)
  • No streaming support
  • 10 MB body limit

🚀 Recommendations

Must Fix (Before Merge):

  1. Add URL validation to prevent SSRF (or document why not needed)
  2. Fix body size check - use ureq built-in max_response_body_size

Should Fix (Before Merge):

  1. Add tests for edge cases (timeout, redirects, large bodies)
  2. Verify M type annotation follows Seq conventions

Nice to Have (Future PR):

  1. Custom header support for authentication
  2. Document blocking I/O limitation
  3. Add redirect control

✅ Final Assessment

Code Quality: 8/10 - Clean, well-refactored, good Rust practices
Security: 6/10 - Good awareness but no actual SSRF mitigation
Testing: 7/10 - Solid coverage but missing edge cases
Documentation: 9/10 - Excellent docs with security warnings

Overall: Solid implementation with good iterative improvement. Main concerns are SSRF and body size check.

Recommendation: Request changes for issues #1 and #2, then approve. 🚦


👍 Great Work!

The incremental improvements through multiple commits show excellent response to feedback. The refactoring from 170 lines of duplication to clean helper functions demonstrates good engineering discipline. Once the security issues are addressed, this will be a valuable feature!

…implemented:

  SSRF Protection Implementation:

  1. URL Validation (validate_url_for_ssrf)
    - Parses URL and validates scheme (only http/https allowed)
    - Blocks localhost and *.localhost hostnames
    - Resolves hostname to IP and checks against blocked ranges
  2. Blocked IP Ranges:
    - Loopback: 127.0.0.0/8
    - Private: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
    - Link-local: 169.254.0.0/16 (cloud metadata endpoints)
    - IPv6: loopback, link-local (fe80::/10), unique local (fc00::/7)
    - IPv4-mapped IPv6 addresses are also checked
  3. Blocked Schemes:
    - file://, ftp://, gopher://, etc.
  4. Tests Added: 8 new unit tests covering:
    - Localhost blocking
    - Loopback IP blocking
    - Private range blocking
    - Link-local blocking
    - Invalid scheme blocking
    - Public URL allowance
    - IPv4/IPv6 dangerous address detection

  Other improvements in this session:
  - Connection pooling via LazyLock<ureq::Agent>
  - Code deduplication with handle_response()
  - Updated ureq version to 2.12
  - Added url crate for URL parsing
@claude
Copy link
Copy Markdown

claude Bot commented Jan 12, 2026

Pull Request Review: HTTP Client Implementation (#255)

Summary

This PR implements a comprehensive HTTP client for Seq with GET, POST, PUT, DELETE operations, built-in SSRF protection, and TLS support via ureq. The implementation is well-structured and follows good security practices. Overall, this is a solid addition to the Seq standard library.

✅ Strengths

1. Security - SSRF Protection

Excellent work on SSRF mitigation. The implementation blocks:

  • Localhost and loopback addresses (127.x.x.x, ::1)
  • Private networks (10.x.x.x, 172.16-31.x.x, 192.168.x.x)
  • Link-local addresses (169.254.x.x) - critical for cloud metadata protection
  • IPv6 private ranges
  • Non-HTTP schemes (file://, ftp://, etc.)

The validation happens before requests are made, which is the correct approach.

Code reference: crates/runtime/src/http_client.rs:139-196

2. Resource Limits

  • 30-second timeout prevents hanging requests
  • 10 MB body size limit prevents memory exhaustion
  • Connection pooling via shared agent instance

3. Comprehensive Testing

  • Integration tests cover all HTTP methods
  • Tests include error handling scenarios
  • SSRF protection has thorough unit tests
  • Examples demonstrate proper usage patterns

4. API Design

The response map structure is clean and idiomatic:

{ "status": 200, "body": "...", "ok": true, "error": "..." }

5. Documentation

  • Excellent inline documentation in http_client.rs
  • Updated BATTERIES_INCLUDED.md appropriately
  • Comprehensive examples in examples/http-client.seq

🔍 Issues & Concerns

1. CRITICAL: DNS Resolution Timing Issue

Location: crates/runtime/src/http_client.rs:172-191

match addr_str.to_socket_addrs() {
    Ok(addrs) => {
        for addr in addrs {
            if is_dangerous_ip(addr.ip()) {
                return Err(format!(...));
            }
        }
    }
    Err(_) => {
        // DNS resolution failed - allow the request to proceed
        // (ureq will handle the DNS error appropriately)
    }
}

Problem: This creates a Time-of-Check-Time-of-Use (TOCTOU) race condition. The DNS lookup happens during validation, but ureq will perform another DNS lookup when making the actual request. Between these two lookups:

  • DNS records could change (TTL expires, cache invalidation)
  • An attacker could use short TTLs to return a public IP during validation and a private IP during the actual request
  • Round-robin DNS could return different IPs

Attack Scenario:

  1. Attacker sets up DNS with 1-second TTL
  2. First lookup (validation): Returns public IP → passes validation
  3. TTL expires
  4. Second lookup (ureq): Returns 127.0.0.1 → bypasses protection

Recommendation: This is difficult to fix perfectly with ureq since it doesn't expose DNS resolution hooks. Options:

  1. Accept the risk but document it clearly
  2. Switch to a lower-level HTTP client (like hyper with custom connectors) that allows DNS resolution control
  3. Add a note in the documentation about the TOCTOU limitation

2. MODERATE: IPv6 IPv4-Mapped Address Edge Case

Location: crates/runtime/src/http_client.rs:118-123

// IPv4-mapped IPv6 addresses: check the embedded IPv4
if let Some(ipv4) = ip.to_ipv4_mapped() {
    return is_dangerous_ipv4(ipv4);
}

Issue: This only handles the mapped format (::ffff:127.0.0.1). IPv6 has multiple ways to represent IPv4:

  • IPv4-mapped: ::ffff:192.168.1.1 ✅ (handled)
  • IPv4-compatible (deprecated but exists): ::192.168.1.1 ❌ (not handled)
  • 6to4: 2002:c0a8:0101:: (maps to 192.168.1.1) ❌ (not handled)

Recommendation:

// Add to is_dangerous_ipv6
// IPv4-compatible (deprecated): ::w.x.y.z
if segments[0..6] == [0, 0, 0, 0, 0, 0] {
    let ipv4 = Ipv4Addr::new(
        (segments[6] >> 8) as u8, (segments[6] & 0xff) as u8,
        (segments[7] >> 8) as u8, (segments[7] & 0xff) as u8
    );
    return is_dangerous_ipv4(ipv4);
}

3. MINOR: Error Information Leak

Location: crates/runtime/src/http_client.rs:184-186

return Err(format!(
    "Blocked: {} resolves to private/internal IP {}",
    host, addr.ip()
));

Issue: Revealing the resolved IP address in error messages could leak information about internal network structure to attackers.

Recommendation: Simplify to:

return Err(format!("Blocked: {} resolves to private/internal IP", host));

4. MINOR: Missing URL Decoding in Host Check

Location: crates/runtime/src/http_client.rs:166-169

URLs can contain percent-encoded characters. An attacker might try:

  • http://local%68ost/ → decodes to localhost
  • http://127.0.0.%31/ → decodes to 127.0.0.1

Status: The url crate handles this correctly by decoding the host automatically, so this is likely not exploitable. But worth verifying with a test.

Recommendation: Add a test case:

#[test]
fn test_ssrf_blocks_encoded_localhost() {
    assert!(validate_url_for_ssrf("http://local%68ost/").is_err());
    assert!(validate_url_for_ssrf("http://127.0.0.%31/").is_err());
}

5. MINOR: Response Body Size Check Timing

Location: crates/runtime/src/http_client.rs:348-357

match response.into_string() {
    Ok(body) => {
        if body.len() > MAX_BODY_SIZE {
            error_response(format!(...))
        }
    }
}

Issue: The entire response body is read into memory before checking the size limit. For a malicious server sending 100MB, this will consume 100MB before rejecting it.

Recommendation: Use ureq's into_reader() and check content-length header:

let content_length = response.header("content-length")
    .and_then(|v| v.parse::<usize>().ok());

if let Some(len) = content_length {
    if len > MAX_BODY_SIZE {
        return error_response(format!("Response too large..."));
    }
}

// Then read with limit

6. MINOR: Consistent Error Handling in Tests

Location: tests/integration/src/test-http.seq:76-82

: test-http-connection-error ( -- )
  # Use a non-routable IP to trigger connection error
  "http://192.0.2.1:12345/test" http.get

Issue: This test uses 192.0.2.1 which is in TEST-NET-1 (documentation range), but the SSRF protection blocks private ranges. This test will fail with "Blocked: private/internal IP" instead of "Connection error".

Recommendation: Use a public IP with closed port, or add a test exemption, or use a different approach.

🎯 Code Quality

Positive

  • Clean separation of concerns
  • Comprehensive inline documentation
  • Good use of Rust safety features (LazyLock, proper error handling)
  • Consistent naming conventions
  • Well-structured tests

Minor Suggestions

  1. Constants Location: Consider moving constants to a module-level config struct for easier customization
  2. Logging: Consider adding optional debug logging for troubleshooting (behind a feature flag)
  3. Custom Headers: Future enhancement could allow custom headers (User-Agent, Authorization, etc.)

📊 Performance Considerations

Good:

  • Connection pooling via shared agent
  • Efficient use of ureq (minimal dependencies)
  • 30-second timeout prevents resource exhaustion

⚠️ Consider:

  • All HTTP operations are synchronous/blocking. This is fine for current Seq usage, but document this behavior.
  • The 30-second timeout is fixed. Consider making it configurable in the future.

🔒 Security Rating

Aspect Rating Notes
SSRF Protection 🟡 B+ Strong baseline, but TOCTOU issue
TLS Security 🟢 A Uses rustls (modern, secure)
Input Validation 🟢 A Proper URL parsing
Resource Limits 🟢 A- Timeout and size limits (minor timing issue)
Error Handling 🟡 B Some information leakage

✅ Test Coverage

  • ✅ All HTTP methods (GET, POST, PUT, DELETE)
  • ✅ Success cases
  • ✅ Error cases (404, network errors)
  • ✅ Response structure validation
  • ✅ SSRF protection unit tests
  • ⚠️ Missing: Redirect handling tests, large response tests, timeout tests

📝 Recommendations Summary

Must Fix (Before Merge)

  1. Fix test-http-connection-error test - Currently will fail due to SSRF blocking TEST-NET

Should Fix (Before Merge)

  1. Document TOCTOU limitation - Add clear warning in code comments and documentation
  2. Fix response body size check - Check content-length header before reading full body

Consider for Follow-up PR

  1. IPv6 IPv4-compatible address handling
  2. Error message information leakage reduction
  3. Timeout configurability
  4. Custom headers support
  5. Add test for URL encoding edge cases

🎉 Conclusion

This is a high-quality implementation that adds valuable functionality to Seq. The code is well-structured, secure by default, and properly tested. The SSRF protection is notably thorough, which is rare to see in HTTP client implementations.

The TOCTOU DNS issue is the main concern, but it's a known limitation of validating URLs before passing them to a third-party HTTP client. With proper documentation, this is acceptable for v1.

Recommendation:Approve with minor changes (fix the test, add TOCTOU documentation)


Great work on this implementation! The security-first approach is exactly what Seq needs in its standard library.

@navicore navicore merged commit b7b4b94 into main Jan 13, 2026
2 checks passed
@navicore navicore deleted the i-247 branch January 13, 2026 00:08
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant