7.7 KiB
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:160–188): User URL passed as a clean argument, not shell-interpreted. Safe even with special characters. - ffmpeg invocations (
src/web.rs:404–415): Similarly safe, using separate.arg()calls. - Preview handler (
src/web.rs:492–498): 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:230–236):
- 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:176–186):
- 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:
pasteis 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:339–350) - 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:464–479) - 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:484–527) - 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
--cookieserror 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_urlis read-only from browser. - Sensitive data: Only the
backup.directorypath could leak file structure; this is expected behavior.
Network Security
Web server scope
- Binding: Configured via
web.portinconfig.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 to127.0.0.1or 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
- Verify web server binding — Ensure
axumbinds to127.0.0.1only, not0.0.0.0.- Check:
src/web.rs:618where the listener is created. - Mitigation: Add a config option for
bind_addror document localhost-only setup.
- Check:
MEDIUM
-
File permissions on
cookies.txt— Remind users tochmod 600 cookies.txt.- Add note to README.
-
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.
-
Filter sensitive logs — Consider not echoing yt-dlp stderr if it contains auth errors.
- Current: Acceptable for personal use. Not a blocker.
LOW
-
Update
pastecrate dependency chain — Monitor egui/eframe for a replacement.- Not urgent; no security impact in your use case.
-
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