Production Hardening¶
Security and performance hardening for production deployments.
Architecture vs. operations
This page is the operational checklist — TLS, secrets, RBAC, network boundaries. For the architecture (identity contract, IdentityAdapter SPI, threat model, A2A signing, audit integrity, kill-switch authorisation), see Security architecture.
Security Hardening¶
Authentication & Authorization¶
# application-prod.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://login.microsoftonline.com/${AZURE_TENANT_ID}/v2.0
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: when_authorized
RBAC Configuration¶
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health/**").permitAll()
.requestMatchers("/api/v1/agent/**").hasRole("USER")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.build();
}
}
Secrets Management¶
# Never store secrets in configuration files
regulus:
llm:
gemini:
project-id: ${GOOGLE_CLOUD_PROJECT}
# Use Workload Identity or service account
spring:
cloud:
vault:
uri: ${VAULT_ADDR}
authentication: kubernetes
kubernetes:
role: regulus-agent
TLS Configuration¶
server:
ssl:
enabled: true
protocol: TLS
enabled-protocols: TLSv1.3
ciphers:
- TLS_AES_256_GCM_SHA384
- TLS_AES_128_GCM_SHA256
key-store: ${SSL_KEYSTORE_PATH}
key-store-password: ${SSL_KEYSTORE_PASSWORD}
key-store-type: PKCS12
HTTP Security Headers¶
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
return http
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'"))
.frameOptions(FrameOptionsConfig::deny)
.xssProtection(XssConfig::disable) // Rely on CSP
.contentTypeOptions(Customizer.withDefaults())
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000))
)
.build();
}
}
Input Validation¶
Request Validation¶
public record ChatRequest(
@NotBlank
@Size(max = 10000)
String message,
@NotNull
@Valid
ChatContext context
) {}
public record ChatContext(
@NotBlank
@Pattern(regexp = "^[A-Z0-9-]{1,50}$")
String customerId,
@NotBlank
@Pattern(regexp = "^[A-Z_]{1,30}$")
String purposeCode,
boolean hasConsent,
@Pattern(regexp = "^sess-[a-z0-9]{8,32}$")
String sessionId
) {}
Prompt Injection Prevention¶
regulus:
safety:
prompt-injection:
enabled: true
detection-model: rule-based # or ml-based
block-on-detection: true
log-attempts: true
@Component
public class PromptInjectionFilter {
private final List<Pattern> injectionPatterns = List.of(
Pattern.compile("(?i)ignore\\s+(previous|above|all)\\s+instructions"),
Pattern.compile("(?i)system\\s*:\\s*"),
Pattern.compile("(?i)\\[INST\\]"),
Pattern.compile("(?i)\\{\\{.*\\}\\}")
);
public boolean detectInjection(String input) {
return injectionPatterns.stream()
.anyMatch(pattern -> pattern.matcher(input).find());
}
}
Performance Hardening¶
Connection Pooling¶
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
data:
redis:
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
max-wait: -1ms
HTTP Client Configuration¶
@Bean
public WebClient webClient() {
HttpClient httpClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.responseTimeout(Duration.ofSeconds(30))
.doOnConnected(conn -> conn
.addHandlerLast(new ReadTimeoutHandler(30))
.addHandlerLast(new WriteTimeoutHandler(30)));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(httpClient))
.codecs(configurer -> configurer
.defaultCodecs()
.maxInMemorySize(16 * 1024 * 1024)) // 16MB
.build();
}
Caching¶
spring:
cache:
type: redis
redis:
time-to-live: 3600000 # 1 hour
regulus:
cache:
llm-responses:
enabled: true
ttl: 300s
max-size: 10000
@Service
public class CachedLlmService {
@Cacheable(value = "llm-responses",
key = "#request.hashCode()",
condition = "#request.cacheable")
public Mono<ChatResponse> chat(ChatRequest request) {
return llmClient.chat(request);
}
}
Rate Limiting¶
resilience4j:
ratelimiter:
instances:
llm-calls:
limitForPeriod: 100
limitRefreshPeriod: 1m
timeoutDuration: 0s
admin-calls:
limitForPeriod: 10
limitRefreshPeriod: 1m
Circuit Breaker¶
resilience4j:
circuitbreaker:
instances:
llm-service:
slidingWindowSize: 10
failureRateThreshold: 50
waitDurationInOpenState: 30s
permittedNumberOfCallsInHalfOpenState: 3
slowCallRateThreshold: 80
slowCallDurationThreshold: 10s
Resource Limits¶
JVM Configuration¶
ENTRYPOINT ["java", \
"-XX:+UseG1GC", \
"-XX:MaxGCPauseMillis=200", \
"-XX:+UseStringDeduplication", \
"-Xmx1g", \
"-Xms512m", \
"-XX:MaxMetaspaceSize=256m", \
"-jar", "app.jar"]
Kubernetes Resources¶
Request Size Limits¶
spring:
codec:
max-in-memory-size: 10MB
server:
max-http-header-size: 16KB
tomcat:
max-http-post-size: 10MB
Monitoring & Alerting¶
Health Checks¶
management:
endpoint:
health:
show-details: when_authorized
probes:
enabled: true
group:
liveness:
include: livenessState
readiness:
include:
- readinessState
- db
- redis
- llm
Critical Alerts¶
groups:
- name: regulus-critical
rules:
- alert: HighErrorRate
expr: |
rate(regulus_llm_errors_total[5m]) /
rate(regulus_llm_requests_total[5m]) > 0.1
for: 5m
labels:
severity: critical
- alert: KillSwitchActivated
expr: regulus_killswitch_status == 1
for: 0m
labels:
severity: critical
- alert: HighLatency
expr: |
histogram_quantile(0.99, rate(regulus_llm_latency_bucket[5m])) > 30
for: 5m
labels:
severity: warning
- alert: ServiceDown
expr: up{job="regulus-agent"} == 0
for: 1m
labels:
severity: critical
Disaster Recovery¶
Backup Configuration¶
regulus:
backup:
audit-logs:
enabled: true
destination: s3://regulus-backups/audit/
schedule: "0 0 * * *" # Daily
retention-days: 2555 # 7 years
configuration:
enabled: true
destination: s3://regulus-backups/config/
schedule: "0 * * * *" # Hourly
Failover Configuration¶
regulus:
llm:
primary:
provider: gemini
location: europe-west2
fallback:
provider: azure-openai
region: uksouth
failover:
enabled: true
threshold: 3
window: 60s
Security Checklist¶
- OAuth2/OIDC authentication enabled
- RBAC configured for all endpoints
- TLS 1.3 only, strong ciphers
- Secrets in Vault/KMS, not config
- Input validation on all endpoints
- Prompt injection detection enabled
- Rate limiting configured
- Circuit breakers enabled
- Network policies applied
- Pod security context configured
- Audit logging enabled
- Alerting configured
- Backup procedures tested
- Disaster recovery plan documented
Performance Checklist¶
- Connection pools sized appropriately
- HTTP client timeouts configured
- Response caching enabled where safe
- JVM garbage collection tuned
- Resource limits set
- HPA configured
- Database indexes optimized
- Query performance monitored
- Memory usage monitored
- Latency percentiles tracked