API Key Security
How ReplayCI generates, stores, and validates API keys and authentication credentials.
API keys
Generation
API keys are generated using cryptographically secure randomness:
Format: rci_live_<32-character-base64url>
Entropy: 192 bits
Example: rci_live_A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6
The rci_live_ prefix enables quick identification and SecurityGate scanning.
Storage
API keys are never stored in plaintext. The raw key is:
- Shown to the user exactly once (at signup or key creation)
- Immediately hashed with SHA-256
- Stored as
key_hashin theApiKeydatabase table
Subsequent authentication compares the SHA-256 hash of the presented key against the stored hash. The raw key cannot be recovered from the database.
Validation
Every request to /api/v1/* endpoints goes through API key authentication in middleware:
- Extract Bearer token from
Authorizationheader - Validate format matches the
rci_live_prefix - Compute SHA-256 hash and look up against stored hashes
- Verify the key is not revoked
- Resolve the associated tenant and user for downstream authorization
Limits
| Constraint | Value |
|---|---|
| Max active keys per tenant | 5 |
| Key entropy | 192 bits |
| Revocation | Immediate (next request rejected) |
last_used_at tracking | Updated fire-and-forget on each request |
Password hashing
User passwords are hashed using scrypt with industry-standard parameters:
- Unique random salt per password
- Constant-time comparison to prevent timing attacks
- Passwords are never stored or logged in plaintext
Session tokens
Dashboard authentication uses signed session tokens:
| Property | Value |
|---|---|
| Signing | HMAC-SHA256 |
| Access token lifetime | 15 minutes |
| Refresh token lifetime | 7 days |
| Cookie flags | HttpOnly, SameSite=Strict, Secure (production) |
Token refresh
When an access token expires, middleware automatically checks the refresh token. If valid, a new access token is issued transparently — no user interaction required. Refresh tokens are stored as SHA-256 hashes in the database; the raw token only exists in the browser cookie.
Token revocation
Refresh tokens can be revoked immediately. The token cleanup job removes expired and revoked tokens after their retention period (30 days for refresh tokens).
Rate limiting
Authentication endpoints have specific rate limits to prevent brute-force attacks:
| Endpoint | Limit | Window |
|---|---|---|
POST /api/auth/signup | 5 requests | Per hour (per IP) |
POST /api/auth/forgot-password | 3 requests | Per hour (per email) |
POST /api/v1/runs | 100 runs | Per day (per tenant) |
POST /* (general) | 30 requests | Per minute (per tenant) |
GET /* (general) | 120 requests | Per minute (per tenant) |
Rate limiting uses a token bucket algorithm to prevent brute-force and abuse.
Password reset
Password reset uses a secure token flow:
- User requests reset → system generates 32-byte random token
- SHA-256 hash stored in
PasswordResetTokentable - Raw token sent via email link (HTTPS only)
- Token is single-use —
used_attimestamp marks consumption - Token expires after 1 hour
- Existing unused tokens are invalidated when a new one is created
The forgot-password endpoint always returns 200 OK regardless of whether the email exists (anti-enumeration).
Email verification
New accounts must verify their email before using the API:
- Signup triggers a verification email (fire-and-forget — signup succeeds even if email fails)
- Verification token: 32-byte random, SHA-256 hashed in database, 24-hour TTL
- Unverified accounts receive
403 EMAIL_NOT_VERIFIEDon/api/v1/*endpoints - Dashboard access is not gated — users can view their dashboard while unverified
- Resend endpoint rate-limited to 3/hour
Secret redaction
The structured logger automatically redacts sensitive fields (API keys, tokens, credentials) at all nesting levels. Even in debug or error logs, credentials are never written in plaintext.
What we recommend
- Rotate API keys periodically — revoke old keys from Settings, create new ones
- Use environment variables — never put
REPLAYCI_API_KEYin config files or source control - Use strong passwords — scrypt is resistant to brute force, but a strong password is your first line of defense
- Check your CI secrets — ensure
REPLAYCI_API_KEYandREPLAYCI_PROVIDER_KEYare stored in your CI platform's secret manager, not in workflow files