Documentation Index
Fetch the complete documentation index at: https://mintlify.com/TecharoHq/Anubis/llms.txt
Use this file to discover all available pages before exploring further.
Anubis uses cryptographic signing to secure challenge tokens and prevent tampering. This page covers key generation, JWT signing, and cookie security.
JWT Signing
Challenge solutions are stored in JWT (JSON Web Tokens) signed with either:
- Ed25519 (recommended) - Elliptic curve signatures
- HMAC-SHA512 - Symmetric key signing
Ed25519 (Recommended)
Ed25519 provides fast, secure signatures with small key sizes.
Generate Key
# Generate 32-byte (256-bit) random key
openssl rand -hex 32 > /etc/anubis/ed25519.key
# Example output:
# a3f5d8c2e1b4f6a9c7d3e8f1a2b5c6d7e3f4a1b2c5d6e7f8a9b1c2d3e4f5a6b7
# Via command-line flag
anubis --ed25519-private-key-hex-file /etc/anubis/ed25519.key
# Via environment variable
export ED25519_PRIVATE_KEY_HEX_FILE=/etc/anubis/ed25519.key
# Or inline (not recommended)
export ED25519_PRIVATE_KEY_HEX=a3f5d8c2e1b4f6a9c7d3e8f1a2b5c6d7e3f4a1b2c5d6e7f8a9b1c2d3e4f5a6b7
HMAC-SHA512
Symmetric signing using a shared secret.
# Generate secret
openssl rand -base64 64
# Configure
export HS512_SECRET="your-secret-here"
Note: Ed25519 is preferred. HMAC-SHA512 is legacy.
Random Key (Development Only)
If no key is configured, Anubis generates a random key on startup:
WARN generating random key, Anubis will have strange behavior when multiple
instances are behind the same load balancer target
Consequences:
- Challenges invalidated on restart
- Multi-instance deployments won’t work
- Users must re-solve challenges after deployment
⚠️ Never use random keys in production
Key Management
Storage Requirements
Keys must be:
- Persistent - Survives container/pod restarts
- Secret - Not committed to version control
- Accessible - Readable by Anubis process
File Permissions
# Secure key file
chown anubis:anubis /etc/anubis/ed25519.key
chmod 400 /etc/anubis/ed25519.key
# Verify
ls -l /etc/anubis/ed25519.key
# -r-------- 1 anubis anubis 64 Jan 1 12:00 /etc/anubis/ed25519.key
Docker/Kubernetes
Docker Secret
# Create secret
docker secret create anubis_key /path/to/ed25519.key
# Use in service
docker service create \
--secret anubis_key \
--env ED25519_PRIVATE_KEY_HEX_FILE=/run/secrets/anubis_key \
ghcr.io/techarohq/anubis:latest
Kubernetes Secret
# Create secret from file
kubectl create secret generic anubis-key \
--from-file=ed25519.key=/path/to/ed25519.key
apiVersion: apps/v1
kind: Deployment
metadata:
name: anubis
spec:
template:
spec:
containers:
- name: anubis
image: ghcr.io/techarohq/anubis:latest
env:
- name: ED25519_PRIVATE_KEY_HEX_FILE
value: /secrets/ed25519.key
volumeMounts:
- name: signing-key
mountPath: /secrets
readOnly: true
volumes:
- name: signing-key
secret:
secretName: anubis-key
defaultMode: 0400
Key Rotation
- Generate new key
- Deploy Anubis with new key
- Existing JWTs remain valid until expiry
- New challenges use new key
No downtime required. Users with old cookies will re-solve challenges.
Cookie Security
Anubis sets multiple security flags on cookies.
Secure Flag
Requires HTTPS:
# Enable (default)
anubis --cookie-secure=true
# Disable (development only)
anubis --cookie-secure=false
If --cookie-secure=false, SameSite automatically downgrades from None to Lax.
SameSite
Controls cross-site cookie sending:
# Options: None, Lax, Strict, Default
anubis --cookie-same-site None
| Value | Behavior | Use Case |
|---|
None | Sent on all requests (requires Secure) | Embedded/third-party use |
Lax | Sent on top-level navigation | Most sites (recommended) |
Strict | Only sent on same-site requests | High-security apps |
Default | Browser default (usually Lax) | Legacy compatibility |
Default: None (with Secure flag)
Partitioned (CHIPS)
Enable Cookies Having Independent Partitioned State:
anubis --cookie-partitioned
Required for third-party cookies in Chrome (2024+).
Cookie Domain
Share cookies across subdomains:
# Static domain
anubis --cookie-domain example.com
# Dynamic (auto-detect from request)
anubis --cookie-dynamic-domain
Static domain:
- Cookie valid for
*.example.com
- Set once, works everywhere
Dynamic domain:
- Cookie valid for request domain only
- Users on
www.example.com and api.example.com get separate cookies
⚠️ Cannot use both: Setting both flags is an error.
Cookie Expiration
# Default: 24 hours
anubis --cookie-expiration-time 24h
# Custom values
anubis --cookie-expiration-time 1h # 1 hour
anubis --cookie-expiration-time 7d # 7 days (not supported, use 168h)
anubis --cookie-expiration-time 168h # 7 days
After expiration, users must re-solve challenges.
Cookie Prefix
Customize cookie names:
# Default: "anubis"
anubis --cookie-prefix myapp
# Results in cookies:
# - myapp-auth
# - myapp-cookie-verification
Use different prefixes when running multiple Anubis instances on the same domain.
JWT Claims
JWT payload contains:
{
"exp": 1735689600, // Expiration timestamp
"iat": 1735603200, // Issued at timestamp
"nbf": 1735603200, // Not before timestamp
"difficulty": 2, // Optional: if --difficulty-in-jwt
"X-Real-IP": "1.2.3.4" // Optional: if --jwt-restriction-header set
}
Difficulty in JWT
Include challenge difficulty in token:
anubis --difficulty-in-jwt
Use cases:
- Track which difficulty was solved
- Audit challenge settings
- Debug multi-difficulty policies
JWT IP Restriction
Bind JWT to specific IP address:
# Default: X-Real-IP
anubis --jwt-restriction-header X-Real-IP
# Custom header
anubis --jwt-restriction-header CF-Connecting-IP
If set, JWT is only valid when request comes from the same IP that solved the challenge.
Limitations:
- Breaks mobile users (IP changes)
- Issues with carrier-grade NAT
- Only use if your threat model requires it
Security Best Practices
Key Generation
✅ Do:
- Use cryptographically secure random number generator
- Store keys in secrets management (Vault, AWS Secrets Manager)
- Rotate keys periodically
- Use Ed25519 (not HMAC-SHA512)
🚫 Don’t:
- Commit keys to version control
- Use the same key across environments
- Share keys between services
- Use predictable/weak keys
Cookie Settings
✅ Do:
- Use
--cookie-secure in production (HTTPS only)
- Set appropriate
--cookie-same-site for your use case
- Use short expiration times (balance security vs UX)
- Enable
--cookie-partitioned for third-party contexts
🚫 Don’t:
- Disable
--cookie-secure in production
- Use
SameSite=None without HTTPS
- Set extremely long expiration times
- Ignore browser warnings about cookie flags
Deployment
✅ Do:
- Always configure signing keys in production
- Use persistent storage backends (bbolt, valkey, s3api)
- Monitor for “generating random key” warnings
- Test key rotation procedure
🚫 Don’t:
- Rely on random key generation
- Use memory storage backend in production
- Forget to mount secrets in containers
- Share signing keys across environments
Validation Errors
Key Validation
# Error: supplied key is not hex-encoded
ED25519_PRIVATE_KEY_HEX=not-hex-data
# Fix: Use hex-encoded key
ED25519_PRIVATE_KEY_HEX=$(openssl rand -hex 32)
# Error: supplied key is not 32 bytes long
ED25519_PRIVATE_KEY_HEX=abc123 # Too short
# Fix: Must be exactly 64 hex characters (32 bytes)
ED25519_PRIVATE_KEY_HEX=$(openssl rand -hex 32)
Conflicting Configuration
# Error: do not specify both HS512 and ED25519 secrets
HS512_SECRET=secret1
ED25519_PRIVATE_KEY_HEX=secret2
# Fix: Use only one
unset HS512_SECRET
# Error: can't set COOKIE_DOMAIN and COOKIE_DYNAMIC_DOMAIN
anubis --cookie-domain example.com --cookie-dynamic-domain
# Fix: Choose one
anubis --cookie-domain example.com
Persistent Storage Warning
When using persistent storage without a configured key:
WARN [misconfiguration] persistent storage backend is configured, but no
private key is set. Challenges will be invalidated when Anubis restarts.
Set HS512_SECRET, ED25519_PRIVATE_KEY_HEX, or ED25519_PRIVATE_KEY_HEX_FILE
Impact:
- All active users must re-solve challenges after restart
- Degraded user experience
- Potential spike in challenge traffic
Fix: Configure a signing key (see Key Generation).
Next Steps