Security
Security is a top priority for ekoDB. Your data is protected with enterprise-grade features designed for production workloads.
Encryption
Data at Rest
All data stored in ekoDB is encrypted using AES-256-GCM encryption:
- Record data - All field values are encrypted before storage
- Encrypted IDs - Record identifiers are encrypted to prevent enumeration attacks
- WAL (Write-Ahead Log) - Transaction logs are encrypted
- Backups - All backup files inherit encryption
Data in Transit
- TLS 1.3 - All connections use modern TLS encryption
- Certificate validation - Strict certificate verification
- Perfect Forward Secrecy - Key exchange protects past sessions
Encryption Keys
ekoDB automatically manages encryption keys:
Database Key Structure:
├── Master Key (derived from your deployment)
├── Collection Keys (per-collection encryption)
└── Record Keys (per-record for sensitive data)
The admin key generated at the time of deploying your database is automatically encrypted and rotated by app.ekodb.io to secure your account. All other API keys (that you generate) are your responsibility to rotate as needed. See Creating Scoped API Keys below for how to generate new keys and revoke old ones.
Authentication
API Keys
ekoDB uses API keys for authentication. Each key is associated with specific permissions.
Key Types:
| Type | Description | Use Case |
|---|---|---|
| Admin Key | Full access to all operations | Development, deployment management |
| Scoped Key | Limited to specific collections | Production applications |
| Read-Only Key | Read access only | Analytics, reporting |
Creating Scoped API Keys
API key management is performed via the REST API. Use an admin token to create new scoped keys:
POST /api/auth/register
Authorization: Bearer {ADMIN_TOKEN}
Content-Type: application/json
{
"label": "production-backend",
"description": "Backend service API key",
"is_admin": false,
"collection_permissions": {
"users": ["read", "write"],
"products": ["read"],
"orders": ["read", "write"]
}
}
Response:
{
"api_key": "eko_prod_abc123...",
"label": "production-backend",
"created_at": "2026-01-28T12:00:00Z"
}
Example with curl:
curl -X POST https://{EKODB_API_URL}/api/auth/register \
-H "Authorization: Bearer {ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"label": "production-backend",
"description": "Backend service API key",
"is_admin": false,
"collection_permissions": {
"users": ["read", "write"],
"products": ["read"],
"orders": ["read", "write"]
}
}'
Once you have an API key, use it to initialize any client library:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use ekodb_client::Client;
let client = Client::builder()
.base_url("https://mydb.production.google.ekodb.net")
.api_key("eko_prod_abc123...")
.build()?;
from ekodb_client import Client
client = Client.new(
'https://mydb.production.google.ekodb.net',
'eko_prod_abc123...'
)
import { EkoDBClient } from '@ekodb/ekodb-client';
const client = new EkoDBClient({
baseURL: 'https://mydb.production.google.ekodb.net',
apiKey: 'eko_prod_abc123...'
});
await client.init();
const { EkoDBClient } = require('@ekodb/ekodb-client');
const client = new EkoDBClient({
baseURL: 'https://mydb.production.google.ekodb.net',
apiKey: 'eko_prod_abc123...'
});
await client.init();
import io.ekodb.client.EkoDBClient
val client = EkoDBClient.builder()
.baseUrl("https://mydb.production.google.ekodb.net")
.apiKey("eko_prod_abc123...")
.build()
import "github.com/ekoDB/ekodb-client-go"
client := ekodb.NewClient(
"https://mydb.production.google.ekodb.net",
"eko_prod_abc123...",
)
JWT Tokens
API keys are exchanged for JWT tokens for API access:
- Token Generation - Client libraries automatically handle this
- Token Lifetime - Default 1 hour (configurable)
- Token Refresh - Automatic refresh before expiration
- Token Revocation - Delete the API key to revoke all associated tokens
Access Control
Collection-Level Permissions
Control access at the collection level:
| Permission | Description |
|---|---|
read | Query, find, and search records |
write | Insert, update, and delete records |
admin | Manage collection schema and settings |
Field-Level Access Control
Restrict access to specific fields within a collection:
{
"collection_permissions": {
"users": {
"read": true,
"write": true,
"select_fields": ["id", "name", "email"],
"exclude_fields": ["password_hash", "ssn"],
"writable_fields": ["name", "email", "preferences"]
}
}
}
| Field | Description |
|---|---|
select_fields | Only return these fields (allowlist) |
exclude_fields | Never return these fields (blocklist) |
writable_fields | Only allow writes to these fields |
Use exclude_fields to ensure sensitive data like password hashes, SSNs, or internal flags are never exposed to API consumers.
Network Security
Supported Protocols
ekoDB only supports secure connections:
| Protocol | Status | Description |
|---|---|---|
| HTTPS | ✅ Supported | REST API connections |
| WSS | ✅ Supported | WebSocket connections |
| HTTP | ❌ Blocked | Rejected at load balancer |
| WS | ❌ Blocked | Rejected at load balancer |
Rate Limiting
Protect your deployment from abuse. Rate limits are based on your deployment tier:
| Tier | Rate Limit |
|---|---|
| Free | 50 requests/minute |
| Developer | 100 requests/minute |
| Startup | 250 requests/minute |
| Business | 500 requests/minute |
| Professional | 1,000 requests/minute |
| Enterprise Basic | 2,000 requests/minute |
| Enterprise Standard | 4,000 requests/minute |
Rate Limit Responses:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1706456400
IP Allowlisting (Enterprise)
Restrict API access to specific IP addresses or CIDR ranges:
{
"allowed_ips": [
"203.0.113.0/24",
"198.51.100.42"
]
}
API Key Management
Listing Keys
View all API keys for your deployment:
GET /api/auth/keys
Authorization: Bearer {ADMIN_TOKEN}
Rotating Keys
Best practice: Rotate API keys periodically.
- Create new key with same permissions via the REST API
- Update applications to use new key
- Delete old key after confirming new key works
# Step 1: Create new key
curl -X POST https://{EKODB_API_URL}/api/auth/register \
-H "Authorization: Bearer {ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"label": "production-backend-v2",
"description": "Rotated key - January 2026",
"is_admin": false,
"collection_permissions": {
"users": ["read", "write"],
"products": ["read"]
}
}'
# Step 2: Update your application configuration with new key
# Step 3: Delete old key
curl -X DELETE https://{EKODB_API_URL}/api/auth/keys/{old_key_id} \
-H "Authorization: Bearer {ADMIN_TOKEN}"
Deleting Keys
Immediately revoke access by deleting an API key:
DELETE /api/auth/keys/{key_id}
Authorization: Bearer {ADMIN_TOKEN}
Deleting an API key immediately invalidates all JWT tokens generated from that key. Ensure you've updated all applications using that key first.
Security Best Practices
1. Use Environment Variables
Never hardcode API keys in source code:
# .env file (never commit to git!)
EKODB_API_KEY=eko_prod_abc123...
EKODB_URL=https://mydb.production.google.ekodb.net
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
use std::env;
use ekodb_client::Client;
let client = Client::builder()
.base_url(&env::var("EKODB_URL").expect("EKODB_URL not set"))
.api_key(&env::var("EKODB_API_KEY").expect("EKODB_API_KEY not set"))
.build()?;
import os
from ekodb_client import Client
client = Client.new(
os.environ['EKODB_URL'],
os.environ['EKODB_API_KEY']
)
import { EkoDBClient } from '@ekodb/ekodb-client';
const client = new EkoDBClient({
baseURL: process.env.EKODB_URL!,
apiKey: process.env.EKODB_API_KEY!
});
await client.init();
const { EkoDBClient } = require('@ekodb/ekodb-client');
const client = new EkoDBClient({
baseURL: process.env.EKODB_URL,
apiKey: process.env.EKODB_API_KEY
});
await client.init();
import io.ekodb.client.EkoDBClient
val client = EkoDBClient.builder()
.baseUrl(System.getenv("EKODB_URL"))
.apiKey(System.getenv("EKODB_API_KEY"))
.build()
import (
"os"
"github.com/ekoDB/ekodb-client-go"
)
client := ekodb.NewClient(
os.Getenv("EKODB_URL"),
os.Getenv("EKODB_API_KEY"),
)
2. Implement Least Privilege
Create separate API keys for different services:
| Service | Permissions |
|---|---|
| User Service | users: read, write |
| Analytics | users: read, events: read |
| Admin Dashboard | Admin key (restricted access) |
3. Monitor API Usage
Review access patterns in your ekoDB dashboard:
- Failed authentication attempts
- Unusual query patterns
- High-volume operations
4. Secure Your Infrastructure
- Use private networks when possible
- Enable firewall rules
- Implement audit logging
5. Handle Errors Securely
Never expose internal error details to clients:
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
match client.find("users", query).await {
Ok(result) => result,
Err(e) => {
// Log full error internally
eprintln!("Database error: {:?}", e);
// Return generic message to client
return Err(AppError::new("An error occurred. Please try again."));
}
}
try:
result = client.find('users', query)
except Exception as e:
# Log full error internally
logger.error(f"Database error: {e}")
# Return generic message to client
raise HTTPException(status_code=500, detail="An error occurred. Please try again.")
try {
const result = await client.find('users', query);
} catch (error) {
// Log full error internally
console.error('Database error:', error);
// Return generic message to client
throw new Error('An error occurred. Please try again.');
}
try {
const result = await client.find('users', query);
} catch (error) {
// Log full error internally
console.error('Database error:', error);
// Return generic message to client
throw new Error('An error occurred. Please try again.');
}
try {
val result = client.find("users", query)
} catch (e: Exception) {
// Log full error internally
logger.error("Database error", e)
// Return generic message to client
throw ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "An error occurred. Please try again.")
}
result, err := client.Find("users", query)
if err != nil {
// Log full error internally
log.Printf("Database error: %v", err)
// Return generic message to client
http.Error(w, "An error occurred. Please try again.", http.StatusInternalServerError)
return
}
Compliance
ekoDB is designed with compliance in mind:
| Standard | Status |
|---|---|
| GDPR | ✅ Data encryption, deletion support |
| SOC 2 | 🔄 In progress |
| HIPAA | 📋 Available on Enterprise plan |
Data Deletion
Support GDPR "right to be forgotten":
- 🦀 Rust
- 🐍 Python
- 📘 TypeScript
- 📦 JavaScript
- 🟣 Kotlin
- 🔷 Go
// Hard delete user data
client.delete("users", &user_id).await?;
// Delete related data with filter
client.batch_delete("user_events", BatchDeleteOptions {
filter: Some(QueryExpression::Condition {
field: "user_id".to_string(),
operator: FilterOperator::Eq,
value: json!(user_id),
}),
..Default::default()
}).await?;
# Hard delete user data
client.delete('users', user_id)
# Delete related data with filter
client.batch_delete('user_events', {
'filter': {
'type': 'Condition',
'content': {
'field': 'user_id',
'operator': 'Eq',
'value': user_id
}
}
})
// Hard delete user data
await client.delete('users', userId);
// Delete related data with filter
await client.batchDelete('user_events', {
filter: {
type: 'Condition',
content: { field: 'user_id', operator: 'Eq', value: userId }
}
});
// Hard delete user data
await client.delete('users', userId);
// Delete related data with filter
await client.batchDelete('user_events', {
filter: {
type: 'Condition',
content: { field: 'user_id', operator: 'Eq', value: userId }
}
});
// Hard delete user data
client.delete("users", userId)
// Delete related data with filter
client.batchDelete("user_events", mapOf(
"filter" to mapOf(
"type" to "Condition",
"content" to mapOf(
"field" to "user_id",
"operator" to "Eq",
"value" to userId
)
)
))
// Hard delete user data
_, err := client.Delete("users", userID)
// Delete related data with filter
_, err = client.BatchDelete("user_events", ekodb.BatchDeleteOptions{
Filter: map[string]interface{}{
"type": "Condition",
"content": map[string]interface{}{
"field": "user_id",
"operator": "Eq",
"value": userID,
},
},
})
Vulnerability Reporting
Found a security issue? We take security seriously.
Report vulnerabilities to: security@ekodb.io
Response times:
- Critical: 24 hours
- High: 48 hours
- Medium: 1 week
- Low: 2 weeks
We follow responsible disclosure practices and will acknowledge your contribution.