Security Guardrails -- cortex
July 30, 2026 ยท View on GitHub
Safety and security patterns enforced across cortex.
Credential management
Storage
- All credentials in
.envwithchmod 600permissions - Never commit
.envor any file containing secrets - Use
.env.exampleas a tracked template with placeholder values only - Generate tokens with
openssl rand -hex 32
Ignore files
.gitignore and .dockerignore must include:
.env
*.secret
credentials.*
*.pem
*.key
Hook enforcement
Git hooks are managed by lefthook (lefthook.yml at the repo root). The security-relevant pre-commit command is:
| Hook | Purpose |
|---|---|
scripts/block-env-commits.sh (env_guard) | Blocks commits containing env-credential patterns (e.g. real token values) |
The other pre-commit commands (diff_check, yaml, lint, format, skills) enforce hygiene, and pre-push runs the full test suite. .env.example is maintained by hand โ treat it as the documentation template, not a generated guarantee that every variable the server reads is listed.
Credential rotation
- Generate new token:
openssl rand -hex 32 - Update
.envwith the newCORTEX_TOKENvalue - Restart the server:
just restart - Update MCP client configuration with new token
- Verify:
just health
Authentication
cortex has a single authentication boundary: MCP clients authenticating to the MCP HTTP server.
Bearer token
When CORTEX_TOKEN is set, all requests to /mcp require:
Authorization: Bearer {CORTEX_TOKEN}
Token comparison uses subtle::ConstantTimeEq to prevent timing attacks.
Unauthenticated by default
When CORTEX_TOKEN is not set, the MCP endpoint is open. This is acceptable for LAN-only deployments but not recommended when exposed via reverse proxy.
Health endpoint
/health is always unauthenticated -- required for Docker HEALTHCHECK, docker-compose probes, and SWAG liveness checks.
Docker security
Non-root execution
The container runs as non-root (UID/GID 1000):
RUN groupadd --gid 1000 syslog && \
useradd --uid 1000 --gid syslog --no-create-home --shell /sbin/nologin syslog
USER 1000:1000
Override with CORTEX_UID and CORTEX_GID in docker-compose.yml.
No baked environment
The Docker image contains only two non-sensitive defaults:
ENV RUST_LOG=info
ENV CORTEX_DB_PATH=/data/cortex.db
No credentials are baked into the image. Verify with:
docker inspect cortex:latest | jq '.[0].Config.Env'
Resource limits
The compose file sets memory and CPU limits, both configurable via env vars:
deploy:
resources:
limits:
memory: ${CORTEX_MEMORY_LIMIT:-2G}
cpus: '${CORTEX_CPU_LIMIT:-1.0}'
The memory default is 2G. Heavy stats/sessions aggregations over a large
database can spike memory; the previous 512M default triggered OOM restarts.
Raise CORTEX_MEMORY_LIMIT (e.g. 4G) on hosts with very large databases.
Network security
CORS restriction
CORS is restricted to localhost:3100 and 127.0.0.1:3100. MCP CLI clients (mcporter, curl) are not browser-based and ignore CORS entirely. This prevents malicious webpages from exfiltrating the log database via cross-origin browser fetch.
Port 1514 vs 514
cortex listens on port 1514 (not 514) to avoid needing root or CAP_NET_BIND_SERVICE. Use iptables PREROUTING to redirect 514 to 1514 for devices that cannot be reconfigured:
sudo iptables -t nat -A PREROUTING -p udp --dport 514 -j REDIRECT --to-port 1514
sudo iptables -t nat -A PREROUTING -p tcp --dport 514 -j REDIRECT --to-port 1514
SWAG reverse proxy
When exposing MCP over HTTPS via SWAG:
- Add auth at the proxy layer or set
CORTEX_TOKEN - Add public reverse-proxy hostnames to
CORTEX_ALLOWED_HOSTSso RMCP Host validation accepts them - Use
/config/nginx/proxy-confs/cortex.subdomain.confon the SWAG host, or an equivalent nginx vhost
Input handling
Syslog message trust boundary
Syslog content (hostname, message, app_name) is untrusted user-controlled data. Any LAN host can UDP-spoof an arbitrary hostname. The source_ip field records the actual network sender address and is the only trustworthy network identity for a log entry.
FTS5 query injection
search_logs passes user queries to SQLite FTS5. FTS5 query syntax is its own DSL (not SQL), so SQL injection is not possible via the query parameter. Invalid FTS5 syntax returns a database error, not a security vulnerability.
SQL parameterization
All SQL queries use parameterized bindings (params![] and named_params![]). No user input is interpolated into SQL strings.
Logging
- Never log credentials, tokens, or API keys
- Auth failure logs include method and path but never the submitted token value
RUST_LOG=debugandRUST_LOG=traceare safe for development -- no secrets are emitted at any log level