# Leave this as is if connecting from a remote machine SERVER_IP=0.0.0.0 # SECURITY: JWT secret for token signing. # REQUIRED — the application will refuse to start if this is unset or set to the # known-compromised default disclosed in GHSA-4gxj-hw3c-3x2x. # Generate a unique secret with: openssl rand -base64 32 JWT_SECRET=REPLACE_ME # SECURITY: Dedicated secret for signing OAuth2 state tokens (SSO flows). # If not set, falls back to JWT_SECRET — set this to isolate SSO state signing. # Generate with: openssl rand -base64 32 SSO_STATE_SECRET=REPLACE_ME # SECURITY: Fernet encryption key for TOTP secrets stored in the database. # If not set, falls back to a key derived from JWT_SECRET. # ⚠️ Once set and users have enrolled 2FA, do NOT change this key — existing # TOTP secrets in the database will become unreadable. # Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" TOTP_ENCRYPTION_KEY=REPLACE_ME # WebAuthn / Passkey sign-in (optional — leave WEBAUTHN_ORIGINS empty to disable). # RP ID must match the host users reach in the browser (no scheme/port). # ORIGINS is a comma-separated list of allowed frontend origins (scheme + host + port). # Example local dev: use localhost (not 127.0.0.1 — browsers reject passkeys on the IP). # WEBAUTHN_ORIGINS=http://localhost:5173 WEBAUTHN_RP_ID=localhost WEBAUTHN_RP_NAME=SOCFortress CoPilot WEBAUTHN_ORIGINS=https://localhost:5173 # Shared secrets required on the webhook routes that are gated by a header instead of a # JWT: GRAYLOG_API_HEADER_VALUE protects /api/graylog/invoke (active-response) and the # /api/incidents/alerts/create/threshold|velo-sigma alert-injection routes; # VELOCIRAPTOR_API_HEADER_VALUE and GRAFANA_API_HEADER_VALUE protect their respective # routes. All three FAIL CLOSED: if left blank the route is denied for everyone, so there # is no guessable default to exploit (GHSA-x8gc-f8p4-frc2, GHSA-xh98-w6qh-cr44 — same class # as the JWT_SECRET default in GHSA-4gxj-hw3c-3x2x). Generate a unique random value per # deployment (e.g. `openssl rand -hex 32`) and send it as the matching request header # (`Graylog`, `Velociraptor`, `grafana`). Do NOT commit a real value or reuse one secret. GRAYLOG_API_HEADER_VALUE= VELOCIRAPTOR_API_HEADER_VALUE= GRAFANA_API_HEADER_VALUE= MYSQL_URL=copilot-mysql # ! Avoid using special characters in the password ! # MYSQL_ROOT_PASSWORD=REPLACE_WITH_PASSWORD MYSQL_USER=copilot # ! Avoid using special characters in the password ! # MYSQL_PASSWORD=REPLACE_WITH_PASSWORD MINIO_URL=copilot-minio MINIO_ROOT_USER=admin # ! Make the password at least 8 characters long ! # MINIO_ROOT_PASSWORD=REPLACE_ME MINIO_SECURE=False # ! ALERT FORWARDING IP # Set this to the IP of the host running CoPilot. This is used by Graylog to forward alerts to CoPilot # ! Not needed anymore since we are reading from the index now # ! Ensure Graylog is able to reach this IP and port 5000 ALERT_FORWARDING_IP=0.0.0.0 # Connector Credentials # ! SETTING UP YOUR CONNECTORS DEMOs https://www.youtube.com/@taylorwalton_socfortress/videos! # WAZUH_INDEXER_URL=https://127.1.1.1:9200 WAZUH_INDEXER_USERNAME=admin WAZUH_INDEXER_PASSWORD=admin WAZUH_MANAGER_URL=https://127.1.1.1 WAZUH_MANAGER_USERNAME=dummy WAZUH_MANAGER_PASSWORD=dummy GRAYLOG_URL=http://127.1.1.1 GRAYLOG_USERNAME=dummy GRAYLOG_PASSWORD=dummy GRAYLOG_NETWORK_URL=http://127.1.1.1 GRAYLOG_NETWORK_USERNAME=dummy GRAYLOG_NETWORK_PASSWORD=dummy SHUFFLE_URL=https://127.1.1.1 SHUFFLER_API_KEY=dummy SHUFFLE_WORKFLOW_ID=dummy VELOCIRAPTOR_URL=https://127.1.1.1 VELOCIRAPTOR_API_KEY_PATH=dummy # gRPC deadlines for the backend's Velociraptor calls, in seconds. # VELOCIRAPTOR_QUERY_TIMEOUT covers ordinary VQL (listing clients, reading results) -- # those hit the Velociraptor server's own datastore and should be quick. # VELOCIRAPTOR_FLOW_TIMEOUT covers waiting for a collection to finish, which takes as # long as the endpoint needs to run the artifact. Raise it if collections on this # deployment legitimately run longer than five minutes; a 504 from CoPilot does not # stop the flow, which keeps running on the Velociraptor server. # NOTE: not the same knob as VELOCIRAPTOR_TIMEOUT further down -- that one is the MCP # server's HTTP timeout and has no effect on these calls. # VELOCIRAPTOR_QUERY_TIMEOUT=30 # VELOCIRAPTOR_FLOW_TIMEOUT=300 SUBLIME_URL=http://127.1.1.1 SUBLIME_API_KEY=dummy INFLUXDB_URL=http://127.1.1.1 INFLUXDB_API_KEY=dummy INFLUXDB_ORG_AND_BUCKET=dummy,dummy # Verify TLS certificates on the InfluxDB v2 REST API calls used by check provisioning. # Off by default because a stack's InfluxDB is normally a co-located appliance with a # self-signed certificate. Set to True where InfluxDB is fronted by a trusted CA. INFLUXDB_VERIFY_SSL=False GRAFANA_URL=http://127.1.1.1 GRAFANA_USERNAME=dummy GRAFANA_PASSWORD=dummy WAZUH_WORKER_PROVISIONING_URL=http://127.1.1.1 EVENT_SHIPPER_URL=graylog_host GELF_INPUT_PORT=gelf_port ALERT_CREATION_PROVISIONING_URL=http://127.1.1.1 HAPROXY_PROVISIONING_URL=http://127.1.1.1 # VirusTotal VIRUSTOTAL_URL=https://www.virustotal.com/api/v3 VIRUSTOTAL_API_KEY=REPLACE_ME # Portainer PORTAINER_URL=http://127.1.1.1:9000 PORTAINER_USERNAME=admin PORTAINER_PASSWORD=admin PORTAINER_ENDPOINT_ID=2 # SOCFORTRESS TALON : https://github.com/taylorwalton/talon/tree/main TALON_URL=http://127.1.1.1:3100 TALON_API_KEY=REPLACE_ME # Severity applied to alerts whose source supplies none. Wazuh alerts map from # their rule level; Office 365, CrowdStrike, Carbon Black, Huntress and others # carry no equivalent, and would otherwise be invisible to any notification # route gating above the fallback. # One of: Informational | Low | Medium | High | Critical. Default: High. DEFAULT_ALERT_SEVERITY=High # CoPilot's own public URL, used to build deep links in notifications # ("Open in CoPilot"). Optional — unset simply means notifications carry no # link. No trailing slash. COPILOT_URL=https://copilot.example.com # RESEND : transactional email for notification routes (https://resend.com) # Deployment-wide, like SHUFFLER_API_KEY — the per-customer differentiator is the # to/from addresses on each notification route. Free tier is 1,000 emails/month # across ALL customers, so gate email routes on severity and watch the quota # counter in the UI. RESEND_URL=https://api.resend.com RESEND_API_KEY=REPLACE_ME # Must be on a domain verified in Resend, or sends are rejected. RESEND_FROM_ADDRESS=alerts@example.com # ! CoPilot MCP # OpenAI Configuration OPENAI_API_KEY=REPLACE_ME OPENAI_MODEL=gpt-4o # Application Configuration LOG_LEVEL=INFO # JWT token expiry in minutes (default: 1440 = 24h) ACCESS_TOKEN_EXPIRE_MINUTES=1440 # Audit log retention, in days (issue #943). A daily scheduled job deletes audit_log rows # older than this so the append-only audit trail can't grow without bound. Default 90. # Raise it if your compliance framework mandates a longer minimum retention; set 0 to disable # pruning entirely (keep all audit entries forever). AUDIT_LOG_RETENTION_DAYS=90 # MCP Server Process Management MCP_SERVER_ENABLED=true # External OpenSearch Configuration OPENSEARCH_URL=https://your-wazuh-indexer-url:9200 # Likely your wazuh-indexer OPENSEARCH_USERNAME=YOUR_WAZUH_INDEXER_USERNAME OPENSEARCH_PASSWORD=YOUR_WAZUH_INDEXER_PASSWORD OPENSEARCH_SSL_VERIFY=false OPENSEARCH_SSL_SHOW_WARN=false # OpenSearch MCP Server Configuration MCP_OPENSEARCH_AUTH_TOKEN=secret-token # External MySQL Configuration MYSQL_ENABLED=true MYSQL_HOST=copilot-mysql MYSQL_PORT=3306 MYSQL_DATABASE=copilot # MySQL MCP Server Configuration MCP_MYSQL_AUTH_TOKEN=mysql-token MCP_MYSQL_SERVER_ENABLED=true # External Wazuh Configuration WAZUH_PROD_URL=https://your-wazuh-manager:55000 WAZUH_PROD_USERNAME=wazuh-wui WAZUH_PROD_PASSWORD=wazuh-wui WAZUH_PROD_SSL_VERIFY=false WAZUH_PROD_TIMEOUT=30 # Wazuh MCP Server Configuration MCP_WAZUH_AUTH_TOKEN=wazuh-token MCP_WAZUH_SERVER_ENABLED=true # External Velociraptor Configuration VELOCIRAPTOR_API_KEY=/app/velociraptor-config.yaml # Dont change this VELOCIRAPTOR_SSL_VERIFY=false # MCP server only. The backend's own deadlines are VELOCIRAPTOR_QUERY_TIMEOUT / # VELOCIRAPTOR_FLOW_TIMEOUT above -- raising this one will not make a slow artifact # collection succeed. VELOCIRAPTOR_TIMEOUT=30 # Quarantine artifact overrides. Leave unset to use Velociraptor's built-in # artifacts. On Velociraptor 0.76.x+ the built-in Linux.Remediation.Quarantine # runs a "forbidden connection test" that can roll back a working quarantine; # point this at a custom artifact (which Velociraptor won't let you override in # place) to avoid it. See issue #913. # VELOCIRAPTOR_LINUX_QUARANTINE_ARTIFACT=Custom.Linux.Remediation.Quarantine # VELOCIRAPTOR_WINDOWS_QUARANTINE_ARTIFACT=Custom.Windows.Remediation.Quarantine # Velociraptor MCP Server Configuration MCP_VELOCIRAPTOR_AUTH_TOKEN=velociraptor-token MCP_VELOCIRAPTOR_SERVER_ENABLED=true MCP_VELOCIRAPTOR_HOST=0.0.0.0 MCP_VELOCIRAPTOR_PORT=8001 # SOCFortress MDR Forwarding # When enabled, alerts for customers that have the "SOCFortress MDR" integration # deployed are forwarded to the MDR server (POST /api/v1/alerts/copilot). The # per-customer collector UUID is entered in the integration UI; MDR_COLLECTOR_UUID # below is only a single-tenant fallback. MDR_ENABLED=false MDR_SERVER_URL=https://mdr-server.socfortress.co MDR_COLLECTOR_UUID= # --- File Analysis module (#974) --------------------------------------------- # Tier 1 static inspection is always on; detonation is opt-in and OFF by default. # State is stored in MinIO (already provisioned) — NO new database tables. # Tier 1 — static inspection (always on) INSPECTOR_URL=http://inspector-runner:9000 # the runner sidecar; CoPilot never talks to Docker INSPECTOR_MODE=container # container (prod) | service (dev only) FILE_ANALYSIS_DEV_MODE=false # true = in-process analyzers, NO isolation (dev only) FILE_ANALYSIS_MINIO_BUCKET=file-analysis FILE_ANALYSIS_MAX_FLOW_FILES=25 # cap on files pulled from one collection FILE_ANALYSIS_MINIO_RETRIES=4 # retry MinIO ops on a transient blip (e.g. VPN timeout) FILE_ANALYSIS_MINIO_BACKOFF=0.5 # base seconds for exponential backoff between retries VELOCIRAPTOR_API_CONFIG=/data/api_client.yaml # NOTE: the Velociraptor connector's # api_key column is a PATH to this file # Tier 2 — detonation (opt-in; leave SANDBOX_BACKEND=none to disable entirely). # In-product setup guide: File Analysis -> Sandbox Setup tab. SANDBOX_BACKEND=none # none | local_vm | remote CAPE_API_URL=http://cape-host:8000/apiv2 # only used when SANDBOX_BACKEND != none CAPE_API_TOKEN= CAPE_TASK_TIMEOUT=120 # per-analysis guest runtime (seconds) CAPE_POLL_INTERVAL=15 CAPE_POLL_TIMEOUT=1800 # Reputation enrichment (reuses the existing VirusTotal connector) # WARNING: submitting a file UPLOADS it to VirusTotal — it becomes publicly available. ENRICHMENT_SUBMIT_FILES=false ENRICHMENT_VT_MALICIOUS_THRESHOLD=5 ENRICHMENT_VT_MAX_WAIT=300 # Graylog Threshold Alert -> OpenSearch index mapping # A Graylog threshold/aggregation event does not carry the source index pattern, so # CoPilot must be told which index to search per SOURCE value to resolve the underlying # events (alert asset/title + timeline). Built-in defaults cover wazuh and office365. # # THRESHOLD_SOURCE_INDEX_MAPPING: JSON object mapping a custom SOURCE to an index # pattern string, or a [index_pattern, time_field] pair (time field defaults to # "timestamp"). Merges over and overrides the built-in defaults. Example: # THRESHOLD_SOURCE_INDEX_MAPPING={"bitwarden": "bitwarden-*", "dellswitch": ["dellswitch-*", "timestamp"]} # # THRESHOLD_SOURCE_INDEX_FALLBACK_ENABLED: when true (default), an unmapped SOURCE # falls back to "-*" with a "timestamp" time field, so most custom sources # work with no config. Set false to raise on unknown sources instead of guessing. THRESHOLD_SOURCE_INDEX_MAPPING= THRESHOLD_SOURCE_INDEX_FALLBACK_ENABLED=true # SMTP (optional) — used by the customer Security panel to email a temporary # password to a user. When SMTP_HOST is unset, the email action is disabled in # the UI and the backend refuses to send (all other Security actions still work). # # The email's subject and body are customisable per customer: author a message # template with the "Temporary password email" trigger under Notifications → # Message Templates. With none authored, a built-in branded HTML template is # used. COPILOT_URL above (if set) becomes the {{ login_url }} that template # offers as a Sign in button. SMTP_HOST= SMTP_PORT=587 SMTP_USERNAME= SMTP_PASSWORD= SMTP_FROM= SMTP_USE_TLS=true # Performance instrumentation (#1072) — measures how long the backend event loop # is unable to schedule work. The backend serves every request from ONE event # loop, so a synchronous/blocking call inside an `async def` handler stalls all # concurrent requests; that stall is what this harness makes visible. Read the # numbers at GET /api/performance/summary (admin scope), attribute them to # endpoints at /api/performance/stalls, and POST /api/performance/reset between # runs to take a clean before/after. # # Overhead is a few microseconds per request, so it is safe to leave enabled. PERF_MONITOR_ENABLED=true # Watchdog sleep interval, seconds. Lower = finer resolution, more wakeups. PERF_LAG_SAMPLE_INTERVAL=0.25 # Scheduling drift above this counts as a stall and is logged at WARNING. PERF_LAG_STALL_THRESHOLD_MS=100 # Requests at least this slow are counted separately. PERF_SLOW_REQUEST_MS=1000 # Ring buffer sizes — these bound the memory the harness can use. PERF_RECENT_REQUESTS=500 PERF_RECENT_STALLS=200 PERF_LAG_SAMPLES=2000 PERF_DURATION_SAMPLES=200 PERF_MAX_ENDPOINTS=500 # Performance session logs (#1072) — one JSON Lines file per server start, so # separate runs can be compared later (e.g. before vs after moving blocking calls # off the event loop). Records are flushed as they happen, so a killed process # still leaves everything observed up to that moment on disk. PERF_LOG_ENABLED=true # Where the files go. Defaults to backend/logs/performance when running locally; # the Docker image sets /opt/logs/performance, which compose already mounts to # ./data/copilot-backend-data/logs on the host. PERF_LOG_DIR= # Seconds between aggregate roll-up records inside the session file. PERF_LOG_SNAPSHOT_INTERVAL=60 # Session files to keep — the oldest are pruned at startup. `uvicorn --reload` # restarts the app on every file save, so dev runs create many short sessions. PERF_LOG_RETENTION=50 # Optional tag for a run; ends up in the filename and in the session_start record. # Example: PERF_SESSION_LABEL=before-fix uvicorn copilot:app --port=5000 PERF_SESSION_LABEL= # Database connection pool (#1072). Defaults were SQLAlchemy's (5 + 10 overflow), # which a single page load could exhaust — a request then waits for a free # connection, which is indistinguishable from a slow query without measuring. # MySQL's own `max_connections` must comfortably exceed POOL_SIZE + MAX_OVERFLOW # across every process that talks to it. DB_POOL_SIZE=20 DB_MAX_OVERFLOW=20 DB_POOL_TIMEOUT=30 DB_POOL_RECYCLE=1800 # Cancel abandoned requests (#1072, level 1). Starlette runs a handler to # completion even after the client has gone, so a 30s query keeps going for a user # who navigated away three seconds in. Only GETs are cancelled — aborting a # mutation would hide whether it happened without undoing it. CANCEL_ON_DISCONNECT=true # Comma-separated path prefixes never cancelled: streaming endpoints, where # holding the connection open is the feature. CANCEL_ON_DISCONNECT_EXCLUDE=/api/agents/sca/overview/stream # Database query metrics (#1072, level 2). Times every statement and samples the # connection pool, so a slow endpoint can be attributed to a slow query, to # waiting for a connection, or to a fixed per-round-trip cost — three problems # that look identical from the outside. Read at GET /api/performance/database. DB_SLOW_QUERY_MS=250 DB_QUERY_SAMPLES=2000 DB_MAX_STATEMENTS=300 # Response compression (#1072, level 3). There was none: the catalog ships 3-5k # Wazuh rules per request and the incident lists ship full alert rows. Payloads # below the minimum are left alone — the CPU and the extra header outweigh the # few bytes saved. GZIP_MINIMUM_SIZE=500 # Connector credential cache (#1072, level 4). Every outbound call to Wazuh, # Graylog, Velociraptor, Cortex and friends first read the `connectors` table to # find its URL and key — 3.4 queries per request, and two statements each because # `history_logs` loads eagerly and nobody reads it. Connector rows are # deployment-wide and change only when an operator saves them, so every write path # invalidates this cache immediately after its commit; the TTL only covers edits # made directly in the database. Set to 0 to disable it and restore the previous # behaviour exactly. CONNECTOR_CACHE_TTL_SECONDS=600