catacomb/SECURITY_AUDIT.md
Luna 1ef3fe56c6 Add web themes, network binding, UI auth, library maintenance; optimize + fix rescan
Features:
- Port all 7 desktop themes (Dracula, Trans, Emo variants, etc.) to the web UI
- Configurable bind interface (localhost/Tailscale/LAN/all) with detected IPs,
  settable from both the web UI and desktop GUI
- Optional password that gates the whole UI and all API access via session
  cookies (Argon2), with a login page, logout, and middleware; settable from
  both UIs
- Library maintenance: scan for duplicate video IDs and missing assets
  (thumbnail/info.json/description), remove duplicates (scan-and-confirm, with
  out-of-library path guard), and re-fetch missing sidecars via yt-dlp.
  Surfaced in both the web UI and a desktop GUI window.

Optimizations / cleanup:
- Cache has_chapters at scan time (library.rs) instead of re-reading and
  parsing every info.json on each /api/library request
- Use the cached channel size in get_library
- Pool a single SQLite connection in WebState instead of opening one per request
- Remove a 1.2 GB stray nested src/yt-offline duplicate tree

Fixes:
- Rescan no longer clears all thumbnail textures (caused thumbnails to unload);
  prune only removed entries and bump library_generation so the card grid
  refreshes correctly

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 02:58:09 -07:00

7.7 KiB
Raw Blame History

Security Audit — yt-offline

Date: 2026-05-17
Scope: Rust codebase + dependencies
Threat Model: Self-hosted personal YouTube archiving tool; single user accessing via localhost + LAN


Summary

Overall Risk: LOW

No critical vulnerabilities found. The codebase follows security best practices for command execution, database access, and file serving. One informational warning on a transitive dependency (low risk).


Findings

Command Injection — SECURE

All external process invocations use std::process::Command or tokio::process::Command with .arg() calls, bypassing shell interpretation:

  • yt-dlp invocations (src/downloader.rs:160188): User URL passed as a clean argument, not shell-interpreted. Safe even with special characters.
  • ffmpeg invocations (src/web.rs:404415): Similarly safe, using separate .arg() calls.
  • Preview handler (src/web.rs:492498): yt-dlp preview command also uses .arg(), not vulnerable to command injection.

No shell escaping needed; no format!() in command strings.


SQL Injection — SECURE

All database queries in src/database.rs use parameterized statements:

// Correct — parameter substitution
self.conn.execute(
    "INSERT OR REPLACE INTO watched (video_id) VALUES (?1)",
    [video_id],
)?;

// Correct — rusqlite::params! macro
self.conn.execute(
    "INSERT OR REPLACE INTO positions (video_id, position_secs) VALUES (?1, ?2)",
    rusqlite::params![video_id, position_secs],
)?;

No string concatenation in SQL strings. Video IDs come from path parameters and are bound safely.


Path Traversal — SECURE

File serving (src/web.rs:687):

  • Uses tower_http::services::ServeDir::new(&channels_root) — a well-audited crate that prevents .. traversal.

Info.json access (src/web.rs:230236):

  • Videos are looked up only from the scanned library (find_video_info_path).
  • Cannot directly request arbitrary files by path; must exist in the library.

URL path encoding (src/web.rs:176186):

  • Correct percent-encoding of URL segments.
  • Path components are filtered to Component::Normal (rejects .. and symlinks).

Unsafe Code — NONE FOUND

Result of search: grep -r "unsafe" across all .rs files returns zero unsafe blocks.

All memory safety is guaranteed by Rust's type system. No C FFI or raw pointers.


⚠️ Dependency Warning (LOW RISK)

Crate: paste v1.0.15
Status: Unmaintained since 2024-10-07
Severity: Warning (not an error)
Dependency Chain:

paste 1.0.15
  ← egui-wgpu (GUI rendering)
    ← eframe (GUI framework)
      ← yt-offline (your app)

Impact:

  • paste is a procedural macro for code generation; it does not process untrusted input.
  • The crate is used only at compile-time for the GUI stack.
  • No security-critical functionality in paste.
  • Mitigation: Monitor for a replacement in the egui/eframe ecosystem. No action needed now.

Input Validation

Download URLs

  • Handler: post_download() (src/web.rs:339350)
  • Validation: Trimmed and checked for empty; passed directly to yt-dlp via .arg().
  • Risk: Low — yt-dlp validates URLs server-side and fails gracefully on malformed input.

