Skip to content

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.

#AttackBaselineAfter Hardening
10.1Path traversal (download)PASSPASS
10.2Path traversal (upload filename)PASSPASS
10.3IDOR on user filesPASSPASS
10.4Unauthenticated tool accessPASSPASS
10.5Privilege escalation via registrationPASSPASS
10.6Privilege escalation via self-updatePASSPASS
10.7Session token entropyPASSPASS
10.8Password hash strengthPASSPASS
10.9SQL injection (login)PASSPASS
10.10SQL injection (search)PASSPASS
10.11Command injection (filename)PASSPASS
10.12Header injection (CRLF)PASSPASS
10.13Brute force loginFAILPASS
10.14Account enumerationPASSPASS
10.15Default credentialsPASSPASS
10.16Debug endpointsPASSPASS
10.17Directory listingPASSPASS
10.19Session fixationPASSPASS
10.20Token reuse after logoutPASSPASS
10.21API key after revocationPASSPASS
10.22Malicious file uploadPASSPASS
10.23Polyglot filePASSPASS
10.24Audit log completenessPASSPASS
10.25No sensitive data in logsFAILPASS
10.26SSRF cloud metadataPASSPASS
10.27SSRF localhostPASSPASS
10.28SSRF IP encoding bypassesPASSPASS
10.29SVG XSS (script)PASSPASS
10.30SVG XXEPASSPASS
10.31SVG SSRFPASSPASS
10.32SVG event handlerPASSPASS

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_WRONLY flags

[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

SeverityBeforeAfter
Critical (production)10
High (production)60
Moderate (production)42 (transitive, low-risk)
Dev-only20+20+ (semantic-release/vitest chains)

Security Tests Added

Test FileCount
tests/unit/security-svg-sanitize.test.ts20
tests/unit/security-auth-hardening.test.ts14
tests/unit/security-input-validation.test.ts23
tests/unit/security-error-handling.test.ts12
tests/unit/security-network.test.ts19
tests/unit/security-rate-limiting.test.ts13
tests/unit/security-file-processing.test.ts3
tests/integration/security-auth-hardening.test.ts5
tests/integration/security-rate-limiting.test.ts5
Total114

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_MIN to 1000 or lower for internet-facing deployments
  • [ ] Set LOGIN_ATTEMPT_LIMIT to 30
  • [ ] Set ANALYTICS_ENABLED=false if 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_ORIGIN to your exact domain
  • [ ] Use Docker secrets or .env file for sensitive environment variables
  • [ ] Enable filesystem-level encryption on the /data volume
  • [ ] Set MAX_STORAGE_PER_USER_MB appropriate for your deployment
  • [ ] Set SESSION_DURATION_HOURS to 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 /data volume regularly