Security contract¶
A short reference of what FigureCollector guarantees, what it doesn't, and where to look for the implementation.
What we guarantee¶
Container surface¶
FROM scratchbackend — no shell, no libc, no package manager. Source:server/Dockerfile.- Distroless nginx for both frontend and docs. Source:
client/Dockerfile,docs/Dockerfile. - Read-only rootfs,
cap_drop: ALL,no-new-privileges: trueon every service. Source:docker-compose.yml,docker-compose.prod.yml,docker-compose.docs.yml. - Non-root user (
65532) everywhere. - No OpenSSL anywhere in the dependency tree. Verified per-PR with
cargo tree -i openssl-sys. Source:Cargo.toml(cookbook of Rustls + aws-lc-rs alternatives).
Network surface¶
- Only one host port exposed in production — the frontend nginx. Backend + Postgres + Garage are internal-only. Source:
docker-compose.prod.yml. - Strict CSP, COOP, CORP, Referrer-Policy, Permissions-Policy on the frontend nginx. Source:
client/nginx.conf. - Rate limiting on auth-sensitive and anonymous gift-share routes via
tower_governor. Source:server/src/routes/mod.rs. - WebSocket upgrades validate the
Originheader against the configured frontend URL (anti-CSWSH). Source:server/src/routes/ws.rs.
Auth¶
- OIDC with PKCE (
openidconnectcrate). - Local accounts with Argon2id (
argon2crate), default parameters m=19456,t=2,p=1. - Session-fixation defense: session id rotation on every login.
- Cookies:
HttpOnly,SameSite=Lax, andSecurederived from the public scheme (on for any HTTPS deployment). - CSRF: SameSite=Lax plus a Fetch-Metadata backstop that refuses cross-site state-changing requests; OIDC carries
state+ PKCE + nonce, and the login initiation itself refuses a cross-site navigation (login-CSRF guard). Source:server/src/routes/mod.rs,server/src/routes/auth.rs.
Data handling¶
- Photos are private by default. Served through the backend with ownership checks; no direct bucket exposure.
- EXIF strip on every upload to remove location metadata.
- Magic-bytes mimetype validation rather than trusting the
Content-Typeheader. - Size + dimension caps on uploads.
External fetches¶
- MFC scraping: rate-limited (1 req/s per user), aggressive 24 h Postgres cache, identifiable
User-Agentso MFC ops can contact us if there's a problem. - AniList: same rate-limit pattern.
- SSRF egress filter: user-settable outbound URLs (notification webhook / ntfy / Apprise) are scheme-allow-listed and their resolved IPs rejected when private / loopback / link-local / CGNAT / ULA / metadata; the shared HTTP client follows only same-host redirects, so a 3xx can't pivot to an internal target. Source:
server/src/external/notify_channel.rs,server/src/main.rs. - MangaCollector server allow-list: the instance a user links to is not free-form — it must be an admin-approved origin (a user submits →
pending→ admin approves / revokes). Submitting runs the SSRF guard before the origin is stored, and every cross-link fetch is gated onstatus = 'approved'over the same no-redirect client, so apending/revokedserver is never fetched. Revoking an origin notifies every linked user and disables their integration. Source:server/src/domain/manga_servers.rs,server/src/routes/manga.rs.
What we don't guarantee¶
- End-to-end encryption. The database and S3 are server-side; an attacker with database access could read everything. If you need at-rest encryption, configure it at the volume / filesystem level (LUKS, ZFS native encryption, …).
- Offline cryptographic erase. Deleting a user only marks rows + drops bucket objects asynchronously; a forensic recovery on the underlying disk could still surface deleted content.
- Anonymity from the host operator. A self-hosting admin (you) can see every row in the database. There is no "blind" mode.
Where to look¶
| Concern | Source |
|---|---|
| Hardening posture | docker-compose*.yml, */Dockerfile, */nginx.conf |
| CSP + security headers | client/nginx.conf, docs/nginx.conf |
| Auth flows | server/src/auth/, server/src/routes/auth.rs |
| Rate limiting | server/src/routes/mod.rs (tower_governor) |
| CSRF + egress filtering | server/src/routes/mod.rs (Fetch-Metadata), server/src/external/notify_channel.rs (SSRF) |
| Image upload validation | server/src/domain/photo.rs |
| Migrations + schema | server/migrations/ (SQL), server/src/migration/ (Rust wrappers) |
| Dependency policy | server/Cargo.toml (Rustls everywhere; no openssl-sys), client/package.json (pnpm-only) |
Reporting a vulnerability¶
Open a GitHub issue marked PRIVATE via the security advisory feature, or email the maintainer. The repo has CodeQL configured (.github/codeql.yml) so common SAST findings get caught at PR time.