Modern Gemini protocol server and client implementation using asyncio
Project description
Nauyaca - Gemini Protocol Server & Client
A modern, high-performance implementation of the Gemini protocol in Python using asyncio, providing both server and client capabilities.
โจ Why Nauyaca?
Nauyaca (pronounced "now-YAH-kah", meaning "serpent" in Nahuatl) brings modern Python async capabilities to the Gemini protocol:
- ๐ High Performance: Uses asyncio's low-level Protocol/Transport pattern for maximum efficiency
- ๐ Security First: TOFU certificate validation, rate limiting, and access control built-in
- โ๏ธ Production Ready: Comprehensive configuration, middleware system, and systemd integration
- ๐ ๏ธ Developer Friendly: Full type hints, extensive tests, and powered by
uvfor fast dependency management - ๐ Well Documented: Clear architecture docs, security guidelines, and API examples
๐ Table of Contents
- Why Nauyaca?
- Project Overview
- Technology Stack
- Project Structure
- Quick Start
- Core Features
- Configuration
- Architecture
- Testing
- API Examples
- Security Features
- Deployment
- Contributing
- Resources
- Roadmap
๐ Project Overview
What is Gemini?
The Gemini protocol is a modern, privacy-focused alternative to HTTP and the web. It aims to be:
- Simple: Easier to implement than HTTP, harder to extend (by design)
- Privacy-focused: No cookies, no tracking, no JavaScript
- Secure: TLS is mandatory, not optional
- Lightweight: Text-focused content with minimal formatting
- User-centric: Readers control how content is displayed
Think of it as a modern take on Gopher, sitting comfortably between the complexity of the web and the simplicity of plain text.
About Nauyaca
This project implements the Gemini protocol with a focus on performance and security. Unlike typical implementations, Nauyaca uses Python's low-level asyncio Protocol/Transport pattern for efficient, non-blocking network I/O with fine-grained control over connection handling.
Goals
- โ Implement a production-ready Gemini server
- โ Implement a full-featured Gemini client
- โ Support all Gemini protocol features (TLS, client certs, TOFU, etc.)
- โ Provide clean, maintainable, well-documented code
- โ Include comprehensive test coverage
- โ Offer both library and CLI interfaces
Project Status
Current Phase: Security Hardening & Integration Testing
| Feature | Status |
|---|---|
| Core Protocol Implementation | โ Complete |
| TLS 1.2+ Support | โ Complete |
| Server Configuration (TOML) | โ Complete |
| TOFU Certificate Validation | โ Complete |
| Rate Limiting & DoS Protection | โ Complete |
| IP-based Access Control | โ Complete |
| Client Session Management | โ Complete |
| Security Documentation | โ Complete |
| Integration Testing | ๐ง In Progress |
| CLI Interface | ๐ง In Progress |
| Static File Serving | ๐ Planned |
| Content Type Detection | ๐ Planned |
The core protocol and security features are production-ready. CLI and content serving features are being actively developed.
๐ Technology Stack
- Python 3.11+ - Modern Python features and performance
- asyncio - Asynchronous I/O using Protocol/Transport pattern
- ssl - TLS 1.2+ encryption with custom certificate validation
- uv - Fast, modern Python package manager for development
- pytest-asyncio - Async test support and fixtures
- typer - CLI interface with rich terminal output
- cryptography - Certificate handling and TOFU implementation
- tomllib - TOML configuration file parsing
๐ Project Structure
nauyaca/
โโโ README.md
โโโ SECURITY.md # Security documentation
โโโ CONTRIBUTING.md # Contribution guidelines
โโโ CODE_OF_CONDUCT.md # Code of conduct
โโโ pyproject.toml # Project metadata and dependencies (managed by uv)
โโโ uv.lock # Dependency lock file
โโโ config.example.toml # Full configuration example
โโโ config.minimal.toml # Minimal configuration example
โ
โโโ src/
โ โโโ nauyaca/
โ โโโ __init__.py
โ โโโ __main__.py # CLI entry point
โ โ
โ โโโ protocol/
โ โ โโโ __init__.py
โ โ โโโ constants.py # Status codes, MIME types, etc.
โ โ โโโ request.py # Request parsing
โ โ โโโ response.py # Response building
โ โ โโโ status.py # Status code utilities
โ โ
โ โโโ server/
โ โ โโโ __init__.py
โ โ โโโ protocol.py # Server protocol implementation
โ โ โโโ handler.py # Request handler
โ โ โโโ router.py # URL routing
โ โ โโโ config.py # Server configuration
โ โ โโโ middleware.py # Rate limiting, access control
โ โ
โ โโโ client/
โ โ โโโ __init__.py
โ โ โโโ protocol.py # Client protocol implementation
โ โ โโโ session.py # High-level client API
โ โ
โ โโโ security/
โ โ โโโ __init__.py
โ โ โโโ tls.py # TLS context creation
โ โ โโโ certificates.py # Cert generation and management
โ โ โโโ tofu.py # TOFU database
โ โ
โ โโโ utils/
โ โโโ __init__.py
โ โโโ url.py # URL parsing/validation
โ
โโโ tests/
โ โโโ conftest.py # Pytest fixtures
โ โโโ test_protocol/
โ โโโ test_server/
โ โโโ test_client/
โ โโโ test_security/
โ โโโ test_integration/
โ
โโโ docs/
โ โโโ gemini_protocol/ # Gemini protocol reference docs
โ โโโ gemtext.txt
โ
โโโ capsule/ # Example Gemini capsule content directory
๐ Quick Start
Prerequisites
- Python 3.14 or higher
- uv - Fast Python package manager (recommended)
# Install uv if you haven't already curl -LsSf https://astral.sh/uv/install.sh | sh
Installation
Option 1: Standalone CLI tool (recommended for general use)
uv tool install nauyaca
Option 2: As a library (for development or embedding in your project)
# Add to existing project
uv add nauyaca
# Or start a new project
uv init my-gemini-project
cd my-gemini-project
uv add nauyaca
Option 3: From source (for development)
git clone https://github.com/alanbato/nauyaca.git
cd nauyaca
uv sync
Running the Server
# Minimal - serve current directory (uses auto-generated cert)
nauyaca serve ./capsule
# With custom host/port
nauyaca serve ./capsule --host 0.0.0.0 --port 1965
# With configuration file
nauyaca serve --config config.toml
# With TLS certificates
nauyaca serve ./capsule --cert cert.pem --key key.pem
Generate SSL Certificates
# Generate self-signed certificate for testing
nauyaca cert generate --hostname localhost --output ./certs
# For production (with proper hostname)
nauyaca cert generate --hostname gemini.example.com --days 365
Using the Client
# Get a resource
nauyaca get gemini://geminiprotocol.net/
# Get with verbose output showing response headers
nauyaca get gemini://geminiprotocol.net/ --verbose
# Manage TOFU database
nauyaca tofu list
nauyaca tofu trust geminiprotocol.net
nauyaca tofu export backup.toml
nauyaca tofu import backup.toml
nauyaca tofu revoke example.com
As a Library
import asyncio
from nauyaca.client import GeminiClient
async def main():
# Simple fetch with TOFU validation
async with GeminiClient() as client:
response = await client.get("gemini://geminiprotocol.net/")
if response.is_success():
print(f"Content-Type: {response.meta}")
print(response.body)
elif response.is_redirect():
print(f"Redirect to: {response.redirect_url}")
else:
print(f"Error {response.status}: {response.meta}")
asyncio.run(main())
Development Standards
This project follows modern Python best practices:
- PEP 8 style guide compliance
- Ruff for linting and code formatting (replaces Black, isort, flake8)
- mypy for strict type checking
- pytest for comprehensive testing with async support
- uv for fast, reliable dependency management
Running Tests
# Run all tests
uv run pytest
# Run with coverage report
uv run pytest --cov=src/nauyaca --cov-report=html
# Run specific test file
uv run pytest tests/test_protocol/test_request.py
# Run specific test function
uv run pytest tests/test_server/test_handler.py::test_static_file_serving
# Run with verbose output
uv run pytest -v
# Run only unit tests (fast)
uv run pytest -m unit
# Run only integration tests
uv run pytest -m integration
# Watch mode (requires pytest-watch)
uv run ptw
๐ Core Features
Server Features
-
Protocol Implementation
- TLS 1.2+ encryption (mandatory)
- Complete status code support (1x-6x)
- Request URL parsing and validation
- Response header generation
-
Content Serving
- Static file serving
- Directory listings
- MIME type detection
- Gemtext rendering
- CGI script support
- Virtual hosting
-
Security
- Client certificate support (status 6x)
- Certificate-based authentication
- Rate limiting
- Access control lists
- Path traversal protection
-
Features
- URL rewriting and routing
- Logging and monitoring
- Graceful shutdown
- Hot reload (development mode)
- Custom error pages
Client Features
-
Protocol Implementation
- TLS connection handling
- Request sending
- Response parsing
- Redirect following
-
Security
- TOFU certificate validation
- Certificate pinning
- Client certificate support
- Known hosts database
-
Features
- Async/await API
- Connection pooling
- Response caching
- Timeout handling
- Retry logic
- CLI interface
๐ง Configuration
Server Configuration
The server supports TOML configuration files for persistent settings. Command-line arguments override config file values.
Minimal Configuration
Create a config.toml file with just the essentials:
[server]
# Required: Path to your gemini content
document_root = "./capsule"
# All other settings use sensible defaults:
# - host: localhost
# - port: 1965
# - TLS: auto-generated self-signed certificate (for testing only!)
# - Rate limiting: enabled with default limits
# - Access control: allow all
Full Configuration Example
For production deployments, use a complete configuration:
[server]
host = "0.0.0.0"
port = 1965
document_root = "./capsule"
certfile = "./certs/cert.pem"
keyfile = "./certs/key.pem"
[rate_limit]
enabled = true
capacity = 10 # Max burst size (requests)
refill_rate = 1.0 # Requests per second
retry_after = 30 # Seconds to wait when limited
[access_control]
# IP-based access control (supports CIDR notation)
allow_list = ["192.168.1.0/24", "10.0.0.1"]
deny_list = ["203.0.113.0/24"]
default_allow = true # Default policy when no lists match
Using Configuration Files
# Load configuration from file
nauyaca serve --config config.toml
# Override specific settings
nauyaca serve --config config.toml --host 0.0.0.0 --port 11965
# Without config file (all settings from CLI)
nauyaca serve ./capsule --host localhost --port 1965
Client Configuration
The client uses TOFU (Trust-On-First-Use) certificate validation with a local database:
# TOFU database location
~/.nauyaca/known_hosts.db
# List known hosts
nauyaca tofu list
# Export known hosts for backup
nauyaca tofu export backup.toml
# Import known hosts
nauyaca tofu import backup.toml
# Revoke trust for a host
nauyaca tofu revoke example.com
# Manually trust a host (connects and retrieves certificate)
nauyaca tofu trust example.com
๐ Architecture
Key Design Decisions
- asyncio Protocol Pattern: Low-level control, high performance
- Plugin Architecture: Extensible handler system
- TOFU by Default: Privacy-focused certificate validation
- Stateless: Each request is independent (no sessions)
- Type Hints: Full typing for better IDE support and error catching
๐งช Testing
Nauyaca has comprehensive test coverage across multiple layers:
Test Organization
-
Unit Tests (
@pytest.mark.unit)- Test individual components in isolation
- Mock external dependencies
- Fast execution for rapid development feedback
-
Integration Tests (
@pytest.mark.integration)- Test component interactions
- Real network connections (localhost only)
- TLS handshake validation
-
Security Tests
- TOFU certificate validation
- Rate limiting behavior
- Access control enforcement
- Path traversal protection
Test Coverage
Current test coverage focuses on:
- โ Protocol parsing and validation
- โ TLS configuration and certificate handling
- โ TOFU database operations (store, verify, export, import)
- โ Server middleware (rate limiting, access control)
- โ Configuration loading and validation
- ๐ง End-to-end client/server integration (in progress)
Run uv run pytest --cov=src/nauyaca --cov-report=html to generate a detailed coverage report.
๐ API Examples
Using Nauyaca as a Library
import asyncio
from nauyaca.client import GeminiClient
async def main():
# Create client with TOFU validation enabled (default)
async with GeminiClient() as client:
# Simple GET request
response = await client.get("gemini://geminiprotocol.net/")
# Check response status
if response.is_success():
print(f"Content-Type: {response.meta}")
print(response.body)
elif response.is_redirect():
print(f"Redirect to: {response.redirect_url}")
elif 10 <= response.status < 20:
# Input required (status 1x)
print(f"Input requested: {response.meta}")
else:
print(f"Error {response.status}: {response.meta}")
asyncio.run(main())
Advanced Configuration
from nauyaca.client import GeminiClient
# Custom timeout and redirect settings
async with GeminiClient(timeout=60, max_redirects=3) as client:
response = await client.get("gemini://geminiprotocol.net/")
# Disable redirect following
async with GeminiClient() as client:
response = await client.get(
"gemini://geminiprotocol.net/",
follow_redirects=False
)
For more advanced usage, see the integration tests which demonstrate server and client usage patterns.
๐ Security Features
Nauyaca implements multiple layers of security to protect both servers and clients. See SECURITY.md for complete security documentation.
TLS Security
Mandatory TLS 1.2+
- All Gemini connections require TLS 1.2 or higher
- No plaintext fallback - non-TLS connections rejected
- Strong cipher suites enforced by default
- Self-signed certificates supported (TOFU model)
# Automatic strong TLS configuration
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
TOFU (Trust-On-First-Use) Certificate Validation
How TOFU Works:
- First Connection: Accept certificate, store SHA-256 fingerprint
- Subsequent Connections: Verify certificate matches stored fingerprint
- Certificate Change: Prompt user for confirmation (may be renewal or MITM attack)
TOFU Management:
# List all known hosts with fingerprints
nauyaca tofu list
# Export for backup/sharing
nauyaca tofu export backup.toml
# Import from backup
nauyaca tofu import backup.toml
# Revoke trust for compromised host
nauyaca tofu revoke example.com
# Manually trust a certificate (connects and retrieves it)
nauyaca tofu trust example.com
Storage: Certificates stored in ~/.nauyaca/known_hosts.db (SQLite database)
Rate Limiting & DoS Protection
Token Bucket Algorithm
- Industry-standard rate limiting per client IP
- Configurable capacity (burst size) and refill rate
- Automatic cleanup of idle rate limiters (memory efficient)
- Returns status
44 SLOW DOWNwhen limits exceeded
Configuration:
[rate_limit]
enabled = true
capacity = 10 # Max burst size
refill_rate = 1.0 # Requests per second
retry_after = 30 # Seconds to wait when limited
Example Rate Limits:
- Personal capsule: capacity=5, refill_rate=0.5 (restrictive)
- Public server: capacity=20, refill_rate=2.0 (generous)
- High-traffic: capacity=50, refill_rate=5.0 (very generous)
IP-based Access Control
Allow/Deny Lists with CIDR Support
- Individual IPs:
10.0.0.1 - IPv4 networks:
192.168.1.0/24 - IPv6 networks:
2001:db8::/32 - Configurable default policy (allow or deny)
Configuration:
[access_control]
allow_list = ["192.168.1.0/24", "10.0.0.1"] # Whitelist
deny_list = ["203.0.113.0/24"] # Blacklist
default_allow = true # Default policy
Processing Order:
- Check deny list โ reject if match
- Check allow list โ accept if match
- Apply default policy
Use Cases:
- Private capsule: Set
default_allow = false, add trusted IPs to allow_list - Public server: Set
default_allow = true, add abusive IPs to deny_list
Request Validation & Protection
Size Limits:
- Maximum request size: 1024 bytes (per Gemini spec)
- Oversized requests receive status
59 BAD REQUEST
Timeout Protection:
- Default request timeout: 30 seconds
- Slow clients receive status
40 TIMEOUT - Prevents slow-loris attacks
Path Traversal Protection:
# All file paths canonicalized and validated
safe_path = (root / requested_path).resolve()
if not safe_path.is_relative_to(root):
return Response(status=51, meta='Not found') # Never expose path info
Client Certificate Support
Mutual TLS (mTLS):
- Server can request client certificates for authentication
- Status codes
60-62for certificate-based access control - Certificate fingerprint validation
Generate Client Certificate:
nauyaca cert generate-client --name "My Identity"
Security Best Practices
For Server Operators:
- Use proper certificates (CA-signed or self-signed with TOFU)
- Keep private keys secure (file mode 0600)
- Enable rate limiting appropriate to your traffic
- Use whitelist mode for private capsules
- Monitor logs for suspicious activity
- Keep document root clean of sensitive files
For Client Users:
- Verify certificate fingerprints on first connection
- Be suspicious of unexpected certificate changes
- Keep TOFU database backed up
- Use separate certificates for different identities
- Check redirect destinations before following
Important: See SECURITY.md for:
- Complete security documentation
- Vulnerability reporting process
- Known limitations
- Deployment guidelines
- Compliance information
๐ Deployment
Systemd Service Example
For production deployments on Linux systems with systemd:
[Unit]
Description=Nauyaca Gemini Protocol Server
After=network.target
[Service]
Type=simple
User=nauyaca
Group=nauyaca
WorkingDirectory=/opt/nauyaca
ExecStart=/usr/local/bin/nauyaca serve --config /etc/nauyaca/config.toml
Restart=always
RestartSec=10
# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/nauyaca/capsule
[Install]
WantedBy=multi-user.target
Save this as /etc/systemd/system/nauyaca.service, then:
# Enable and start the service
sudo systemctl enable nauyaca
sudo systemctl start nauyaca
# Check status
sudo systemctl status nauyaca
# View logs
sudo journalctl -u nauyaca -f
๐ค Contributing
We welcome contributions! Follow these steps to get started.
Development Setup
# Clone the repository
git clone https://github.com/alanbato/nauyaca.git
cd nauyaca
# Install dependencies with uv
uv sync
# Run tests to verify setup
uv run pytest
# Run linting
uv run ruff check src/ tests/
# Run type checking
uv run mypy src/
Development Workflow
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Write/update tests
- Ensure tests pass:
uv run pytest - Run linting:
uv run ruff check src/ tests/ - Run type checking:
uv run mypy src/ - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open a Pull Request
Commit Convention
Follow Conventional Commits:
feat: Add client certificate support
fix: Handle edge case in URL parsing
docs: Update API reference
test: Add integration tests for server
refactor: Simplify protocol parsing logic
๐ License
MIT License - see LICENSE file for details
๐ Resources
Gemini Protocol
- Gemini Protocol Homepage - Official Gemini protocol website
- Awesome Gemini - Curated list of Gemini resources
Python & Asyncio
- Python asyncio Documentation - Official asyncio docs
- asyncio Protocol/Transport - Low-level networking
- Real Python - Async IO - Comprehensive tutorial
Nauyaca Documentation
- SECURITY.md - Security features and best practices
- CONTRIBUTING.md - Contribution guidelines
- CODE_OF_CONDUCT.md - Code of conduct
๐ฌ Support & Community
- ๐ Documentation: See SECURITY.md, CONTRIBUTING.md, and project docs
- ๐ Bug Reports: GitHub Issues
- ๐ก Feature Requests: GitHub Discussions
- ๐ค Contributing: See Contributing section above
- ๐ง Security Issues: See SECURITY.md for responsible disclosure
Getting Help
- Check the documentation (SECURITY.md, CONTRIBUTING.md)
- Search existing GitHub Issues
- Ask questions in GitHub Discussions
- Review the integration tests for usage examples
๐บ๏ธ Roadmap
Version 0.2.0 (Current)
- โ Core protocol implementation
- โ Security features (TOFU, rate limiting, access control)
- โ Configuration system
- ๐ง Integration testing
- ๐ง CLI interface completion
Version 0.3.0 (Next)
- Static file serving
- Content type detection
- Directory listings
- Error page templates
- Performance optimization
Version 1.0.0 (Stable)
- Production-ready release
- Stable API
- Complete documentation
- Performance benchmarks
- Migration guides
๐ Acknowledgments
- Solderpunk for creating the Gemini protocol
- The Gemini community for feedback and inspiration
- Contributors and testers who help improve Nauyaca
Development Status: This project is in active development (pre-1.0). Core protocol and security features are stable, but the high-level API may change based on community feedback.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nauyaca-0.2.0.tar.gz.
File metadata
- Download URL: nauyaca-0.2.0.tar.gz
- Upload date:
- Size: 43.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c29f67bab24c3f31bfbbb66de4c1881946679f8b0684dadf90f35066436f7bf
|
|
| MD5 |
8ff255606adf44d3318d8fa2af81d43d
|
|
| BLAKE2b-256 |
dca242d0b744e7a61a6b983d66031e8019f562bf6e47a1cb8cd0234089b24446
|
Provenance
The following attestation bundles were made for nauyaca-0.2.0.tar.gz:
Publisher:
release-pypi.yml on alanbato/nauyaca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nauyaca-0.2.0.tar.gz -
Subject digest:
5c29f67bab24c3f31bfbbb66de4c1881946679f8b0684dadf90f35066436f7bf - Sigstore transparency entry: 711866537
- Sigstore integration time:
-
Permalink:
alanbato/nauyaca@c65463f6e1ccfde3a4ae80e831a1eff84e85fe14 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/alanbato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@c65463f6e1ccfde3a4ae80e831a1eff84e85fe14 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nauyaca-0.2.0-py3-none-any.whl.
File metadata
- Download URL: nauyaca-0.2.0-py3-none-any.whl
- Upload date:
- Size: 54.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4716ac85fe38c3e2cf9e71301d39cee0ee83127ed37cc7d7b9329714c885aab
|
|
| MD5 |
020e74585fcd77a0717f8aed3ab0bc10
|
|
| BLAKE2b-256 |
9ec25d69434df85b3321a0c28553bbbc63989e8cb5a94b1eaf650850983c7795
|
Provenance
The following attestation bundles were made for nauyaca-0.2.0-py3-none-any.whl:
Publisher:
release-pypi.yml on alanbato/nauyaca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nauyaca-0.2.0-py3-none-any.whl -
Subject digest:
d4716ac85fe38c3e2cf9e71301d39cee0ee83127ed37cc7d7b9329714c885aab - Sigstore transparency entry: 711866539
- Sigstore integration time:
-
Permalink:
alanbato/nauyaca@c65463f6e1ccfde3a4ae80e831a1eff84e85fe14 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/alanbato
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release-pypi.yml@c65463f6e1ccfde3a4ae80e831a1eff84e85fe14 -
Trigger Event:
push
-
Statement type: