Findings from the audit (50-check harness across guest/user/uploader/staff/ admin) and their fixes: - SSRF: 'Download to Library' and the staged-upload resolve path fetched any http(s) URL. services.validate_remote_url now enforces the e621 media allowlist and open_remote re-validates every redirect hop; the download-task create endpoint and the guest proxy use them, so internal addresses (127.0.0.1, LAN, metadata) are rejected with 400. - Privilege escalation: staff could promote users to staff and demote other staff. Role changes across the staff boundary now require an admin, matching the account-deletion rules; the Users page hides what the backend would refuse. - SPA-only gating: /api/storage/ and /api/duplicates/* were readable by any authenticated account (absolute paths, duplicate groups) while the SPA only shows them to uploaders. They now require CanUpload. - Throttling (REST_FRAMEWORK, env-overridable, counted in Redis): anon 120/min, user 600/min, login 5/min, register 20/hour, guest e621 proxy 60/hour. Login now goes through a throttled view. - e621 API keys are encrypted at rest with a Fernet key derived from SECRET_KEY (apps/accounts/crypto.py); a data migration encrypts existing rows and the column widens first. Reads decrypt transparently, legacy plaintext still works, and a changed SECRET_KEY reads as 'not configured' instead of leaking. Rotating SECRET_KEY now invalidates stored keys as well as signed media URLs. - Hardening: the server refuses to start with DEBUG=False while SECRET_KEY is still the development default. Verified: corrected harness 50/50 (guest visibility, IDOR, signed-URL tamper/expiry, staged-upload/similarity privacy, role matrix, SSRF), login throttles at the 6th attempt with 429, anon polling unaffected, the guest proxy still reaches allowlisted hosts, live e621 auth works with the decrypted key, and DB rows hold only ciphertext.
9.4 KiB
9.4 KiB
J621 Roadmap / To-Dos
Working list of what's still missing, roughly in priority order. Check items off as they land.
1. Library management
-
Duplicates engine
- Exact MD5 duplicate detection with grouped results
- Perceptual similarity (aHash, dHash, pHash, wHash) with threshold slider
and algorithm toggles (
imagehashserver-side) - Visual similarity groups: pagination, selection, delete and dismiss
-
Delete & storage page
- Storage overview (watched folder, media folder, temp)
- Delete by J-ID with preview grid and bulk selection (plus per-copy deletion of duplicate locations)
- Temp folder cleanup
-
Library search upgrades
- Search by tags (custom + e621 tags) with a Filename / Tags / Both selector
- Tag cloud in the sidebar (click to search, hidden from guests for blacklisted items)
- Status filter (matched / not_found / deleted / custom / unknown)
-
Ephemeral similarity check (
/similar)- Drop a file: exact MD5 match, perceptual matches against the library, and e621 IQDB candidates (auto-run for images)
- Nothing enters the library: temp files are wiped on startup, after
SIMILARITY_TTL_MINUTES(default 30), on demand and bymanage.py cleanup_similarity
2. e621 integration
- Download progress bar on the detail view
- Backend download task (status, progress %, downloaded/total bytes, speed)
- Progress endpoint the SPA polls; cancel support
- Frontend progress bar with speed and cancel on
/detail/<post> - The status footer's "Active Workers" now counts running download tasks
- Download to client — streams the e621 original straight to the browser (works for guests; host-restricted proxy, no library write)
- Match local files to e621
- Match by MD5 from the library detail, plus manual post ID linking (with an MD5-mismatch warning) and unlink
- Batch cache status for the whole library (
matched/not_found/deleted) via background scan tasks (/api/matches/) andmanage.py match_e621 - Show match status + e621 metadata in the library: status badge and
match card on the detail, and
not_found/deletedstatus filters
- IQDB reverse search
- Search from a local file (
POST /iqdb_queries.jsonfrom the library detail, using the signed raw file) - Show candidate posts to link (thumbnail, rating, score, favs, tags; select then confirm — same flow as uploads, exact MD5 marked)
- Search from a local file (
- Follows
- Follow tags from the
+on tag chips (online + library detail) and pools from the Follow button on a pool page (validated against e621, first feed fetched immediately) - Followed screens: cover cards (latest image), unseen badges and Mark seen per follow
- Merged newest-first feed with per-follow filter and unseen-only toggle
- Blacklisted-tag cloud built in a daemon thread, then polled (10 min TTL; the user's e621 blacklist, guest default as fallback)
- "Followed" nav entry
- Periodic sync commands:
sync_followed_tagsandsync_followed_pools(one e621 search per unique tag/pool)
- Follow tags from the
- Pools browser (
/pools+/pools/<id>)- Index search by name; category, active/deleted and sort filters; pagination according to the e621 OpenAPI spec
- Cover thumbnails from each pool's first post (one batched post call, blacklist-aware) and deleted-pool markers
- Pool detail: DText description, posts kept in pool order with chunked loading and in-library badges, blacklist reveal toggle, Follow button
Fshortcut to favorite a post (online detail), tag finder in the Ctrl+K palette (e621 tag suggestions with follow toggles, recent searches, and "search e621 for …")
3. Uploads & ingestion pipeline
Files now stage first and are resolved before entering the library.
- Backend staging storage
TempUploadmodel: user, md5, filename, temp path, status (pending/visual_match/completed/error), resolution- Files land in a temp folder first; only completed uploads move into the watched library folder
- Pending/upload list endpoint, file serving, discard endpoint
cleanup_temp_uploadscommand for old staged files
- Auto-upload / auto-match
- MD5 computed on staging; exact duplicates resolve immediately
- MD5 batch-checked against e621; matches auto-complete with post metadata stored and rating seeded
- IQDB similarity on upload (SPA-driven)
- Automatic + manual IQDB checks with candidate posts
- "Visual Similarity Detected" state with candidate picker
- Perceptual-hash comparison against the library (staged uploads are flagged with their library matches as soon as they land)
- Upload UI
- Three-column board: Pending & Unmatched / Visual Similarity Detected / Auto-uploaded & Indexed
- Metadata modal (link to e621 post, IQDB candidates, custom metadata)
- Per-file progress plus batch processing indicator
4. Staff tools
- Optimization modal (client-side, no server processing)
- Browser pipeline in a Web Worker: Mediabunny/WebCodecs for video (hardware-accelerated where available), jSquash (MozJPEG/OxiPNG/libwebp) for images, gifenc/gifuct-js + UPNG for GIF/APNG
- Options per file type: image quality/resolution; animation scale/fps/palette; video quality/resolution/codec/container/hardware preference (codec support detected per browser)
- Original vs processed previews with sizes and savings, progress bar with ETA and a "Processing the file…" state
- Apply overwrites the same J-ID:
POST /api/files/J-x/optimize/replaces the file(s) and recomputes MD5/size/perceptual hashes (409 when the result matches another item, 400 when it is identical) - Animated formats are detected by header (APNG
acTLchunk, WebPVP8Xflag), so APNGs keep their frames and animated WebP is refused with a notice instead of being flattened (browsers have no animated WebP encoder)
- Stats dashboard (
/stats, staff)- CPU / RAM / GPU / disk usage (
psutil+nvidia-smi; capacity bars follow the design system's 80% / 95% colour thresholds) - Active jobs list (downloads + match scans with progress) plus the recently finished ones
- Live log tail with level highlighting and an auto-scroll toggle
(root logging now also writes a rotating
backend/logs/j621.log)
- CPU / RAM / GPU / disk usage (
5. Shell & polish
- 18+ entry screen: an age check gates the app before anything renders
(remembered per browser in
localStorage) - Toasts for action results (success/error) plus a shared confirm dialog for destructive actions; form-field validation stays inline
- Mobile bottom sheets for metadata panels (<768px): detail asides (Library/Online/Similar) and long pool descriptions
- Profile pictures: staff Users page and a self-service Account picker (searchable library grid, remove supported) set avatars from library J-IDs
- Profile extras: per-user landing page, default rating filter/sort, items per page and thumbnail size (synced to the account, applied on load)
- Backend-less local mode: with no backend connected the SPA runs on the e621-facing pages only (Online, Pools) using credentials stored in the browser, and the shell offers a "Setup Backend" button instead
- Command palette: tag finder, recent searches, navigation commands (Ctrl+K / ⌘K, reachable from anywhere in the shell)
6. Infrastructure
- Guest blacklist refresh on a timer (
refresh_guest_blacklistvia cron/systemd) - Follow sync on a timer (
sync_followed_tags+sync_followed_poolsvia cron/systemd, e.g. every 30 minutes) - Similarity temp cleanup on a timer (
cleanup_similarityvia cron; the TTL also cleans lazily when new checks are created) - Production setup: build the SPA, serve via Nginx (static +
/media+/library), systemd unit for Waitress - Automated tests (backend API + frontend components)
- Backfill e621 metadata for items downloaded before metadata was stored —
covered by the match scan (
/api/matches/scopeall, or per-item Recheck) andmanage.py match_e621 --scope all
Dependencies / notes
- Duplicates and upload visual-similarity share the perceptual hashing layer
(
imagehashserver-side; no external hashing service). - Download progress, optimization and the stats "Active Workers" count share the same task style; optimization itself runs in the browser (the home server is too weak for ffmpeg) and only the result is uploaded.
- IQDB and e621 matching depend on e621 credentials being configured; the SPA
talks to e621 directly for browsing, while the backend e621 client
(
apps/library/e621.py) handles metadata matching and batch scans. - Anything touching e621 endpoints follows the OpenAPI spec (https://e621.wiki/openapi.yaml) — see AGENTS.md for how to fetch and which response shapes to watch out for.
- Security review done (Aug 2026): permissions verified per role with a repeatable harness, e621 keys encrypted at rest, rate limits added and the download paths host-allowlisted. See AGENTS.md for the rules to keep.