Security model

walkindb exposes raw SQL to unauthenticated callers. This is the document that explains why that's safe and exactly what we do to make it safe.

This page is a summary of the full threat model. For the complete, commit-tracked model, read SECURITY.md in the repo.

Threat model in one paragraph

Assume every caller is hostile. walkindb defends against: resource DoS, CPU bombs, disk bombs, filesystem escape (ATTACH '/etc/passwd'), native code execution (load_extension), cross-tenant data leakage, exfiltration / C2 via outbound network, session hijacking, and storage abuse. SQL injection is not in the threat model — callers provide raw SQL by design.

Rollout state

walkindb v0.1.0 ships the following subset of the defense-in-depth design:

LayerStatusNotes
1. SQLite compile-time hardening⏳ RoadmapBlocked on switching off modernc.org/sqlite. Application keyword blocklist + LIMIT_ATTACHED=0 + Landlock cover the concrete attacks today.
2. sqlite3_set_authorizer callback⏳ RoadmapSame blocker. Substituted with the application keyword blocklist.
3. Per-connection sqlite3_limit✅ LiveFull suite: VDBE_OP=500k, SQL_LENGTH=8k, LENGTH=1M, EXPR_DEPTH=50, COMPOUND_SELECT=10, LIKE_PATTERN_LENGTH=100, VARIABLE_NUMBER=100, TRIGGER_DEPTH=5, FUNCTION_ARG=16, LIMIT_ATTACHED=0.
4. Hard 2 s query timeout✅ Livecontext.WithTimeout(2*time.Second) per call. 408 on interrupt.
5. Per-instance 10 MB cap✅ LivePRAGMA max_page_count = 2560. 507 on full.
6a. Landlock filesystem jail✅ LiveProcess confined to /var/walkindb/** + /tmp. open(2) for anything outside fails at the kernel level.
6b. seccomp-bpf syscall allowlist✅ Livevia systemd SystemCallFilter=@system-service with explicit denies for @mount @swap @reboot @raw-io @cpu-emulation @debug @obsolete @privileged @resources.
6c. Separate executor process + per-instance cgroups⏳ RoadmapSingle-process today.
7. Rate and quota limits🟡 Partial60 req/min and 10 new instance creates/min per IP are live. Per-IP concurrent-instance cap, per-instance query cap, and global circuit breaker are not yet shipped.
8. Session token discipline✅ LiveHMAC-SHA256 over UUIDv7 + 32-byte nonce. Daily secret rotation with current + previous accepted for one cycle. 404 on any verify failure. Tokens never logged.

What this means for an attack like ATTACH '/etc/passwd'

That single attempt trips three independent layers:

  1. Application keyword blocklist — rejects the request with 400 {"error":"forbidden sql keyword: ATTACH"} before SQLite parses it. Comment-stripping defeats /* skip */ ATTACH; matching is case-insensitive.
  2. LIMIT_ATTACHED=0 — if the blocklist is ever disabled, SQLite itself refuses to attach any databases.
  3. Landlock — if both checks above are somehow bypassed, the kernel returns EACCES on the open(2) for any file outside /var/walkindb/**. The attacker cannot read /etc/passwd from walkindb's process no matter what SQL they run.

Same pattern applies to readfile('/etc/shadow'), load_extension('/lib/evil.so'), and every other filesystem-touching function.

What protects your walk-in from someone else's

  • Every walk-in is a separate SQLite file under /var/walkindb/instances/<uuid>/db.sqlite.
  • Session tokens are bound to a specific instance via HMAC. Tampering flips the hash and returns 404.
  • Tokens are never logged. The structured access log stores only the resolved instance ID, not the token plaintext.
  • The walkindb process runs as an unprivileged system user with no capabilities. Systemd hardens it further with NoNewPrivileges, ProtectSystem=strict, MemoryDenyWriteExecute, RestrictNamespaces, and the rest of the standard hardened-unit boilerplate.
  • The TTL sweeper scans every 30 seconds and deletes expired instances. The 10-minute window limits how long a compromised session is useful.

What we log and don't log

walkindb writes exactly these seven fields per HTTP request to a JSONL access log:

  • timestamp
  • source_ip
  • instance_id (omitted on healthz / pre-session requests)
  • http_method
  • http_status
  • sql_byte_length — the byte count, not the text
  • user_agent

We never log the SQL text, bound parameters, result rows, column names, or the session token plaintext. Access logs are retained for 7 days then deleted by an automated job. This is a public commitment in the Privacy Notice; changing it is a material policy change.

What we don't defend against

  • CVEs in SQLite itself. Mitigation: aggressive patching, subscribing to sqlite-users, and the Landlock + seccomp sandbox, which contains most escapes even if SQLite has a bug.
  • Side-channel timing attacks between co-located instances (Spectre/Meltdown class). Mitigation: kernel mitigations, and the 10-minute TTL makes long-lived observation windows impossible.
  • PII safety. We don't claim it. The landing page and the AUP explicitly warn: do not store user PII or regulated data in a walk-in.

Bug bounty

If you find a way past any of the layers above, email [email protected]. We pay a bounty proportional to impact. Responsible-disclosure only — please don't drop 0-days on the open endpoint.

Also see