# Validation & Defaults
This document describes configuration validation rules and default values.
## Validation Process
Configuration validation occurs in multiple stages:
1. **Parse-time validation**: Syntax and type checking
2. **Schema validation**: Required fields, value constraints
3. **Semantic validation**: Cross-references, logical consistency
4. **Runtime validation**: File existence, network addresses
## Schema Version Compatibility
```kdl
schema-version "1.0"
```
| < min supported | Not loadable | Error |
| = current | Full support | Exact match |
| > current | Loadable | Warning (may lack features) |
Current version: `1.0`
Minimum supported: `1.0`
## Validation Rules
### Server
| `worker-threads` | `>= 0` (0 = auto-detect) |
| `max-connections` | `> 0` |
| `graceful-shutdown-timeout-secs` | `> 0` |
| `trace-id-format` | Must be `tinyflake` or `uuid` |
### Listeners
| `id` | Non-empty, unique |
| `address` | Valid socket address (host:port) |
| `protocol` | Must be `http`, `https`, `h2`, or `h3` |
| `tls` | Required when protocol is `https` |
| `request-timeout-secs` | `> 0` |
**At least one listener is required.**
### Routes
| `id` | Non-empty, unique |
| `matches` | At least one match condition |
| `upstream` | Must reference existing upstream (unless builtin/static) |
| `filters` | All filter IDs must exist |
| `builtin-handler` | Required when `service-type` is `builtin` |
| `static-files.root` | Required when `service-type` is `static` |
### Upstreams
| `id` | Non-empty, unique |
| `targets` | At least one target required |
| `targets[].address` | Valid host:port format |
| `targets[].weight` | `> 0` |
| `health-check.interval-secs` | `> 0` |
| `health-check.timeout-secs` | `> 0`, `< interval-secs` |
### Filters
| `id` | Non-empty, unique |
| `type` | Valid filter type |
| `rate-limit.max-rps` | `> 0` |
| `compress.algorithms` | At least one algorithm |
| `geo.database-path` | Non-empty |
| `geo.countries` | Valid ISO 3166-1 alpha-2 codes |
| `agent.agent` | Must reference existing agent |
### Agents
| `id` | Non-empty, unique |
| `timeout-ms` | `> 0` |
| `transport.unix-socket` | Parent directory must exist |
| `transport.grpc.address` | Valid address format |
| `events` | At least one event |
### WAF
| `engine` | Must be `modsecurity`, `coraza`, or `custom` |
| `mode` | Must be `off`, `detection`, or `prevention` |
| `ruleset.paranoia-level` | `1-4` |
| `body-inspection.max-decompression-ratio` | `> 0` |
### Observability
| `metrics.address` | Valid socket address |
| `tracing.sampling-rate` | `0.0-1.0` |
| `logging.level` | Valid log level |
### Limits
| `max-header-size-bytes` | `> 0` |
| `max-header-count` | `> 0` |
| `max-body-size-bytes` | `> 0` |
## Reference Integrity
The validator ensures all cross-references are valid:
```
Route → Upstream ✓ Upstream must exist
Route → Filter ✓ Filter must exist
Filter → Agent ✓ Agent must exist
Listener → Route ✓ Default route must exist
```
Invalid references produce clear error messages:
```
Configuration validation failed:
Route 'api' references non-existent upstream 'backend'
```
## Default Values
### Server Defaults
| `worker-threads` | `0` | Auto-detect CPU cores for optimal performance |
| `max-connections` | `10000` | Reasonable for most deployments |
| `graceful-shutdown-timeout-secs` | `30` | Allow in-flight requests to complete |
| `daemon` | `false` | Prefer systemd/container orchestration |
| `trace-id-format` | `tinyflake` | Compact, operator-friendly IDs |
| `auto-reload` | `false` | Explicit reload preferred in production |
### Listener Defaults
| `request-timeout-secs` | `60` | Standard HTTP timeout |
| `keepalive-timeout-secs` | `75` | Slightly longer than typical client |
| `max-concurrent-streams` | `100` | Reasonable HTTP/2 limit |
### TLS Defaults
| `min-version` | `tls1.2` | Security baseline |
| `client-auth` | `false` | mTLS opt-in |
| `ocsp-stapling` | `true` | Better client experience |
| `session-resumption` | `true` | Performance optimization |
### Route Defaults
| `priority` | `normal` | Standard priority |
| `service-type` | `web` | Traditional web proxy |
| `failure-mode` | `closed` | Security-first (fail-closed) |
| `waf-enabled` | `false` | WAF opt-in |
| `websocket` | `false` | WebSocket opt-in |
### Upstream Defaults
| `load-balancing` | `round-robin` | Simple, fair distribution |
| `targets[].weight` | `1` | Equal weight by default |
| `connection-pool.max-connections` | `100` | Reasonable pool size |
| `connection-pool.max-idle` | `20` | Balance memory vs latency |
| `connection-pool.idle-timeout-secs` | `60` | Reclaim idle connections |
| `timeouts.connect-secs` | `10` | Reasonable connect timeout |
| `timeouts.request-secs` | `60` | Standard request timeout |
| `timeouts.read-secs` | `30` | Balance responsiveness |
| `timeouts.write-secs` | `30` | Balance responsiveness |
### Health Check Defaults
| `interval-secs` | `10` | Balance responsiveness vs overhead |
| `timeout-secs` | `5` | Quick failure detection |
| `healthy-threshold` | `2` | Require consistent health |
| `unhealthy-threshold` | `3` | Tolerate transient failures |
### Filter Defaults
#### rate-limit
| `burst` | `10` | Allow small bursts |
| `key` | `client-ip` | Per-client limiting |
| `on-limit` | `reject` | Clear feedback to client |
| `status-code` | `429` | Standard rate limit status |
| `backend` | `local` | Simple single-instance |
#### compress
| `algorithms` | `["gzip", "brotli"]` | Wide client support |
| `min-size` | `1024` | Don't compress tiny responses |
| `level` | `6` | Balance ratio vs CPU |
#### cors
| `allowed-origins` | `["*"]` | Permissive default |
| `allow-credentials` | `false` | Security default |
| `max-age-secs` | `86400` | Cache preflight 24h |
#### geo
| `action` | `block` | Blocklist mode |
| `on-failure` | `open` | Fail-open on lookup error |
| `status-code` | `403` | Standard forbidden |
| `cache-ttl-secs` | `3600` | Cache lookups 1h |
### Agent Defaults
| `timeout-ms` | `1000` | 1 second timeout |
| `failure-mode` | `closed` | Security-first |
| `request-body-mode` | `buffer` | Simpler agent implementation |
| `chunk-timeout-ms` | `5000` | Per-chunk timeout |
| `max-concurrent-calls` | `100` | Prevent agent overload |
### WAF Defaults
| `mode` | `prevention` | Active blocking |
| `audit-log` | `true` | Security visibility |
| `ruleset.paranoia-level` | `1` | Low false positives |
| `ruleset.anomaly-threshold` | `5` | Standard CRS threshold |
| `body-inspection.inspect-request-body` | `true` | Inspect requests |
| `body-inspection.inspect-response-body` | `false` | Response inspection opt-in |
| `body-inspection.max-inspection-bytes` | `1048576` | 1MB inspection limit |
### Observability Defaults
| `metrics.enabled` | `true` | Observability by default |
| `metrics.address` | `0.0.0.0:9090` | Standard metrics port |
| `metrics.path` | `/metrics` | Standard Prometheus path |
| `logging.level` | `info` | Balanced verbosity |
| `logging.format` | `json` | Structured logging |
| `tracing.sampling-rate` | `0.01` | 1% sampling |
| `access-log.sample-rate` | `1.0` | Log all requests |
### Limits Defaults
| `max-header-size-bytes` | `8192` | 8KB header limit |
| `max-header-count` | `100` | Reasonable header count |
| `max-body-size-bytes` | `1048576` | 1MB body limit |
| `max-connections-per-client` | `100` | Per-client limit |
### Cache Defaults
| `enabled` | `true` | Caching when configured |
| `backend` | `memory` | Simple in-memory cache |
| `max-size-bytes` | `104857600` | 100MB cache |
| `lock-timeout-secs` | `10` | Prevent thundering herd |
| `disk-shards` | `16` | Concurrent disk access |
## Security Defaults
Zentinel follows a **security-first** design philosophy:
| `failure-mode` | `closed` | Block on failure (not fail-open) |
| `tls.min-version` | `tls1.2` | No legacy TLS |
| `waf-enabled` | `false` | WAF must be explicitly enabled |
| `agent.timeout-ms` | `1000` | Bounded agent calls |
| `limits.*` | Bounded | Prevent resource exhaustion |
## Validation Error Messages
Error messages include context for debugging:
```
Configuration validation failed:
Route 'api' references non-existent upstream 'backend'
Available upstreams: ["web-backend", "api-backend"]
```
```
Configuration validation failed:
Filter 'auth' references non-existent agent 'auth-agent'
Available agents: ["waf-agent"]
```
```
KDL configuration parse error:
Expected closing brace
--> at line 15, column 1
| ^ expected '}'
## Dry-Run Validation
Validate configuration without starting the proxy:
```bash
zentinel --config zentinel.kdl --validate
```
Or programmatically:
```rust
let config = Config::from_file("zentinel.kdl")?;
config.validate()?;
println!("Configuration is valid");
```