Video IDs

  • Source: Extracted from URL paths and library scans.
  • Usage: Passed to SQL via parameterized queries and used to look up files in the library.
  • Risk: Low — parameterized queries eliminate injection; library lookup prevents arbitrary file access.

Position values

  • Handler: post_resume() (src/web.rs:464479)
  • Validation: Numeric (f64) — deserialized by serde, bounds-checked (position > 3.0 filter).
  • Risk: Low — stored in SQLite as REAL, no string manipulation.

Preview query (yt-dlp URL)

  • Handler: get_preview() (src/web.rs:484527)
  • Validation: Trimmed and checked for empty.
  • Risk: Low — passed to yt-dlp via .arg(), no shell injection.

Data Exposure

Watched status & resume positions

  • Storage: SQLite database (yt-offline.db), local filesystem.
  • Access: Only available to the user running the app (same UID).
  • No network exposure: Positions are stored server-side; browser caches position locally.
  • Risk: Low — local-only; no credentials or PII stored.

Download logs

  • Stored in: Job objects, kept in RAM and shown in the UI.
  • Leakage risk: stderr from yt-dlp is visible in the web UI; sensitive auth errors could be logged.
  • Mitigation: Consider filtering --cookies error messages in production. Currently acceptable for personal use.

yt-dlp output (preview, metadata)

  • Handler: Parses JSON from yt-dlp; returns filtered fields to the browser.
  • Risk: Low — only public YouTube metadata (title, duration, view count).

Configuration & Secrets

cookies.txt

  • Location: Current working directory (hardcoded in downloader.rs).
  • Risk: File is world-readable by default. Recommendation: Ensure restrictive file permissions (chmod 600 cookies.txt).

config.toml

  • Contains: Directory paths, port number, browser name, source_url.
  • Risk: Low — no secrets stored. source_url is read-only from browser.
  • Sensitive data: Only the backup.directory path could leak file structure; this is expected behavior.

Network Security

Web server scope

  • Binding: Configured via web.port in config.toml (default 8080).
  • Current binding: Likely 127.0.0.1:8080 (assumed; verify with netstat).
  • Risk: If bound to 0.0.0.0, anyone on the network can access. Recommendation: Bind to 127.0.0.1 or use a reverse proxy with authentication.

No HTTPS

  • Status: Web UI is served over HTTP.
  • Risk: Medium if exposed to untrusted networks. Recommendation: Use a reverse proxy (nginx, Caddy) with TLS for remote access.

No authentication

  • Status: No login required; access is "security by obscurity" (port number).
  • Risk: Medium if the port is discovered. Recommendation: Add optional HTTP Basic Auth or reverse proxy authentication for LAN access.

Recommendations (Priority Order)

HIGH

  1. Web server bindingFIXED
    • Added web.bind config option (default 127.0.0.1).
    • Set in config.toml to 127.0.0.1 for localhost-only access.
    • Users can change to 0.0.0.0 if they understand the security implications.

MEDIUM

  1. File permissions on cookies.txt — Remind users to chmod 600 cookies.txt.

    • Add note to README.
  2. TLS + authentication for remote access — If exposing to LAN/internet:

    • Use a reverse proxy (nginx, Caddy) with mTLS or Basic Auth.
    • Or add built-in HTTP Basic Auth via an optional config field.
  3. Filter sensitive logs — Consider not echoing yt-dlp stderr if it contains auth errors.

    • Current: Acceptable for personal use. Not a blocker.

LOW

  1. Update paste crate dependency chain — Monitor egui/eframe for a replacement.

    • Not urgent; no security impact in your use case.
  2. Add Content-Security-Policy headers — Prevent XSS if web UI is exposed to untrusted networks.

    • Current: Low risk (browser only, no JavaScript vulnerabilities found).

Conclusion

The codebase demonstrates strong security practices for a personal self-hosted tool:

  • No injection vulnerabilities (command, SQL).
  • No unsafe code.
  • Path traversal protection in place.
  • Parameterized database queries.
  • Safe process spawning.

No blockers for personal use. For public/LAN exposure, implement the HIGH-priority recommendations.


Auditor: Claude Code
Audit Method: Manual code review + cargo audit
Status: Complete