security: configurable rate limit fail policy via RATE_LIMIT_FAIL_POLICY

Add "deny" as alias for "closed" policy. Change fail-closed retry-after
from 1s to 60s to give Redis time to recover. Add from_env default test.
Document env var in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
whit3rabbit
2026-03-27 15:30:12 -05:00
co-authored by Claude Sonnet 4.6
parent 3b43c8cae2
commit 591fdee72b
2 changed files with 20 additions and 3 deletions
+1
View File
@@ -91,6 +91,7 @@ OPENAI_API_KEY=sk-... cargo run -p anyllm_proxy
- `IP_ALLOWLIST`: Comma-separated CIDR ranges for IP allowlisting (e.g., `192.168.1.0/24,10.0.0.0/8`). Bare IPs also accepted. When set, only matching IPs can access the proxy.
- `TRUST_PROXY_HEADERS`: Set to `true` or `1` to use `X-Forwarded-For` header for client IP when behind a reverse proxy. Only effective when `IP_ALLOWLIST` is set.
- `WEBHOOK_URLS`: Comma-separated webhook URLs for request completion notifications. Fire-and-forget HTTP POST with `RequestLogEntry` JSON payload.
- `RATE_LIMIT_FAIL_POLICY`: Behavior when Redis rate limiter is unavailable: `open` (default, allow requests) or `closed`/`deny` (reject with 503 and retry-after 60s).
### LiteLLM env var aliases
+19 -3
View File
@@ -17,7 +17,7 @@ pub enum RateLimitFailPolicy {
impl RateLimitFailPolicy {
pub fn from_env_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"closed" => Self::Closed,
"closed" | "deny" => Self::Closed,
_ => Self::Open,
}
}
@@ -97,7 +97,7 @@ impl RedisRateLimiter {
}
RateLimitFailPolicy::Closed => {
tracing::error!(error = %e, "Redis RPM check failed, rejecting request (fail-closed)");
Err(1)
Err(60)
}
},
}
@@ -164,7 +164,7 @@ impl RedisRateLimiter {
}
RateLimitFailPolicy::Closed => {
tracing::error!(error = %e, "Redis TPM check failed, rejecting request (fail-closed)");
Err(1)
Err(60)
}
},
}
@@ -265,9 +265,25 @@ mod tests {
RateLimitFailPolicy::from_env_str("CLOSED"),
RateLimitFailPolicy::Closed
));
assert!(matches!(
RateLimitFailPolicy::from_env_str("deny"),
RateLimitFailPolicy::Closed
));
assert!(matches!(
RateLimitFailPolicy::from_env_str("DENY"),
RateLimitFailPolicy::Closed
));
assert!(matches!(
RateLimitFailPolicy::from_env_str("unknown"),
RateLimitFailPolicy::Open
));
}
#[test]
fn fail_policy_defaults_to_open() {
// When RATE_LIMIT_FAIL_POLICY is unset, from_env should return Open.
std::env::remove_var("RATE_LIMIT_FAIL_POLICY");
let policy = RateLimitFailPolicy::from_env();
assert_eq!(policy, RateLimitFailPolicy::Open);
}
}