rostdav

Security concept

The security concept of Rostdav: trust boundaries, threats and the measures against them.

Binding requirements: section 5 of the instructions. Short version of the threat model: architecture.md.

Trust boundaries

  1. Internet ↔ reverse proxy: TLS, HSTS. Rostdav itself speaks only HTTP on localhost or an internal network.
  2. Reverse proxy ↔ Rostdav: take client IP and scheme only from configured trusted proxies (not yet implemented, M4).
  3. Rostdav ↔ data directory: service user only, mode 0700 (implemented in M0).
  4. User ↔ user: strict separation of file areas, access only via shares.
  5. Command line ↔ running server: a Unix socket admin.sock in the data directory (mode 0600, directory 0700, only root and the server's user are served, requests limited to 1 MiB); no network port. ADR 0010.
  6. Rostdav ↔ GitHub (only if an administrator turned the version check on): one HTTPS GET a day to the releases API, nothing else leaves the server. The request has no cookies and no data about users, files or the server, only User-Agent: rostdav/<version>. Certificates are checked against root certificates compiled into the program. The answer is size-limited (1 MiB), parsed as JSON and reduced to a version, a date, a link (must start with https://github.com/pierrejochem/rostdav/) and the notes, which are shown as text (restricted Markdown, no HTML). A failure is recorded and shown, nothing more. Installing an update is a separate, confirmed action (administrator password, minisign signature, backup first); see ADR 0009. ADR 0009.

Threats and mitigations

IDThreatMitigationStatus
S1Password guessing via WebDAV/OCS/loginRate limit per IP and user, lockouts, app password requirement, 2FA (TOTP, ADR 0007: code entry limited to five failures per user and 15 minutes, secrets encrypted with a key outside the database)M4, M12 implemented
S2Stolen app passwordRevocable per device, last use visible, only hash storedopen (M4)
S3Session theft in the web interfaceHttpOnly, Secure, SameSite=Lax, short lifetime, reissue after sign-inopen (M11)
T1Path TraversalPhysical paths independent of input, segment validationopen (M1)
T2Destination header pointing to a foreign hostOwn host and base URL onlyopen (M2)
T3XXE / XML-BombenParser without DTD/entities, size and depth limitopen (M2)
T4CSRF in the web interfaceToken per session, POST only for changesopen (M11)
R1Unnoticed admin actionsEvent logopen (M7)
I1Access to other users' file IDsAuthorization per node, negative testsopen (M2)
I2Internal details in errorsGeneric responses, details only in the log with request IDM0 for /readyz
I3Secrets in logsAuthorization/Cookie marked sensitive, spans without queryimplemented in M0, smoke test verifies it
I4Guessable share links≥ 128 bits of randomness, only hash stored, optional password and expiryopen (M9)
D1Resource exhaustionBody limits per route, Depth: infinity rejected, quota, timeoutsopen (M2)
D2Orphaned uploads fill the diskExpiry time, cleanup jobopen (M5)
E1User calls an admin functionRole check in the service, not only in the UIopen (M7)
E2XSS via file namesAutomatic escaping (askama), strict CSPopen (M11)

Cryptography

PurposeMethod
Account passwords, link passwordsArgon2id (parameters configurable, defaults per OWASP recommendation at the time of implementation)
App passwords, session and link tokens≥ 128 bits from getrandom, stored as SHA-256, constant-time comparison
TOTP-SecretsChaCha20-Poly1305, key from environment/secret file
Recovery codesArgon2id
Content checksumsBLAKE3 (not a security function, integrity only)

Verification

  • crates/rostdav/tests/authz_matrix.rs: anonymous, wrong password, another user, a disabled user and a read-only public link try 26 doors (WebDAV new and legacy paths, traversal variants, chunked upload, versions, trashbin, OCS sharing, public.php) against one user's data; afterwards that data must be byte-identical. It found the lockout weakness described in ADR 0003 (amendment).

  • crates/rostdav/tests/robustness.rs: 3500 seeded pseudo-random requests (traversal and encoding tricks on every WebDAV door, DOCTYPE/entity/nesting/attribute bombs and random bytes as XML, odd header values). Required: no 5xx, nothing slow, no entity expansion, nothing outside the data layout. Coverage-guided cargo fuzz targets live in fuzz/ (own workspace, nightly toolchain, not part of just check): paths (names and logical paths), dav_xml (PROPFIND/PROPPATCH/REPORT bodies), dav_headers_and_urls (Range, request and Destination paths of every URL space), tokens (chunk and blob names). Run: just fuzz <target> [seconds]. First run: 30 s per target, about 3.3 million inputs in total, no panic or broken invariant. These are short runs; a longer run (hours) is the real test. Entry points are rostdav_dav::fuzzing (hidden from the docs).

  • crates/rostdav/tests/load.rs (ignored by default, run with --ignored): a 512 MiB file, 5000 small files, 32 parallel uploads. It found a pool deadlock: Db::resolve held one database connection while asking the pool for a second, so with eight or more simultaneous requests every connection waited for another and the server answered 500 after 30 s (fixed; regression test many_concurrent_resolves_do_not_deadlock).

Dependencies

  • cargo deny in CI: security advisories, licenses, provenance.
  • New dependencies only with a justification (commit or ADR).
  • unsafe_code = "forbid" across the whole workspace.

Reporting vulnerabilities

[Define a contact channel, e.g. the repository's GitHub Security Advisories.]

This page is generated from docs/security.md in the repository.