An adversarial review of the request path turned up nine issues, all
reproduced against a running server before and after the fix.
The most serious was a permanent denial of service. The in-flight request
counter was decremented on paths that never incremented it; being unsigned
it wrapped to usize::MAX, after which every request was answered with
"41 Server unavailable" until the process restarted. A client that completed
the TLS handshake and disconnected without sending a request was enough --
a port scan, a health check, a cancelled page load. The counter is replaced
by a semaphore permit released on every exit path.
Two further remote denials of service: an error from accept() propagated out
of main and ended the process, so exhausting the descriptor limit killed the
server; and responses were read whole into memory, so cost scaled with
concurrent requests times file size. Accept errors are now logged and retried
after a backoff, and responses stream in 64 KiB chunks capped at 64 MiB.
Peers that open a socket and never send a ClientHello are bounded by a 10s
handshake timeout and a connection cap (max_connections, default 512).
Every virtual host was served whichever certificate came first in a HashMap,
which varies per process, so a host's certificate changed between restarts.
Because Gemini clients pin certificates on first use, this trained users to
dismiss the mismatch warning that would otherwise reveal interception.
Certificates are now selected by SNI with a deterministic fallback, so
unknown hosts still complete a handshake and receive 53.
Dotfiles inside a content root were public, exposing .git/config and any
credentials in it for capsule roots that are git working copies. Path
resolution is now in-tree: reject non-plain components, then compare
canonical prefixes. This replaces path-security, an unaudited micro-crate in
the security boundary that also rejected legitimate filenames containing
'%', '~' or '$'.
rustls moves 0.21 -> 0.23; the 0.21 branch is end of life. TLS 1.2 and 1.3
only, as before.
Malformed requests logged attacker-controlled text at ERROR on every request,
letting a client drive log volume; client-caused conditions now log at debug,
truncated. Hostname routing accepts the authority forms the specification
permits (mixed case, explicit port, userinfo, trailing dot), which previously
returned 53. Per-host port and log_level were accepted and silently ignored,
and now warn at startup.
Adds tests/connection_lifecycle.rs covering the counter underflow, the
oversized-response refusal, hidden files and authority normalization. Fixes
the test client, which sent host:port as SNI. 42 -> 56 tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Test that the server correctly reports missing certificate errors,
rejects invalid hostnames, fails gracefully on port conflicts, and
starts successfully with multiple virtual hosts configured.
- Remove custom logging module and init_logging function
- Update main.rs to use tracing_subscriber with EnvFilter
- Remove log_level from global config structure
- Update documentation and tests to use RUST_LOG
- Format long lines in config.rs and test files for better readability
- Remove BACKLOG.md from git tracking (file kept locally)
- Add BACKLOG.md to .gitignore to prevent future commits
- Backlog files should be local project documentation, not in version control
- Add hostname-based request routing for multiple capsules per server
- Parse virtual host configs from TOML sections ([hostname])
- Implement per-host certificate and content isolation
- Add comprehensive virtual host testing and validation
- Update docs and examples for multi-host deployments
This enables Pollux to serve multiple Gemini domains from one instance,
providing the foundation for multi-tenant Gemini hosting.
- Update Cargo.toml version to 1.0.0
- Revise README.md: document available CLI options (--config, --test-processing-delay), update config format
- Update INSTALL.md: change user from gemini to pollux, simplify certificate setup, remove Let's Encrypt instructions
- Update systemd service user to pollux
- Add comprehensive CHANGELOG.md documenting all v1.0.0 features
- Remove references to eliminated CLI options (--root, --cert, --key, --host, --port)
Key features in v1.0.0:
- Rate limiting with configurable concurrent requests
- Comprehensive config validation and error handling
- Custom logging system with structured output
- Security features: path traversal protection, URI validation
- Systemd integration and complete installation guide
- Full test suite (22 tests) with zero warnings
- Remove TestEnvironment struct and return TempDir from setup function
- Update tests to compute paths from temp_dir.path() on-demand
- Eliminate unused field warnings and reduce code complexity
- Maintain all test functionality with cleaner design
- Create shared tests/common.rs with TestEnvironment setup
- Simplify gemini_test_client.py to single-request client
- Refactor config validation tests to use common setup
- Add test_valid_config_startup for complete server validation
- Fix clippy warning in main.rs
- Remove unused code and consolidate test infrastructure
- Move tests to use std::env::temp_dir() instead of ./tmp
- Generate test certificates on-demand with openssl
- Create isolated test environments with automatic cleanup
- Add comprehensive config validation integration tests
- Temporarily simplify rate limiting test (complex TLS testing deferred)
- Tests now work out-of-the-box for fresh repository clones
- Run tests sequentially to avoid stderr mixing in parallel execution
- Update README.md to mention Python 3 requirement for integration tests
- Make rate limiting test skip gracefully if Python 3 is not available
- Move and rename test helper script to tests/gemini_test_client.py
- Update test to use new script path
- Improve test documentation and error handling
- Test concurrent requests with max_concurrent_requests = 1
- Verify 1 successful response and 4 rate limited responses
- Use python test script for TLS Gemini requests
- Test runs with 3-second processing delay for proper concurrency
- Validates rate limiting behavior end-to-end
- Remove tokio signal handling and mutex-based TLS acceptor reloading
- Simplify main loop back to basic connection acceptance
- Update systemd service to remove ExecReload
- Change certbot hook to use systemctl restart instead of reload
- Maintain <1s restart time for certificate updates
- Eliminate user confusion about partial config reloading
- Add tokio signal handling for SIGHUP
- Implement thread-safe TLS acceptor reloading with Mutex
- Modify main loop to handle signals alongside connections
- Update systemd service (already had ExecReload)
- Add certbot hook script documentation to INSTALL.md
- Enable zero-downtime certificate renewal support
- Replace 'host' config with separate 'bind_host' and 'hostname'
- bind_host: IP/interface for server binding (default 0.0.0.0)
- hostname: Domain for URI validation (required)
- Update all parsing and validation code
- Create dist/ directory with systemd service, config, and install guide
- Add comprehensive INSTALL.md with setup instructions
- Parameter only used in debug builds, unused in release
- Prefix with underscore to indicate intentional non-use
- Eliminates compiler warning in release builds
- Preserves testing functionality in debug builds
- Replace tracing compact format with custom FormatEvent
- Eliminate double space padding between timestamp and log level
- Clean single-space formatting: timestamp level message
- Maintain all existing log content and functionality
- Remove CLI options except --config and --test-processing-delay
- Enforce 1026 byte request limit per Gemini spec (1024 + 2 for CRLF)
- Add comprehensive URL parsing with host and port validation
- Reject malformed URIs and wrong ports with 59 Bad Request
- Update tests for new URL parsing signature
- Fix clippy warning in port parsing
- Log request paths (/big-file.mkv) instead of file system paths
- Custom timestamp format: YYYY-MM-DDTHH:MM:SSZ (no milliseconds)
- Update serve_file to accept request parameter for proper logging
- Strip gemini://host prefix from logged requests for cleaner logs
- Add time crate for custom timestamp formatting
- Replace eprintln! with tracing macros for configurable log levels
- Set up tracing_subscriber with level filtering from config
- Log format: YYYY-MM-DDTHH:MM:SSZ LEVEL IP "request" STATUS "message"
- Success logs: INFO level for 20 responses
- Error logs: WARN for 41/51, ERROR for 59
- Rate limiting and file serving now properly logged
- Remove unused RequestLogger::log_success method
Display the configured max_concurrent_requests value in the server
startup information, consistent with other configuration values like
log level, host, port, etc.
- Add max_concurrent_requests config option (default: 1000)
- Implement global AtomicUsize counter for concurrent request tracking
- Return status 41 'Server unavailable' when limit exceeded
- Proper counter management with decrements on all exit paths
- Add comprehensive config validation (1-1,000,000 range)
- Update documentation with rate limiting details
- Add unit tests for config parsing
- Thread-safe implementation using Ordering::Relaxed
This provides effective DDoS protection by limiting concurrent
connections to prevent server overload while maintaining
configurability for different deployment scenarios.
- Update .gitignore for dev/ and tmp/ directories
- Move certificate setup to dev/ directory with README
- Remove production references from AGENTS.md
- Clean up directory structure and documentation
- Repository now ready for development workflow
- Complete Gemini server implementation with logging
- Add comprehensive documentation (README.md, AGENTS.md)
- Implement certificate management guidelines
- Add .gitignore for security and build artifacts
- All unit tests passing (14/14)
- Ready for production deployment