solongate 0.2.0
Security Gateway for AI Tools and MCP Servers
Requires Python: >=3.9
Keywords: ai, gateway, llm, mcp, security

# SolonGate Python SDK

Security Gateway for AI Tools and MCP Servers.

[![PyPI](https://img.shields.io/pypi/v/solongate)](https://pypi.org/project/solongate/)
[![Python](https://img.shields.io/pypi/pyversions/solongate)](https://pypi.org/project/solongate/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

## Installation

```bash
pip install solongate
```

## Quick Start

### Option 1: Cloud API (Recommended)

```python
from solongate import SolonGateAPI

# Initialize with API key
api = SolonGateAPI(api_key="sg_live_xxxxxxxxxxxxxxxx")

# Validate a tool call
result = api.validate("file.read", {"path": "/home/user/doc.txt"})

if result.allowed:
    print(f"Allowed! Token: {result.token}")
else:
    print(f"Denied: {result.decision.reason}")
```

### Option 2: Self-Hosted

```python
from solongate import SolonGate, SolonGateConfig

# Initialize with local configuration
gate = SolonGate(config=SolonGateConfig(
    token_secret="your-32-character-minimum-secret-key",
))

# Validate a tool call
decision = gate.validate("file.read", {"path": "/home/user/doc.txt"})
```

## API Keys

SolonGate uses API keys for authentication:

| Key Prefix | Environment | Usage |
|------------|-------------|-------|
| `sg_live_` | Production | Real security enforcement |
| `sg_test_` | Development | Testing without affecting production |

Get your API key at [solongate.com/dashboard](https://solongate.com/dashboard)

```python
# Using environment variable
import os
os.environ["SOLONGATE_API_KEY"] = "sg_live_xxx"

api = SolonGateAPI()  # Automatically uses env var
```

## MCP Integration

### Protecting an MCP Server

```python
from mcp.server import Server
from solongate import create_secure_server, PathConstraints

# Create your MCP server
server = Server("my-secure-server")

# Wrap with SolonGate security
secure_server = create_secure_server(
    server,
    gateway_secret="your-32-character-secret-key...",
    root_directory="/home/user",  # Sandbox boundary
    allowed_paths=["/home/user/documents/**"],
    denied_paths=["/home/user/.ssh/**", "/home/user/.env"],
)

# Register tools - they're automatically protected
@secure_server.tool()
async def read_file(path: str) -> str:
    """Read a file from the filesystem."""
    with open(path) as f:
        return f.read()

@secure_server.tool()
async def write_file(path: str, content: str) -> bool:
    """Write content to a file."""
    with open(path, "w") as f:
        f.write(content)
    return True
```

### Securing an MCP Client

```python
from mcp import ClientSession
from solongate import create_secure_client, create_read_only_policy_set

async with ClientSession(...) as session:
    # Wrap with security
    client = create_secure_client(
        session,
        token_secret="your-32-character-secret-key...",
        policy_set=create_read_only_policy_set("file.*"),
    )

    # Calls are automatically validated
    result = await client.call_tool("file.read", {"path": "/home/user/doc.txt"})
```

## Security Features

### 1. Default-Deny Policy

All actions are denied unless explicitly allowed:

```python
from solongate import SolonGate, Permission, PolicyEffect

gate = SolonGate()

# Without explicit allow rules, everything is denied
decision = gate.evaluate("any.tool", Permission.EXECUTE)
print(decision.effect)  # PolicyEffect.DENY
```

### 2. Input Sanitization

Automatic detection of malicious inputs:

```python
from solongate import sanitize_input

# Path traversal attack
result = sanitize_input("../../../etc/passwd")
print(result.safe)  # False
print(result.threats[0].threat_type)  # ThreatType.PATH_TRAVERSAL

# Shell injection
result = sanitize_input("file.txt; rm -rf /")
print(result.safe)  # False
print(result.threats[0].threat_type)  # ThreatType.SHELL_INJECTION
```

### 3. Path-Level Isolation

Sandbox access to specific directories:

```python
from solongate import PathConstraints, is_path_allowed

constraints = PathConstraints(
    root_directory="/home/user",
    allowed=["/home/user/documents/**", "/home/user/downloads/**"],
    denied=["/home/user/.ssh/**", "/home/user/.env"],
)

is_path_allowed("/home/user/documents/file.txt", constraints)  # True
is_path_allowed("/etc/passwd", constraints)  # False (outside root)
is_path_allowed("/home/user/.ssh/id_rsa", constraints)  # False (denied)
```

### 4. Capability Tokens

Short-lived, single-use tokens prevent replay attacks:

```python
from solongate import SolonGate, SolonGateConfig

gate = SolonGate(config=SolonGateConfig(
    token_secret="your-32-character-minimum-secret-key",
    token_ttl_seconds=30,  # Token expires in 30 seconds
))

# Create a token for a specific operation
token = gate.create_capability_token("file.read", "read:/home/user")
print(token.token)       # JWT string
print(token.expires_at)  # Unix timestamp
print(token.nonce)       # Unique nonce for replay protection
```

### 5. Server-Side Verification

MCP servers can verify incoming requests:

```python
from solongate import ServerVerifier, VerificationConfig

verifier = ServerVerifier(VerificationConfig(
    gateway_secret="your-32-character-secret-key...",
))

# Verify incoming token
result = verifier.verify_request(token, expected_tool="file.read")

if not result.valid:
    raise PermissionError(result.error)

# Replay protection - same token fails second time
result2 = verifier.verify_request(token)
print(result2.valid)  # False - "Token already used (replay detected)"
```

### 6. Policy Versioning

Immutable, versioned policies with rollback:

```python
from solongate import PolicyStore, create_default_deny_policy_set

store = PolicyStore()

# Save initial policy
policy = create_default_deny_policy_set()
v1 = store.save_version(policy, "Initial policy", "admin")
print(v1.version)  # 1
print(v1.hash)     # SHA256 hash for integrity

# Make changes and save new version
# ... modify policy ...
v2 = store.save_version(updated_policy, "Add file.read permissions", "admin")
print(v2.version)  # 2

# Rollback if needed
v3 = store.rollback(policy.id, to_version=1)
print(v3.version)  # 3 (new version with v1 content)
```

## Framework Integration

### FastAPI

```python
from fastapi import FastAPI, Request
from solongate import ServerVerifier, VerificationConfig, create_fastapi_middleware

app = FastAPI()
verifier = ServerVerifier(VerificationConfig(gateway_secret="..."))

@app.middleware("http")
async def solongate_middleware(request: Request, call_next):
    return await create_fastapi_middleware(verifier)(request, call_next)

@app.post("/api/tool/{tool_name}")
async def call_tool(tool_name: str, request: Request):
    # Request is already verified by middleware
    verification = request.state.solongate_verification
    # ... handle tool call
```

### Flask

```python
from flask import Flask
from solongate import ServerVerifier, VerificationConfig, create_flask_decorator

app = Flask(__name__)
verifier = ServerVerifier(VerificationConfig(gateway_secret="..."))
require_gateway = create_flask_decorator(verifier)

@app.route("/api/tool/<tool_name>", methods=["POST"])
@require_gateway(expected_tool="file.read")
def call_tool(tool_name):
    # Request is verified
    return {"result": "success"}
```

## Async Support

```python
from solongate import AsyncSolonGateAPI, AsyncMCPInterceptor

# Async API client
async with AsyncSolonGateAPI(api_key="sg_live_xxx") as api:
    result = await api.validate("file.read", {"path": "/home/user/doc.txt"})

# Async interceptor
async_interceptor = AsyncMCPInterceptor(gate, config)
result = await async_interceptor.intercept("tools/call", "file.read", params)
```

## Schema Validation

Define strict input schemas:

```python
from solongate import StrictSchema, ToolRegistry, register_tool

class FileReadInput(StrictSchema):
    path: str
    encoding: str = "utf-8"

# Register globally
register_tool("file.read", "Read a file", FileReadInput)

# Or use a registry
registry = ToolRegistry()
registry.register("file.read", "Read a file", FileReadInput)

# Validation rejects unknown fields
result = registry.validate_input("file.read", {
    "path": "/home/user/doc.txt",
    "malicious_field": "injected"  # This causes validation to fail
})
print(result.valid)  # False
```

## Error Handling

```python
from solongate import (
    SolonGateError,
    PolicyDeniedError,
    SchemaValidationError,
    RateLimitError,
    AuthenticationError,
    APIError,
)

try:
    result = api.validate("some.tool", {"input": "data"})
except AuthenticationError:
    print("Invalid API key")
except PolicyDeniedError as e:
    print(f"Denied: {e.reason}")
except SchemaValidationError as e:
    print(f"Invalid input: {e.errors}")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except APIError as e:
    print(f"API error: {e.message}")
except SolonGateError as e:
    print(f"SolonGate error: {e.code}")
```

## Security Principles

1. **Trust Nothing**: LLMs are untrusted by default
2. **Default Deny**: All actions denied unless explicitly allowed
3. **Minimal Permissions**: Grant only what's needed
4. **Defense in Depth**: Multiple validation layers
5. **Fail Closed**: On error, deny access
6. **Audit Everything**: Complete audit trail

## License

MIT

## Links

- [Documentation](https://docs.solongate.com)
- [Dashboard](https://solongate.com/dashboard)
- [GitHub](https://github.com/solongate/solongate-python)
- [Discord](https://discord.gg/solongate)