Skip to content

Security

Refringe edited this page Mar 8, 2026 · 1 revision

Security

Anchor LFS includes several security features to protect your data and infrastructure.

HMAC-Signed URLs

All transfer URLs (download, upload, verify) are signed with HMAC-SHA256 to prevent unauthorised access and URL tampering.

How It Works

  1. During batch negotiation, the server generates action URLs with two query parameters:
    • exp: Expiration timestamp (Unix epoch)
    • sig: Base64-encoded HMAC-SHA256 signature computed over the URL path and expiration
  2. When a client requests a download or upload, the server validates the signature and checks expiration
  3. Expired or tampered URLs are rejected with a 403 Forbidden response

Configuration

[options]
# HMAC signing key (hex-encoded, minimum 32 bytes / 64 hex characters)
# If omitted, auto-generated and saved to <data_directory>/signing.key
#signing_key = ""

# How long signed URLs remain valid (default: 10m)
#url_expiry = "10m"

Signing Key Management

  • Auto-generated: If no signing_key is configured, a random 32-byte key is generated at startup and saved to <data_directory>/signing.key so it persists across restarts
  • Manual: Generate a key with openssl rand -hex 32 and set it in the config or via ANCHOR_LFS_SIGNING_KEY
  • Minimum size: 32 bytes (64 hex characters)
  • Memory safety: Signing key material is zeroed in memory during graceful shutdown

Multi-Instance Deployments

If running multiple Anchor LFS instances behind a load balancer, all instances must share the same signing_key. Otherwise, a URL signed by one instance will be rejected by another. Set the key explicitly in the configuration rather than relying on auto-generation.

Rate Limiting

Per-IP rate limiting protects against abuse using a token bucket algorithm.

Configuration

[options]
rate_limit_tokens = 10000
rate_limit_window = "24h"

Behaviour

  • Per-IP: Each client IP gets its own token bucket
  • Real IP detection: Uses the realclientip-go library to extract the real client IP from reverse proxy headers
  • Token bucket: Each IP starts with rate_limit_tokens tokens. Each rate-limited request consumes one token. Tokens are replenished over rate_limit_window.
  • Rate-limited endpoints: Batch, download, verify, and all lock operations
  • Not rate-limited: Upload (uploads are authenticated and size-bounded)
  • Fail-open: If the rate limiter encounters an internal error, requests pass through rather than being rejected (prevents limiter failures from causing outages)
  • 429 response: Rate-limited requests receive HTTP 429 Too Many Requests with a Retry-After header

Input Validation

OID Validation

Object IDs (OIDs) must be exactly 64 hexadecimal characters (representing a SHA-256 hash). Invalid OIDs are rejected.

Upload Integrity

  • Content-Length header is required for all uploads
  • Uploads exceeding max_upload_size are rejected during batch negotiation
  • SHA-256 hash is computed during upload and verified against the declared OID
  • Mismatched objects are deleted immediately

Lock Path Validation

Lock file paths are validated to prevent path traversal and injection:

  • Must be valid relative paths
  • Rejects path traversal sequences (..)
  • Rejects absolute paths
  • Rejects null bytes and other unsafe characters

Endpoint Path Sanitisation

Endpoint paths are sanitised before use as storage directories. Unsafe characters are converted to underscores, and path traversal attempts are rejected at configuration validation time.

Request Body Limits

  • JSON request bodies are capped at 1 MB
  • Batch requests accept a maximum of 1000 objects

Media Type Validation

LFS endpoints strictly validate Content-Type and Accept headers, rejecting requests that don't use application/vnd.git-lfs+json.

HTTP Security

Timeout Configuration

ReadHeaderTimeout: 10 seconds
IdleTimeout:       120 seconds
ReadTimeout:       (none; supports large transfers)
WriteTimeout:      (none; supports large transfers)
  • ReadHeaderTimeout protects against slowloris attacks during the header phase
  • IdleTimeout closes idle keep-alive connections after 2 minutes
  • Read and write timeouts are intentionally omitted to support large file transfers without killing legitimate connections

Streaming

Uploads are streamed directly to storage without in-memory buffering, preventing memory exhaustion from large files.

Reverse Proxy Security

When running behind a reverse proxy:

Warning

Always set base_url in production. Without it, the server relies on X-Forwarded-* headers to construct transfer URLs. If a reverse proxy is misconfigured or absent, clients could manipulate these headers to redirect LFS transfers to a malicious server. Setting base_url eliminates this attack surface entirely.

  1. Use base_url: Setting base_url in the configuration eliminates reliance on X-Forwarded-* headers, removing header-based attack surface
  2. Header validation: The X-Forwarded-Host header is validated as a URL authority to prevent header injection
  3. Restrict direct access: Bind the server to 127.0.0.1 or use Docker networking so it's only accessible through the proxy

See Reverse Proxy for detailed proxy configuration.

Authentication Security

  • GitHub Personal Access Tokens are never logged or stored
  • Auth cache entries are keyed by a hash of the token (not the token itself)
  • Transient authentication failures (network errors) are not cached, preventing permanent lockouts from temporary issues
  • The shared HTTP client for GitHub API calls uses a 10-second timeout

See Authentication for details.

Next Steps

Clone this wiki locally