5.0 KiB · text History 67bd0f9

File Upload & Storage Security

Implemented Controls

Storage Architecture

lib/git_gud/storage/

  • Storage.Disk — Local filesystem storage
  • Storage.Garage — S3-compatible object storage
  • Unified interface for put/get/delete/presign operations
  • No multipart upload support — assembly happens in memory for chunked uploads

Storage Controller

lib/git_gud_web/controllers/storage_controller.ex

  • Signed/capability tokens for file access
  • Disk.verify_token/2 — validates token before file access
  • Token contains bucket + key binding (prevents path traversal)
  • Presigned URLs with configurable expiry

Upload Size Limits

Component Limit File/Line
Runner proto body 50MB runner_controller.ex:700
Artifact upload 1GB actions_artifact_controller.ex:76
Cache chunk upload 200MB actions_cache_controller.ex:91
Package upload 200MB PackageRegistryController
LFS objects Configurable LfsController

Runner Artifact Storage

lib/git_gud_web/controllers/actions_artifact_controller.ex

  • Authenticated by runner JWT (RunnerJwtAuth plug)
  • Run-scoped: job only accesses its own run’s artifacts (own_run/2)
  • Storage key sanitized: String.replace(name, ~r/[^A-Za-z0-9._\-]/, "_")
  • Storage path: gitgud-artifacts/run-<run_id>/<sanitized_name>

Runner Cache Storage

lib/git_gud_web/controllers/actions_cache_controller.ex

  • Authenticated by runner JWT
  • Entry-scoped: only the job that reserved can upload (reserved_by?/2)
  • Upload expiry: 600 seconds (10 minutes)
  • Chunk validation: Content-Range header parsed and validated
  • Finalization: chunks assembled in offset order, no gaps allowed

Package Registry Uploads

lib/git_gud_web/controllers/package_registry_controller.ex

  • Size limit: 200MB per upload
  • Content-type validation based on package format
  • Authentication via registry token or PAT
  • Format-specific parsing (Formats.Deb, Formats.Rpm, Formats.Hex, etc.)

Security Observations

High

No file type validation in storage controller

  • StorageController relies on upstream validation
  • No magic byte checking (e.g., PE/ELF/Mach-O for binaries)
  • No MIME type verification against declared Content-Type
  • Recommendation: add content-type validation based on actual file content
  • Use :erlang.md5/1 or similar to check magic bytes for known formats

No virus/malware scanning

  • Uploaded artifacts, packages, and LFS objects not scanned
  • Malicious binaries can be distributed through the forge
  • Recommendation: integrate malware scanner (e.g., ClamAV)
  • Scan on upload, quarantine flagged files
  • Consider async scanning with upload allowed pending scan

Large body reads can cause memory exhaustion

  • actions_artifact_controller.ex:76 — 1GB read per upload
    • Multiple concurrent uploads can exhaust server memory
    • Recommendation: add per-user concurrent upload limits
    • Consider streaming uploads directly to object storage
  • actions_cache_controller.ex:219 — Cache finalization assembles chunks in memory
    • Large cache entries loaded entirely into memory
    • Recommendation: implement streaming assembly or limit max cache entry size

Medium

Content-type spoofing in artifact downloads

  • actions_artifact_controller.ex:150 — Downloads return artifact.content_type from database
  • Attacker can set arbitrary content_type during artifact creation
  • If artifacts served inline in browser, could enable XSS via HTML/JS content types
  • Recommendation: validate and sanitize content_type field
  • Default to application/octet-stream unless explicitly set to safe MIME type
  • Add allowlist of allowed content types

Cache chunk upload not strictly validated

  • actions_cache_controller.ex:87-109 — Upload validates Content-Range header
  • No validation that chunk offset matches expected position
  • Recommendation: add server-side offset tracking per upload session
  • Reject chunks that don’t match expected sequence

Presigned URL expiry too long

  • actions_cache.ex:46 — Download expiry: 300 seconds
  • actions_cache.ex:47 — Upload expiry: 600 seconds
  • Long expiry increases window for URL theft/misuse
  • Recommendation: reduce download expiry to 60 seconds
  • Keep upload expiry at 300 seconds (already short)

No upload checksums

  • Uploaded files not verified against client-provided checksums
  • Corruption during upload not detected
  • Recommendation: accept Content-MD5 header from clients
  • Verify checksum after upload, return error on mismatch

Low

Upload URL construction

  • actions_artifact_controller.ex:184-187 — Upload URLs constructed via string interpolation
  • No validation that endpoint.url() is safe
  • Recommendation: use Phoenix URL helpers for URL generation
  • Prevent URL scheme injection

No rate limiting on uploads

  • Upload endpoints have no rate limiting
  • Can be used for storage exhaustion attacks
  • Recommendation: add per-user upload limits (e.g., 10 uploads/hour)
  • Consider storage quota per repository/organization