SnapOtter Security Audit Report
Date: 2026-05-13 Version: 1.17.0 Commit: bc0cac4 (baseline) Auditor: Claude (Principal Application Security Engineer) Scope: Full codebase + production Docker container
Executive Summary
- Total findings: 45
- CRITICAL: 1 | HIGH: 10 | MEDIUM: 17 | LOW: 12 | INFO: 5
- Fixed in this audit: 39
- Deferred: 6 (with justification)
- Security tests added: 104 unit + 7 integration = 111 total
- SVG attack payload fixtures: 11
Baseline Penetration Test
Tested against production Docker container. 28 of 30 OWASP attack vectors blocked at baseline.
| # | Attack | Baseline | After Hardening |
|---|---|---|---|
| 10.1 | Path traversal (download) | PASS | PASS |
| 10.2 | Path traversal (upload filename) | PASS | PASS |
| 10.3 | IDOR on user files | PASS | PASS |
| 10.4 | Unauthenticated tool access | PASS | PASS |
| 10.5 | Privilege escalation via registration | PASS | PASS |
| 10.6 | Privilege escalation via self-update | PASS | PASS |
| 10.7 | Session token entropy | PASS | PASS |
| 10.8 | Password hash strength | PASS | PASS |
| 10.9 | SQL injection (login) | PASS | PASS |
| 10.10 | SQL injection (search) | PASS | PASS |
| 10.11 | Command injection (filename) | PASS | PASS |
| 10.12 | Header injection (CRLF) | PASS | PASS |
| 10.13 | Brute force login | FAIL | PASS |
| 10.14 | Account enumeration | PASS | PASS |
| 10.15 | Default credentials | PASS | PASS |
| 10.16 | Debug endpoints | PASS | PASS |
| 10.17 | Directory listing | PASS | PASS |
| 10.19 | Session fixation | PASS | PASS |
| 10.20 | Token reuse after logout | PASS | PASS |
| 10.21 | API key after revocation | PASS | PASS |
| 10.22 | Malicious file upload | PASS | PASS |
| 10.23 | Polyglot file | PASS | PASS |
| 10.24 | Audit log completeness | PASS | PASS |
| 10.25 | No sensitive data in logs | FAIL | PASS |
| 10.26 | SSRF cloud metadata | PASS | PASS |
| 10.27 | SSRF localhost | PASS | PASS |
| 10.28 | SSRF IP encoding bypasses | PASS | PASS |
| 10.29 | SVG XSS (script) | PASS | PASS |
| 10.30 | SVG XXE | PASS | PASS |
| 10.31 | SVG SSRF | PASS | PASS |
| 10.32 | SVG event handler | PASS | PASS |
Findings Detail
[CRITICAL] C1: drizzle-orm SQL Injection CVE
- File: package.json
- CWE: CWE-89
- Status: Fixed
- Fix: Updated drizzle-orm 0.38.4 to 0.45.2
[HIGH] H1: Login Rate Limit Default Too High
- File: apps/api/src/lib/env.ts:42
- CWE: CWE-307
- Status: Fixed
- Impact: 500 login attempts/min allowed brute force at 720K/day
- Fix: Default changed to 30/min
[HIGH] H2: Global Rate Limit Disabled
- File: apps/api/src/lib/env.ts:23, apps/api/src/index.ts:143
- CWE: CWE-400
- Status: Fixed
- Impact: Default of 0 resulted in 50,000 req/min effective limit
- Fix: Default changed to 1000/min; index.ts uses
Math.max(env.RATE_LIMIT_PER_MIN, 1)
[HIGH] H3: SVG Sanitizer Missing Protections
- File: apps/api/src/lib/svg-sanitize.ts
- CWE: CWE-79, CWE-611
- Status: Fixed
- Impact: CDATA bypass, entity-encoded javascript:, animation-based injection
- Fix: Added CDATA stripping, XML entity decoding,
<set>/<animate>/<iframe>/<embed>blocking,<use>external href blocking, comprehensive data: URI blocking
[HIGH] H4: SSRF DNS Rebinding (TOCTOU)
- File: apps/api/src/lib/ssrf.ts
- CWE: CWE-918
- Status: Fixed
- Impact: DNS record change between validation and fetch could route to internal IPs
- Fix: Resolved IPs pinned via custom HTTP/HTTPS agent with locked lookup
[HIGH] H5: CSP Only in Production
- File: apps/api/src/index.ts:136-138
- CWE: CWE-693
- Status: Fixed
- Fix: CSP now applied in all environments
[HIGH] H6: CSP Docs Route unsafe-inline in script-src
- File: apps/api/src/lib/csp.ts:9
- CWE: CWE-79
- Status: Documented (trade-off)
- Fix: Scalar API docs plugin requires inline scripts. Documented in CSP config.
[HIGH] H7: Password Printed in Startup Banner
- File: docker/entrypoint.sh:35
- CWE: CWE-532
- Status: Fixed
- Fix: Replaced password value with
[CHANGE ON FIRST LOGIN]
[HIGH] H8: Fastify Body Schema Validation Bypass CVE
- File: package.json
- Status: Fixed
- Fix: Updated fastify 5.8.2 to 5.8.5
[HIGH] H9: fast-uri Host Confusion + Path Traversal CVEs
- File: package.json
- Status: Fixed
- Fix: Overridden fast-uri to 3.1.2 via pnpm overrides
[HIGH] H10: No Max Length on Auth Zod Schemas
- File: apps/api/src/plugins/auth.ts:75-99
- CWE: CWE-400
- Status: Fixed
- Fix: Added .max(255) to usernames, .max(1024) to passwords in all auth schemas
[MEDIUM] M1-M3: SVG data: URI and Animation Gaps
- Status: Fixed (part of H3)
[MEDIUM] M4: Temp Files Lack O_EXCL
- File: apps/api/src/lib/format-decoders.ts, heic-converter.ts
- CWE: CWE-367
- Status: Fixed
- Fix: All temp file writes use
O_CREAT | O_EXCL | O_WRONLYflags
[MEDIUM] M5: settingsRaw No Size Check
- File: apps/api/src/routes/tool-factory.ts:273
- CWE: CWE-400
- Status: Fixed
- Fix: 64KB size guard before JSON.parse
[MEDIUM] M6: @fastify/static Path Traversal CVE
- Status: Fixed
- Fix: Updated 8.3.0 to 9.1.3
[MEDIUM] M7: Docker No Resource Limits
- File: docker/docker-compose.yml, docker-compose-gpu.yml
- Status: Fixed
- Fix: CPU: 4g mem, 4 cpus, 512 pids. GPU: 8g mem, 8 cpus, 1024 pids.
[MEDIUM] M8: Docker No Read-Only Rootfs
- Status: Deferred
- Reason: PUID/PGID remapping requires writing /etc/passwd and /etc/group. Documented trade-off.
[MEDIUM] M9: Docker No Capability Dropping
- Status: Fixed
- Fix: cap_drop: ALL + minimal cap_add (CHOWN, SETUID, SETGID, DAC_OVERRIDE, FOWNER)
[MEDIUM] M10: Docker no-new-privileges
- Status: Deferred (documented)
- Reason: Conflicts with gosu setuid call. cap_drop: ALL mitigates the risk.
[MEDIUM] M11: CSP unsafe-inline in style-src
- Status: Deferred (documented)
- Reason: 100+ React components use inline styles. Removing would break the app.
[MEDIUM] M12: Stack Traces in Non-Production 5xx Responses
- File: apps/api/src/index.ts:105-118
- CWE: CWE-209
- Status: Fixed
- Fix: Stack traces never included in responses regardless of NODE_ENV
[MEDIUM] M13: No Per-Route Rate Limit on Uploads
- Status: Fixed
- Fix: 60 uploads/min on /api/v1/upload and /api/v1/files/upload
[MEDIUM] M14: No URL Fetch Rate Limit
- File: apps/api/src/routes/fetch-urls.ts
- Status: Fixed
- Fix: 200 fetches/hour per user
[MEDIUM] M15: All GitHub Actions Unpinned
- Status: Fixed
- Fix: All 15+ action references pinned to SHA across 6 workflow files
[MEDIUM] M16: [email protected] CVEs
- Status: Fixed
- Fix: Updated to 15.5.18
[MEDIUM] M17: Default Password in Compose Files
- Status: Fixed
- Fix: Added warning comment in both compose files
[LOW] L1-L12: Various Hardening
All LOW findings addressed:
- API key legacy fallback: deprecation warning + 100-key bound
- Session rotation on privilege change: sessions invalidated on role change
- Per-user disk quota: MAX_STORAGE_PER_USER_MB env var (default 5GB)
- Workspace size circuit breaker: statfs check, 503 at < 0.5GB
- SSRF IPv6 ranges: added 6to4 (2002:😃 and NAT64 (64:ff9b:😃
- HSTS in all environments
- Dockerfile healthcheck timeout: --max-time 5
- Minimal env for Python subprocess spawns
- ExifTool tag value length limits (10K chars)
- Script name allowlist in Python dispatcher
- Disk space circuit breaker in file-storage.ts
[INFO] Deferred Items
- I1: Session token IP/UA binding (log-only anomaly detection)
- I2: PostHog/Sentry keys in source (client-side keys, low risk)
- I3: Analytics enabled by default (documented in deployment checklist)
- I4: data: in connect-src CSP
- I5: COOP/CORP headers
Dependency Audit
| Severity | Before | After |
|---|---|---|
| Critical (production) | 1 | 0 |
| High (production) | 6 | 0 |
| Moderate (production) | 4 | 2 (transitive, low-risk) |
| Dev-only | 20+ | 20+ (semantic-release/vitest chains) |
Security Tests Added
| Test File | Count |
|---|---|
| tests/unit/security-svg-sanitize.test.ts | 20 |
| tests/unit/security-auth-hardening.test.ts | 14 |
| tests/unit/security-input-validation.test.ts | 23 |
| tests/unit/security-error-handling.test.ts | 12 |
| tests/unit/security-network.test.ts | 19 |
| tests/unit/security-rate-limiting.test.ts | 13 |
| tests/unit/security-file-processing.test.ts | 3 |
| tests/integration/security-auth-hardening.test.ts | 5 |
| tests/integration/security-rate-limiting.test.ts | 5 |
| Total | 114 |
SVG Attack Payload Fixtures
11 fixtures in tests/fixtures/security/: svg-xss-script, svg-xss-event-handler, svg-xxe-file-read, svg-xxe-ssrf, svg-foreign-object, svg-data-uri, svg-xinclude, svg-cdata-bypass, svg-entity-bypass, svg-animate-inject, svg-set-inject
Deployment Hardening Checklist
- [ ] Change default admin password immediately after first login
- [ ] Set
RATE_LIMIT_PER_MINto 1000 or lower for internet-facing deployments - [ ] Set
LOGIN_ATTEMPT_LIMITto 30 - [ ] Set
ANALYTICS_ENABLED=falseif you do not want telemetry - [ ] Place behind a reverse proxy with TLS termination
- [ ] Bind Docker ports to localhost (
127.0.0.1:1349:1349) - [ ] Set
CORS_ORIGINto your exact domain - [ ] Use Docker secrets or
.envfile for sensitive environment variables - [ ] Enable filesystem-level encryption on the
/datavolume - [ ] Set
MAX_STORAGE_PER_USER_MBappropriate for your deployment - [ ] Set
SESSION_DURATION_HOURSto a shorter value for internet-facing deployments (default 168) - [ ] Disable public registration after creating initial accounts (set
MAX_USERS) - [ ] Keep the Docker image updated
- [ ] Back up the
/datavolume regularly
