This document describes the security measures currently implemented in CIRISBridge infrastructure.
Only the following ports are exposed to the internet:
| Port | Service | Purpose |
|---|---|---|
| 22/tcp | SSH | Administrative access (key-based only) |
| 53/udp,tcp | Constellation DNS | Public DNS service |
| 80/tcp | Caddy | ACME HTTP-01 challenges only |
| 443/tcp | Caddy | HTTPS traffic (all services) |
All application services bind to localhost and are proxied through Caddy:
| Service | Internal Binding | External Path |
|---|---|---|
| CIRISBilling | 127.0.0.1:8000 | /api/, /admin-ui/ |
| CIRISProxy | 127.0.0.1:4000 | /v1/* |
| CIRISLens API | 127.0.0.1:8200 | /lens-api/* |
| Grafana | 127.0.0.1:3001 | / (lens subdomain) |
Services communicate over an internal Docker bridge network (postgres_ciris). Container-to-container traffic uses internal hostnames:
ciris-postgres- Databaseciris-billing- Billing APIciris-proxy- LLM Proxy
- Caddy automatically obtains and renews Let's Encrypt certificates
- All public endpoints enforce HTTPS
- HTTP requests redirect to HTTPS (except ACME challenges)
| Domain | Service |
|---|---|
| billing1.ciris-services-1.ai | Billing US |
| billing1.ciris-services-2.ai | Billing EU |
| proxy1.ciris-services-1.ai | Proxy US |
| proxy1.ciris-services-2.ai | Proxy EU |
| lens.ciris-services-1.ai | Grafana/CIRISLens |
| agents.ciris-services-1.ai | Billing alias |
- Google OAuth 2.0 ID tokens
- Token validation against multiple client IDs (Android app, web)
- Tokens validated server-side using Google's public keys
- API keys stored as Argon2 hashes in PostgreSQL
- Keys validated on each request by CIRISBilling
- CIRISProxy delegates auth to CIRISBilling via callback
- Google OAuth 2.0 for admin panel access
- Restricted to specific Google account(s)
- Session-based after initial OAuth flow
- CIRISLens ingestion requires service tokens
- Tokens stored in
cirislens.service_tokenstable - Each service (billing, proxy) has its own token
- All LLM requests require sufficient credit balance
- Credits checked and deducted per request
- No credit = request rejected (402 Payment Required)
- Implemented at the LLM provider level
- Rate limit errors logged but excluded from alerts
- No application-level rate limiting currently
CIRISProxy routes to ZDR-compliant LLM providers:
- Groq - No request/response logging
- Together AI - No request/response logging
- OpenRouter - No request/response logging
LLM request content is never stored by CIRISBridge.
- PostgreSQL requires password authentication
- Passwords contain special characters (32+ chars)
- Bi-directional replication uses dedicated replication user
- Database not exposed externally (localhost only)
| Data Type | Storage | Protection |
|---|---|---|
| User credentials | PostgreSQL | Argon2 hash |
| API keys | PostgreSQL | Argon2 hash |
| Service tokens | PostgreSQL | Plain (internal only) |
| Google OAuth secrets | Ansible inventory | Not in git |
| LLM provider API keys | .env files | Not in git |
- LLM prompts or responses
- User IP addresses (beyond access logs)
- Payment card details (handled by Google Play)
All secrets are stored in ansible/inventory/production.yml:
- Database passwords
- API keys (Groq, Together, OpenRouter, OpenAI)
- Google OAuth credentials
- LiteLLM master key
- Service tokens
This file is:
- Listed in
.gitignore - Never committed to version control
- Required for deployment
Each service has a .env file generated by Ansible:
- Created from templates during deployment
- Contains service-specific secrets
- Permissions: 600 (owner read/write only)
- Service health events
- Authentication successes/failures
- Credit transactions
- Error events (excluding sensitive data)
- Logs stored in PostgreSQL (
cirislens.service_logs) - No automatic purging currently configured
- Accessible via Grafana dashboards
Alerts trigger on:
- Service errors (ERROR/CRITICAL level)
- High error rate (>5 in 5 minutes)
- Billing service errors
Excluded from alerts:
- Play Integrity verification failures
- Rate limit errors
ansible/runbooks/intrusion-response.yml provides:
- IP blocking via iptables
- Forensic evidence collection
- Credential rotation guidance
- Incident documentation
When triggered, collects:
- Active network connections
- Running processes
- Docker container states
- Authentication logs
- iptables rules
Data archived to ./forensics/ on control node.
- Key-based authentication only
- Password authentication disabled
- Root login permitted (keys only)
- No SSH exposed through Cloudflare
- Requires SSH key access to nodes
- Requires
production.ymlinventory file - Commands logged to shell history
- Application-level rate limiting
- Web Application Firewall (WAF)
- Intrusion Detection System (IDS)
- Automated log rotation/purging
- Multi-factor authentication for admin
- IP allowlisting for admin access
- Root SSH access (mitigated by key-only auth)
- Single admin Google account for OAuth
- No geographic access restrictions
- Service tokens stored unhashed (internal use only)
- Repository: https://github.com/CIRISAI/CIRISBridge
- Monitoring: https://lens.ciris-services-1.ai
This document should be updated when:
- New security measures are implemented
- Security configurations change
- New services are added
- Vulnerabilities are discovered and fixed
Last updated: 2025-12-14