Security Architecture¶
Engramia v0.6.0 · Classification: Internal
Overview¶
Engramia is a REST API + Python library that stores and retrieves agent execution patterns. It processes data provided by calling agent systems — task descriptions, code snippets, evaluation scores, and feedback — and persists them in a scoped storage backend.
Security posture: OWASP ASVS Level 2/3 (see SECURITY.md for the full control mapping).
System Boundaries¶
┌─────────────────────────────────────────┐
Agent / LLM │ TRUST BOUNDARY │
Framework ──HTTPS──▶│ Caddy (TLS termination) │
│ │ │
│ ▼ │
│ Engramia API (FastAPI, non-root) │
│ ├── Auth middleware (Bearer / OIDC) │
│ ├── Rate limiting (per IP/path) │
│ ├── Body size limit (1 MB default) │
│ ├── Security headers (CSP, HSTS, ...) │
│ └── Business logic (Memory + services) │
│ │ │
│ ├──▶ PostgreSQL + pgvector │
│ │ (scoped: tenant + project) │
│ │ │
│ └──▶ LLM Provider (outbound) │
│ OpenAI / Anthropic HTTPS │
└─────────────────────────────────────────┘
External callers authenticate with Bearer tokens. The API trusts nothing that crosses the boundary without a valid token.
LLM providers are downstream — the API calls out to OpenAI/Anthropic over HTTPS. API keys are held in environment variables only, never in storage.
Authentication & Authorization¶
Auth modes¶
| Mode | Mechanism | Use case |
|---|---|---|
auto (default) |
DB if DATABASE_URL set, else env-var |
Most deployments |
env |
ENGRAMIA_API_KEYS static list |
Single-tenant, backward compat |
db |
DB api_keys table, SHA-256 hashed |
Multi-tenant SaaS |
oidc |
OIDC JWT (RS256/ECDSA) + JWKS | Enterprise SSO (Okta, Azure AD, ...) |
dev |
No auth | Local development only |
RBAC roles¶
| Role | Can do |
|---|---|
owner |
All operations including tenant/key management |
admin |
Key management, governance, analytics |
editor |
Learn, recall, evaluate, compose, cancel jobs |
reader |
Recall, metrics, feedback, job status read |
Role is stored per API key in the DB (or mapped from a JWT claim in OIDC mode).
Token security¶
- Keys are stored as SHA-256 hashes — plaintext is shown exactly once at creation
- Comparison uses
hmac.compare_digestto prevent timing oracle attacks - 60-second in-process TTL cache for DB lookups; cache is invalidated on revoke/rotate
- OIDC: JWKS keys are cached 1 hour; failed refresh keeps previous keys (availability over security for transient IdP outages)
Multi-Tenancy and Data Isolation¶
Every storage read/write is scoped to (tenant_id, project_id). The scope is set from the authenticated API key (or OIDC claims) and propagated through Python contextvars — it is never passed as a user-controlled parameter.
PostgreSQL: all queries include AND tenant_id = :tid AND project_id = :pid WHERE clauses. There is no code path that can read across tenant boundaries.
JSON storage: non-default scopes write to {root}/{tenant}/{project}/. Cross-scope reads return None.
Test coverage: tests/test_security/test_tenant_isolation.py — 12 tests verifying that tenant A cannot read, search, or list tenant B's data via any API path.
Transport Security¶
- TLS 1.2+ enforced by Caddy (Let's Encrypt auto-renewal)
Strict-Transport-Security: max-age=31536000; includeSubDomainsheader on all responses- CORS disabled by default; explicit opt-in via
ENGRAMIA_CORS_ORIGINS - All outbound LLM API calls are HTTPS (enforced by provider SDK)
Input Validation and Injection Prevention¶
- Body size limit: 1 MB by default (
ENGRAMIA_MAX_BODY_SIZE) - Pydantic v2 validation on all API request models (type, length, range)
- Prompt injection mitigation: all user content passed to LLMs is wrapped in XML delimiters (
<task>,<code>,<output>) to prevent instruction injection - LIKE wildcard escaping:
%and_in pattern keys are escaped before use in SQL LIKE queries - Path traversal: pattern keys containing
..or/are rejected at the validation layer
Data at Rest¶
- PostgreSQL: data is at rest on the host volume. Encryption at rest depends on the deployment host (Hetzner disk encryption or OS-level LUKS recommended for production).
- JSON storage: files on the host filesystem. Suitable for dev only; not recommended for production with sensitive data.
- API keys: SHA-256 hashed in DB. Original key is never logged or re-transmitted.
- PII redaction: the
RedactionPipelinecan be enabled to strip emails, IPs, JWTs, API keys, and credentials from pattern content before storage.
Data in Transit¶
- All external traffic: HTTPS/TLS via Caddy
- Internal traffic (API → PostgreSQL): on Docker bridge network, no TLS by default. For production with separate DB hosts, configure
sslmode=requireinENGRAMIA_DATABASE_URL. - Outbound to LLM providers: HTTPS enforced by provider SDK
Audit Logging¶
All security-relevant events are logged as structured JSON:
| Event | Trigger |
|---|---|
AUTH_FAILURE |
Invalid or missing token |
KEY_CREATED |
New API key issued |
KEY_REVOKED |
Key revoked |
KEY_ROTATED |
Key rotated |
QUOTA_EXCEEDED |
Pattern quota hit (HTTP 429) |
SCOPE_DELETED |
Tenant/project data deleted |
SCOPE_EXPORTED |
GDPR data export |
RETENTION_APPLIED |
Retention cleanup run |
PII_REDACTED |
PII found and redacted |
Log fields: event, timestamp, ip, tenant_id, project_id, key_id, detail.
Rate Limiting¶
Per-IP, per-path, in-memory:
| Endpoint class | Default limit |
|---|---|
| Standard (learn, recall, metrics, ...) | 60 req/min |
| LLM-intensive (evaluate, compose, evolve) | 10 req/min |
For multi-instance deployments, configure an upstream rate limiter (Nginx, Caddy rate_limit, Cloudflare, AWS WAF).
Error Handling¶
- HTTP 4xx/5xx responses never expose internal exception details, stack traces, or DB query fragments
- Error sanitization is applied in the FastAPI exception handlers
- Internal errors are logged at ERROR level with full context
Secrets Management¶
| Secret | Storage | Rotation |
|---|---|---|
| OpenAI API key | Environment variable | Manual, restart required |
| Anthropic API key | Environment variable | Manual, restart required |
| Engramia API keys | DB (hashed) | POST /v1/keys/{id}/rotate — zero downtime |
| PostgreSQL password | Environment variable / Docker secret | Manual |
| OIDC client secrets | Not stored (stateless JWT validation) | IdP-managed |
Secrets must never be committed to source control. Use .env files (gitignored) or a secrets manager (Vault, AWS Secrets Manager, Hetzner Cloud secrets).
Known Limitations¶
See SECURITY.md for the full list. Key items:
- Rate limiting is in-process — not effective for multi-instance deployments without an upstream rate limiter
- Audit log is append-only JSON — no SIEM integration yet
- No WAF in front of Caddy (optional: Cloudflare proxy)
- OIDC JWKS cache is per-process — a compromised key continues to validate tokens until TTL expires (1 hour max)
Security Contact¶
Report vulnerabilities privately to: security@engramia.dev
Response SLA: 72 hours acknowledgement, 30 days remediation for P0/P1.