-
-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Anchor LFS includes several security features to protect your data and infrastructure.
All transfer URLs (download, upload, verify) are signed with HMAC-SHA256 to prevent unauthorised access and URL tampering.
- 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
-
- When a client requests a download or upload, the server validates the signature and checks expiration
- Expired or tampered URLs are rejected with a
403 Forbiddenresponse
[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"-
Auto-generated: If no
signing_keyis configured, a random 32-byte key is generated at startup and saved to<data_directory>/signing.keyso it persists across restarts -
Manual: Generate a key with
openssl rand -hex 32and set it in the config or viaANCHOR_LFS_SIGNING_KEY - Minimum size: 32 bytes (64 hex characters)
- Memory safety: Signing key material is zeroed in memory during graceful shutdown
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.
Per-IP rate limiting protects against abuse using a token bucket algorithm.
[options]
rate_limit_tokens = 10000
rate_limit_window = "24h"- Per-IP: Each client IP gets its own token bucket
-
Real IP detection: Uses the
realclientip-golibrary to extract the real client IP from reverse proxy headers -
Token bucket: Each IP starts with
rate_limit_tokenstokens. Each rate-limited request consumes one token. Tokens are replenished overrate_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 Requestswith aRetry-Afterheader
Object IDs (OIDs) must be exactly 64 hexadecimal characters (representing a SHA-256 hash). Invalid OIDs are rejected.
-
Content-Lengthheader is required for all uploads - Uploads exceeding
max_upload_sizeare rejected during batch negotiation - SHA-256 hash is computed during upload and verified against the declared OID
- Mismatched objects are deleted immediately
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 paths are sanitised before use as storage directories. Unsafe characters are converted to underscores, and path traversal attempts are rejected at configuration validation time.
- JSON request bodies are capped at 1 MB
- Batch requests accept a maximum of 1000 objects
LFS endpoints strictly validate Content-Type and Accept headers, rejecting requests that don't use application/vnd.git-lfs+json.
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
Uploads are streamed directly to storage without in-memory buffering, preventing memory exhaustion from large files.
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.
-
Use
base_url: Settingbase_urlin the configuration eliminates reliance onX-Forwarded-*headers, removing header-based attack surface -
Header validation: The
X-Forwarded-Hostheader is validated as a URL authority to prevent header injection -
Restrict direct access: Bind the server to
127.0.0.1or use Docker networking so it's only accessible through the proxy
See Reverse Proxy for detailed proxy configuration.
- 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.
- Configuration: Security-related configuration options
- Reverse Proxy: Secure proxy setup
- Troubleshooting: Security-related issues