Files
Elydora-Open-Source/README.md
T
ZacharyZhang-NYandClaude Opus 4.6 e390e68594 Initial commit: Elydora Open Source — self-hosted audit platform for AI agents
Full self-hosted port of the Elydora tamper-evident audit protocol:
- Hono API server with PostgreSQL, Redis, MinIO adapters
- Ed25519 signed operations, chain hashing, Merkle epoch rollups
- PBKDF2 password hashing, HMAC-SHA256 JWT, 5-role RBAC
- Next.js management console
- Node.js, Python, and Go SDKs
- Docker Compose for local deployment
- Helm chart for Kubernetes
- Comprehensive security hardening and audit fixes applied

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:41:42 -05:00

916 lines
30 KiB
Markdown

<div align="center">
# Elydora
**The Responsibility Layer for AI Agents**
Tamper-evident audit trails with cryptographic proof for every AI agent action.
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![Node SDK](https://img.shields.io/badge/node%20sdk-v1.1.0-green.svg)](sdks/node/)
[![Python SDK](https://img.shields.io/badge/python%20sdk-v1.1.0-green.svg)](sdks/python/)
[![Go SDK](https://img.shields.io/badge/go%20sdk-v0.1.0-green.svg)](sdks/go/)
[Quick Start](#quick-start) · [Documentation](#documentation) · [API Reference](#api-reference) · [SDKs](#sdks) · [Contributing](#contributing)
---
</div>
## What is Elydora?
Elydora is an open-source **responsibility protocol for AI agents**. When an AI agent takes an action — reading a file, calling an API, modifying a database, sending a message — Elydora captures a cryptographically signed, tamper-evident record of that action. These records are chain-hashed together to form an unforgeable audit trail, then rolled up into Merkle tree epochs anchored to public timestamp authorities.
The result is a complete, verifiable history of everything your AI agents have ever done, that no one — not even the platform operator — can silently alter.
**Core protocol features:**
- **Ed25519 signed operation records (EOR)** — every agent action is signed with the agent's private key before leaving the agent's process
- **Chain-hashed audit trails** — each record cryptographically commits to the previous record, making silent insertion, deletion, or reordering detectable
- **Merkle tree epoch rollups with TSA anchoring** — periodic epochs hash all records into a Merkle tree whose root is anchored via RFC 3161 Trusted Timestamping
- **Multi-tenant with RBAC** — fine-grained role-based access control with five distinct roles from read-only investigator to organization owner
- **Compliance exports (JSON / PDF)** — one-click export of the complete audit trail for any time range, agent, or operation type
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ AI Agent Process │
│ │
│ ┌──────────────┐ createOperation() ┌──────────────────┐ │
│ │ Agent Logic │ ─────────────────────▶│ Elydora SDK │ │
│ │ │◀───────────────────── │ (Node/Python/Go)│ │
│ └──────────────┘ submitOperation() └────────┬─────────┘ │
│ │ Signs EOR │
│ │ (Ed25519) │
└───────────────────────────────────────────────────┼─────────────┘
│ HTTPS
▼
┌──────────────────────────────────────────────────────────────────┐
│ Elydora API Server │
│ │
│ ┌───────────────┐ ┌───────────────┐ ┌──────────────────┐ │
│ │ Auth / RBAC │ │ Verify EOR │ │ Epoch Rollups │ │
│ │ Middleware │ │ (signature + │ │ (Merkle trees + │ │
│ │ │ │ chain hash) │ │ TSA anchoring) │ │
│ └───────────────┘ └───────────────┘ └──────────────────┘ │
│ │ │ │ │
└───────────┼──────────────────┼─────────────────────┼────────────┘
│ │ │
┌──────▼──────┐ ┌───────▼────────┐ ┌───────▼──────────┐
│ PostgreSQL │ │ Object Store │ │ Redis │
│ (metadata + │ │ (payloads + │ │ (nonce cache + │
│ chain) │ │ receipts + │ │ rate limits) │
│ │ │ exports) │ │ │
└──────────────┘ └────────────────┘ └──────────────────┘
```
---
## Quick Start
### Prerequisites
- Docker 24+
- Docker Compose v2.20+
### One-Command Deploy
```bash
git clone https://github.com/Elydora-Infrastructure/Elydora-Open-Source.git
cd Elydora-Open-Source
# Copy and edit environment configuration
cp .env.example .env
# Generate Ed25519 signing keys for the server and initial admin
./scripts/generate-keys.sh
# Start all services (API, PostgreSQL, MinIO, Redis)
docker compose up -d
# Verify the server is healthy
curl http://localhost:8787/v1/health
```
### Kubernetes Deploy
```bash
helm install elydora ./helm/elydora \
--namespace elydora \
--create-namespace \
-f values.yaml
```
---
## Project Structure
```
Elydora-Open-Source/
├── packages/
│ ├── server/ # Elydora API server
│ └── console/ # Next.js web management console
├── sdks/
│ ├── node/ # @elydora/sdk — Node.js / TypeScript SDK
│ ├── python/ # elydora — Python SDK (sync + async)
│ └── go/ # github.com/Elydora-Infrastructure/Elydora-Go-SDK
├── helm/
│ └── elydora/ # Kubernetes Helm chart
├── scripts/ # Key generation, migration, and utility scripts
├── CONTRIBUTING.md
├── LICENSE
└── README.md
```
| Package | Description |
|---------|-------------|
| `packages/server` | The core API server. Handles EOR ingestion, signature verification, chain validation, RBAC, epoch rollups, and compliance exports. |
| `packages/console` | A web-based management console for viewing agents, browsing the audit trail, managing exports, and administering users. |
| `sdks/node` | TypeScript SDK with full type coverage. Ships a CLI (`elydora`) and a programmatic client. Supports plugin hooks for popular AI coding agents. |
| `sdks/python` | Python SDK with both synchronous (`ElydoraClient`) and asynchronous (`AsyncElydoraClient`) clients. Requires Python 3.9+. |
| `sdks/go` | Go SDK with idiomatic error handling. Ships a CLI binary and embeds zero external runtime dependencies. |
| `helm/elydora` | Production-ready Helm chart for Kubernetes deployments with configurable replicas, resource limits, and secret management. |
---
## Documentation
### Registration and Authentication Flow
```
1. Register organization and first user
POST /v1/auth/register → { user, organization, token }
2. Log in to retrieve a JWT
POST /v1/auth/login → { user, token }
3. Issue a long-lived API token (optional)
POST /v1/auth/token → { token, expires_at }
4. Register an agent (as integration_engineer)
POST /v1/agents/register → { agent, keys }
5. Agent submits operations using its SDK client
POST /v1/operations → { receipt (EAR) }
```
### Operation Lifecycle
```
Agent SDK Server
│ │ │
│ createOperation(params) │ │
│ ─────────────────────────▶ │ │
│ │ 1. Generate UUIDv7 op ID │
│ │ 2. Generate 16-byte nonce │
│ │ 3. Compute payload_hash │
│ │ SHA-256(JCS(payload)) │
│ │ 4. Compute chain_hash │
│ │ SHA-256(prev|ph|id|ts) │
│ │ 5. Sign JCS(EOR) w/ Ed25519 │
│ │ 6. Update internal state │
│ EOR (signed) │ │
│ ◀───────────────────────── │ │
│ │ │
│ submitOperation(eor) │ │
│ ─────────────────────────▶ │ POST /v1/operations │
│ │ ────────────────────────────▶│
│ │ │ Verify signature
│ │ │ Verify chain hash
│ │ │ Check TTL / replay
│ │ { receipt: EAR } │
│ │ ◀────────────────────────────│
│ SubmitOperationResponse │ │
│ ◀───────────────────────── │ │
```
---
## API Reference
All endpoints are prefixed with the configured base URL (default: `https://api.elydora.com`). All authenticated endpoints require a `Bearer` token in the `Authorization` header.
Errors are returned as:
```json
{
"error": {
"code": "INVALID_SIGNATURE",
"message": "Ed25519 signature verification failed",
"request_id": "01J..."
}
}
```
### Authentication
#### `POST /v1/auth/register`
Register a new user and create an organization. Returns a JWT for immediate use.
**Request body:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `email` | string | yes | User email address |
| `password` | string | yes | User password |
| `display_name` | string | no | Human-readable name for the user |
| `org_name` | string | no | Name for the new organization |
**Response:**
```json
{
"user": { "user_id": "...", "email": "...", "role": "org_owner", ... },
"organization": { "org_id": "...", "name": "..." },
"token": "<jwt>"
}
```
---
#### `POST /v1/auth/login`
Authenticate an existing user.
**Request body:** `{ "email": "...", "password": "..." }`
**Response:** `{ "user": { ... }, "token": "<jwt>" }`
---
#### `GET /v1/auth/me`
Return the authenticated user's profile. Requires a valid JWT.
**Response:** `{ "user": { "user_id": "...", "email": "...", "role": "...", ... } }`
---
#### `POST /v1/auth/token`
Issue a long-lived API token.
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `ttl_seconds` | integer \| null | no | Token lifetime in seconds. `null` for non-expiring. |
**Response:** `{ "token": "<api-token>", "expires_at": <ms> | null }`
---
### Agents
Agents are the AI processes that submit operations. An agent has one or more Ed25519 key pairs registered with the server.
#### `POST /v1/agents/register`
Register a new agent. Required role: **integration_engineer**.
**Request body:**
```json
{
"agent_id": "my-agent-v1",
"display_name": "Customer Support Bot",
"responsible_entity": "engineering-team@example.com",
"keys": [
{
"kid": "my-agent-v1-key-v1",
"public_key": "<base64url Ed25519 public key>",
"algorithm": "ed25519"
}
]
}
```
**Response:** `{ "agent": { ... }, "keys": [ ... ] }`
---
#### `GET /v1/agents`
List all agents for the organization. Required role: **readonly_investigator**.
**Response:** `{ "agents": [ { "agent_id": "...", "status": "active", ... } ] }`
---
#### `GET /v1/agents/:id`
Retrieve a specific agent and its keys. Required role: **readonly_investigator**.
**Response:** `{ "agent": { ... }, "keys": [ ... ] }`
---
#### `PATCH /v1/agents/:id`
Update agent metadata. Required role: **integration_engineer**.
---
#### `DELETE /v1/agents/:id`
Permanently delete an agent. Required role: **security_admin**.
**Response:** `{ "deleted": true }`
---
#### `POST /v1/agents/:id/freeze`
Freeze an agent, preventing it from submitting new operations. Required role: **security_admin**.
**Request body:** `{ "reason": "Unusual activity detected" }`
---
#### `POST /v1/agents/:id/unfreeze`
Re-activate a frozen agent. Required role: **security_admin**.
**Request body:** `{ "reason": "Investigation complete" }`
**Response:** `{ "agent": { "status": "active", ... } }`
---
#### `POST /v1/agents/:id/revoke`
Revoke a specific signing key. The agent remains registered but the key can no longer be used to sign new operations. Required role: **security_admin**.
**Request body:** `{ "kid": "my-agent-v1-key-v1", "reason": "Key rotation" }`
---
### Operations
#### `POST /v1/operations`
Submit a signed Elydora Operation Record (EOR). Required role: **integration_engineer**. The EOR must be constructed and signed by the agent SDK — never by the server.
**Request body:** A complete EOR (see [Protocol Specification](#protocol-specification)).
**Response:** `{ "receipt": <EAR> }` — the server-issued Elydora Acknowledgment Receipt.
---
#### `GET /v1/operations/:id`
Retrieve a stored operation and its receipt. Required role: **readonly_investigator**.
**Response:** `{ "operation": { ... }, "receipt": { ... } }`
---
#### `POST /v1/operations/:id/verify`
Re-verify a stored operation's cryptographic integrity. Required role: **readonly_investigator**.
**Response:**
```json
{
"valid": true,
"checks": {
"signature": true,
"chain": true,
"receipt": true,
"merkle": true
},
"errors": []
}
```
---
### Audit
#### `POST /v1/audit/query`
Query the tamper-evident audit log with filtering and cursor-based pagination. Required role: **compliance_auditor**.
**Request body:**
| Field | Type | Description |
|-------|------|-------------|
| `agent_id` | string | Filter by agent |
| `operation_type` | string | Filter by operation type |
| `start_time` | integer | Start of time range (Unix ms) |
| `end_time` | integer | End of time range (Unix ms) |
| `cursor` | string | Pagination cursor from a previous response |
| `limit` | integer | Maximum number of records to return |
**Response:** `{ "operations": [ ... ], "cursor": "...", "total_count": 1042 }`
---
### Epochs
Epochs are periodic rollups that Merkle-hash all operations in a time window and anchor the root to a public timestamp authority (RFC 3161 TSA).
#### `GET /v1/epochs`
List all epoch records for the organization. Required role: **readonly_investigator**.
**Response:** `{ "epochs": [ { "epoch_id": "...", "root_hash": "...", "leaf_count": 500, ... } ] }`
---
#### `GET /v1/epochs/:id`
Retrieve a specific epoch including its TSA anchor. Required role: **readonly_investigator**.
**Response:**
```json
{
"epoch": {
"epoch_id": "...",
"org_id": "...",
"start_time": 1700000000000,
"end_time": 1700003600000,
"root_hash": "<base64url SHA-256 Merkle root>",
"leaf_count": 500,
"created_at": 1700003601234
},
"anchor": {
"tsa_token": "<base64 RFC3161 TimeStampToken>"
}
}
```
---
### Exports
#### `POST /v1/exports`
Create an asynchronous compliance export job. Required role: **compliance_auditor**.
**Request body:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `start_time` | integer | yes | Start of export window (Unix ms) |
| `end_time` | integer | yes | End of export window (Unix ms) |
| `format` | `"json"` \| `"pdf"` | yes | Export format |
| `agent_id` | string | no | Restrict to one agent |
| `operation_type` | string | no | Restrict to one operation type |
**Response:** `{ "export": { "export_id": "...", "status": "queued", ... } }`
---
#### `GET /v1/exports`
List all exports for the organization. Required role: **compliance_auditor**.
---
#### `GET /v1/exports/:id`
Poll export status and retrieve a download URL once complete. Required role: **compliance_auditor**.
**Response:**
```json
{
"export": { "export_id": "...", "status": "done", "completed_at": 1700003700000 },
"download_url": "https://..."
}
```
---
#### `GET /v1/exports/:id/download`
Stream the export file directly. Required role: **compliance_auditor**.
---
### Public Endpoints
#### `GET /v1/health`
Health check. No authentication required.
**Response:**
```json
{
"status": "ok",
"version": "1.1.0",
"protocol_version": "1.0",
"timestamp": 1700000000000
}
```
---
#### `GET /.well-known/elydora/jwks.json`
The server's JSON Web Key Set. Used by external verifiers to validate EAR signatures. No authentication required.
**Response:** `{ "keys": [ { "kty": "OKP", "crv": "Ed25519", "x": "...", "kid": "...", "use": "sig", "alg": "EdDSA" } ] }`
---
## SDKs
### Node.js / TypeScript
**Installation:**
```bash
npm install @elydora/sdk
```
**Requirements:** Node.js 18+
**Quickstart:**
```typescript
import { ElydoraClient } from '@elydora/sdk';
import { generateKeyPair } from '@elydora/sdk/crypto';
// 1. Register your organization (one-time setup)
const { user, token } = await ElydoraClient.register(
'http://localhost:8787',
'admin@example.com',
'your-password',
'Admin User',
'Acme Corp',
);
// 2. Create an agent client
const client = new ElydoraClient({
orgId: user.org_id,
agentId: 'order-processor-v1',
privateKey: process.env.AGENT_PRIVATE_KEY!,
baseUrl: 'http://localhost:8787', // omit for https://api.elydora.com
});
client.setToken(token);
// 3. Register the agent with its public key
await client.registerAgent({
agent_id: 'order-processor-v1',
display_name: 'Order Processor',
keys: [{ kid: 'order-processor-v1-key-v1', public_key: client.getPublicKey(), algorithm: 'ed25519' }],
});
// 4. Submit an operation
const eor = client.createOperation({
operationType: 'order.process',
subject: { order_id: 'ord-123', customer_id: 'cust-456' },
action: { verb: 'charge', amount_cents: 4999, currency: 'USD' },
});
const { receipt } = await client.submitOperation(eor);
console.log('Accepted. seq_no:', receipt.seq_no);
```
**CLI:**
```bash
npx elydora health --base-url http://localhost:8787
npx elydora keygen # Generate a new Ed25519 key pair
npx elydora submit-op ... # Submit an operation from the CLI
```
---
### Python
**Installation:**
```bash
pip install elydora
```
**Requirements:** Python 3.9+, `requests`, `aiohttp`, `cryptography`
**Quickstart (synchronous):**
```python
from elydora import ElydoraClient
# One-time registration
resp = ElydoraClient.register(
"http://localhost:8787",
"admin@example.com",
"your-password",
display_name="Admin User",
org_name="Acme Corp",
)
# Create agent client
client = ElydoraClient(
org_id=resp["user"]["org_id"],
agent_id="order-processor-v1",
private_key=os.environ["AGENT_PRIVATE_KEY"],
base_url="http://localhost:8787", # omit for https://api.elydora.com
token=resp["token"],
)
# Register the agent and submit operations
from elydora.crypto import derive_public_key
client.register_agent({
"agent_id": "order-processor-v1",
"display_name": "Order Processor",
"keys": [{"kid": "order-processor-v1-key-v1",
"public_key": derive_public_key(os.environ["AGENT_PRIVATE_KEY"]),
"algorithm": "ed25519"}],
})
eor = client.create_operation(
operation_type="order.process",
subject={"order_id": "ord-123", "customer_id": "cust-456"},
action={"verb": "charge", "amount_cents": 4999, "currency": "USD"},
)
receipt = client.submit_operation(eor)
print("Accepted. seq_no:", receipt["receipt"]["seq_no"])
```
**Quickstart (asynchronous):**
```python
from elydora import AsyncElydoraClient
async def main():
client = AsyncElydoraClient(
org_id="...", agent_id="...", private_key="...",
base_url="http://localhost:8787",
)
eor = client.create_operation(operation_type="file.read", subject={...}, action={...})
receipt = await client.submit_operation(eor)
```
**CLI:**
```bash
elydora health --base-url http://localhost:8787
elydora keygen
```
---
### Go
**Installation:**
```bash
go get github.com/Elydora-Infrastructure/Elydora-Go-SDK
```
**Requirements:** Go 1.21+
**Quickstart:**
```go
package main
import (
"fmt"
"os"
elydora "github.com/Elydora-Infrastructure/Elydora-Go-SDK"
)
func main() {
// One-time registration
reg, err := elydora.Register(
"http://localhost:8787",
"admin@example.com",
"your-password",
elydora.WithDisplayName("Admin User"),
elydora.WithOrgName("Acme Corp"),
)
if err != nil {
panic(err)
}
// Create agent client
client, err := elydora.NewClient(&elydora.Config{
OrgID: reg.User.OrgID,
AgentID: "order-processor-v1",
PrivateKey: os.Getenv("AGENT_PRIVATE_KEY"),
BaseURL: "http://localhost:8787", // omit for https://api.elydora.com
Token: reg.Token,
})
if err != nil {
panic(err)
}
// Submit an operation
eor, err := client.CreateOperation(&elydora.CreateOperationParams{
OperationType: "order.process",
Subject: map[string]interface{}{"order_id": "ord-123"},
Action: map[string]interface{}{"verb": "charge", "amount_cents": 4999},
})
if err != nil {
panic(err)
}
result, err := client.SubmitOperation(eor)
if err != nil {
panic(err)
}
fmt.Println("Accepted. seq_no:", result.Receipt.SeqNo)
}
```
**CLI:**
```bash
go run ./cmd/elydora health --base-url http://localhost:8787
go run ./cmd/elydora keygen
```
---
## Protocol Specification
### EOR — Elydora Operation Record
The EOR is the fundamental unit of the protocol. It is constructed and signed by the agent SDK before being sent to the server. The server never modifies an EOR after signing.
**Fields:**
| Field | Type | Description |
|-------|------|-------------|
| `op_version` | `"1.0"` | Protocol version |
| `operation_id` | string | UUIDv7 — monotonic, time-ordered unique ID |
| `org_id` | string | Organization identifier |
| `agent_id` | string | Agent identifier |
| `issued_at` | integer | Unix timestamp in milliseconds when the EOR was constructed |
| `ttl_ms` | integer | Validity window in milliseconds (default: 30,000) |
| `nonce` | string | 16 random bytes, base64url-encoded, prevents replay |
| `operation_type` | string | Application-defined operation category (e.g. `"file.read"`, `"order.process"`) |
| `subject` | object | The entity the operation acts upon |
| `action` | object | Description of what was done |
| `payload` | object \| string \| null | Optional structured payload |
| `payload_hash` | string | SHA-256 of JCS-canonicalized `payload`, base64url |
| `prev_chain_hash` | string | Chain hash of the immediately preceding EOR for this agent |
| `agent_pubkey_kid` | string | Key ID of the signing key |
| `signature` | string | Ed25519 signature over JCS-canonical EOR (excluding `signature` field), base64url |
**Signing:**
```
canonical_bytes = JCS_canonicalize(EOR without "signature" field)
signature = Ed25519_sign(agent_private_key, canonical_bytes)
```
**Chain hashing:**
```
chain_hash = SHA-256(
prev_chain_hash + "|" + payload_hash + "|" + operation_id + "|" + issued_at
)
```
All inputs are UTF-8 strings joined with `|`. The genesis (first) `prev_chain_hash` for every agent is the base64url encoding of 32 zero bytes: `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=`.
**Payload hashing:**
```
payload_hash = SHA-256(JCS_canonicalize(payload))
```
If `payload` is `null`, the hash is of the JCS canonical form of `null`.
---
### EAR — Elydora Acknowledgment Receipt
The EAR is issued by the server upon accepting a valid EOR. It constitutes server-side proof of ingestion.
| Field | Type | Description |
|-------|------|-------------|
| `receipt_version` | string | Receipt protocol version |
| `receipt_id` | string | Unique receipt identifier |
| `operation_id` | string | Echo of the operation ID |
| `org_id` | string | Organization ID |
| `agent_id` | string | Agent ID |
| `server_received_at` | integer | Unix ms when the server accepted the EOR |
| `seq_no` | integer | Monotonically increasing sequence number for this agent |
| `chain_hash` | string | Server-computed chain hash confirming the EOR was inserted in the correct position |
| `queue_message_id` | string | Internal durable queue message identifier |
| `receipt_hash` | string | SHA-256 of the canonical receipt content |
| `elydora_kid` | string | Key ID of the server's signing key |
| `elydora_signature` | string | Server's Ed25519 signature over the receipt |
---
### EER — Elydora Epoch Record
Epochs are created periodically. All EOR chain hashes within the epoch window are hashed into a binary Merkle tree. The root hash is then anchored to an RFC 3161-compliant Trusted Timestamp Authority.
| Field | Type | Description |
|-------|------|-------------|
| `epoch_id` | string | Unique epoch identifier |
| `org_id` | string | Organization ID |
| `start_time` | integer | Epoch start (Unix ms) |
| `end_time` | integer | Epoch end (Unix ms) |
| `root_hash` | string | Merkle root of all chain hashes in the window |
| `leaf_count` | integer | Number of operations included |
| `created_at` | integer | When the epoch record was created |
The TSA token is an RFC 3161 `TimeStampToken` proving the Merkle root existed at or before the timestamp authority's stated time.
---
## RBAC Roles
| Role | Level | Capabilities |
|------|-------|-------------|
| `org_owner` | 50 | Full administrative access; can manage all users, roles, agents, and organization settings |
| `security_admin` | 40 | Agent lifecycle management: freeze, unfreeze, revoke keys, delete agents |
| `compliance_auditor` | 30 | Read audit log, query operations, create and download compliance exports |
| `integration_engineer` | 20 | Register agents, submit operations, update agent metadata |
| `readonly_investigator` | 10 | Read-only access to agents, operations, epochs; cannot modify any state |
Higher-level roles inherit all capabilities of lower-level roles.
---
## Configuration
Elydora is configured via environment variables. Copy `.env.example` to `.env` and configure the following:
### Required
| Variable | Description |
|----------|-------------|
| `DATABASE_URL` | PostgreSQL connection string |
| `REDIS_URL` | Redis connection string |
| `STORAGE_ENDPOINT` | Object storage endpoint (S3-compatible, e.g. MinIO) |
| `STORAGE_BUCKET` | Bucket name for payloads, receipts, and exports |
| `STORAGE_ACCESS_KEY_ID` | Object storage access key |
| `STORAGE_SECRET_ACCESS_KEY` | Object storage secret key |
| `JWT_SECRET` | HMAC-SHA256 secret for signing user JWTs |
| `SERVER_PRIVATE_KEY` | Base64url-encoded Ed25519 seed for signing EARs and JWKS |
### Optional
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `8787` | HTTP port the API server listens on |
| `EPOCH_INTERVAL_SECONDS` | `3600` | How often epoch rollups are created |
| `TSA_URL` | — | RFC 3161 Trusted Timestamp Authority URL. If unset, TSA anchoring is disabled. |
| `LOG_LEVEL` | `info` | Log verbosity (`debug`, `info`, `warn`, `error`) |
| `MAX_PAYLOAD_BYTES` | `65536` | Maximum size of an EOR payload |
| `OPERATION_TTL_MAX_MS` | `300000` | Maximum allowable `ttl_ms` in submitted EORs |
| `CORS_ORIGINS` | `*` | Allowed CORS origins for the console |
---
## Security
### Cryptographic Guarantees
**Ed25519 signatures** — Every EOR is signed by the agent's Ed25519 private key before leaving the agent's process. The server verifies this signature against the registered public key before accepting the record. A compromised server cannot forge valid EORs without access to the agent's private key.
**SHA-256 chain hashing** — Each EOR commits to the hash of the previous EOR, the payload, the operation ID, and the timestamp. Silent insertion, deletion, or reordering of records in the chain is detectable because any change invalidates all subsequent chain hashes.
**PBKDF2-SHA256 password hashing** — User passwords are hashed with PBKDF2-SHA256 before storage.
**HMAC-SHA256 JWT signing** — User sessions are signed with a server-side HMAC secret. Tokens cannot be forged without the secret.
**RFC 3161 TSA anchoring** — Epoch Merkle roots are submitted to a public timestamp authority, providing independent third-party attestation of when records existed. This prevents retroactive alteration of historical data even by the platform operator.
**JCS canonicalization (RFC 8785)** — All signing operations use JSON Canonicalization Scheme, eliminating ambiguity in JSON serialization and ensuring signatures are deterministic across all SDK implementations.
### Key Management
- Agent private keys never leave the agent process. The SDK generates signatures locally.
- The server holds only the agent's public key.
- Server signing keys for EARs and JWKS are stored as environment variables and rotated via `kid` versioning.
- Key rotation is supported: register additional keys with the same agent and revoke old keys without disrupting the audit trail.
### Replay Protection
- Each EOR includes a cryptographically random 16-byte nonce.
- The server maintains a nonce cache in Redis to detect and reject replayed operations within the TTL window.
- The `ttl_ms` field defines the validity window; operations submitted after `issued_at + ttl_ms` are rejected.
---
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, code style guidelines, and the pull request process.
Contributions are welcome across all parts of the project: server, console, SDKs, Helm chart, documentation, and new SDK language implementations.
---
## License
Copyright 2025-present Elydora Contributors
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full text.