DelOS runs in the browser against a Node-side API surface. The threats we defend against:
- Prompt injection — attacker payloads designed to override system instructions
- Cross-tenant data leakage — one user reading another's memories
- Stored XSS — memory text rendered without sanitization
- Prototype pollution —
__proto__and friends in tag arrays - Wallet drain — uncapped LLM calls burning provider budget
- Reflection amplification — multi-KB user input echoed back unbounded
- Credential exfiltration — agents tricked into revealing env-var keys
- Approval bypass — destructive skills firing without user consent
- Rate limit (
src/lib/rateLimit.ts)- Per-IP, per-route, sliding window
- Codegen capped at 6/min (heaviest budget)
- Voice capped at 40/min
- Cohort capped at 12/min
- Zod parse — length + shape enforced before any processing
- Injection classifier (
src/lib/security/injection-classifier.ts)- 8 patterns: ignore-previous, system-prompt-leak, credential-exfil, role-override, developer-mode, instruction-override, execute-unrestricted, base64-marker
- Returns 400 with
{ error: "blocked_for_security", pattern }
- Tenant resolution (
src/lib/apiAuth.ts → resolveTenant)- Session cookie wins
- Reserved prefix accepted from body / header / query (regex enforced)
- Otherwise anon IP-scoped tenant
sanitizeMemoryText(src/lib/sanitize.ts)- Drops
<script>tags - Strips inline event handlers (
onerror=,onclick=) - Neutralizes
javascript:anddata:text/htmlURIs - Caps at 4000 chars
- Drops
sanitizeTags- Rejects reserved names:
__proto__,constructor,prototype,__defineGetter__,__defineSetter__,__lookupGetter__,__lookupSetter__ - Lowercases, strips non-namespace chars, dedupes, caps at 16 entries
- Rejects reserved names:
sanitizeTitle(src/lib/security/injection-classifier.ts)- 60-char cap
- Rejects HTML-ish brackets
- Rejects all-lowercase-no-punctuation strings (prompt-fragment echo defense)
Reject memory writes that match:
/(apply to every output)/i // persona preamble leak
/^Run completed for goal/i // run-summary pollution
/^tone:\s*(concise|verbose|balanced)/i // persona fragment
/\[REDACTED\]/i // unfilled template
/SYSTEM[_\s-]?PROMPT\b/i // prompt leak attempt
Plus: anything > 240 chars with no verb is rejected as looks-like-run-summary.
safeRecallfilters local fallback bytenantIddeleteLocalMemoryrequires bothtenantIdANDidto match — wrong tenant returnsnot_foundeven if the id is correctclearLocalMemoriesis tenant-scoped- HydraDB tenant isolation enforced at the client SDK level
Skill risk tiers enforced server-side:
| Tier | Pre-flight gate |
|---|---|
| READ | none |
| REVERSIBLE | none |
| DRAFT | none |
| EXTERNAL | UI approval modal, blocks until user taps Approve |
| MONEY | UI approval modal + secondary confirmation |
| DESTRUCTIVE | UI approval modal + secondary confirmation + 5s typing cooldown |
The approval state is signed with the session token. Voice and assistant flows both route through the same gate.
DELOS_AUTONOMY=1 env var allows chained EXTERNAL skills without per-step approval, gated behind a user-set "trust level" in Settings (default OFF).
- No API keys in JSON responses —
/api/keys/statusreturns booleans only - No GET endpoints for state-changing actions — every state-changing endpoint is POST + has a 405 GET handler
- No tenant ID in URLs as path params — only as
?tenant=query (so it never leaks via referer header to logs) - No client-side env-var exposure —
NEXT_PUBLIC_*is empty by design; all secrets are server-only - No SSE injection —
zodErrreturns a single-line clean string so multi-line error blobs cannot break a downstream SSE consumer
Every memory write is logged with metadata: { runId, tags, ... }. The runId field threads through all events of a run so audit can reconstruct "which run produced which memory."
Pinned facts (via /api/memory/pin) carry metadata: { pinned: true, pinnedAt: ts } so the recall layer can boost user-pinned items above run-summary chatter.
Found a vulnerability? File a security report through the GitHub issue tracker tagged security. Or — for sensitive issues — email the project owner directly (link in README).
Do not file CVEs against pre-1.0 hackathon builds publicly; this is a 48-hour prototype, not production software.