Repository navigation
Expand file tree
/
Copy path.env.example
More file actions
210 lines (188 loc) · 11.2 KB
/
Copy path.env.example
File metadata and controls
210 lines (188 loc) · 11.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# MIRAGE Environment Configuration
# Copy this file to .env and fill in your values.
# cp .env.example .env
# ── Database ────────────────────────────────────────────────────────────────
DB_NAME=mirage
DB_USER=mirage
DB_PASSWORD=changeme
# The official postgres image's entrypoint requires POSTGRES_PASSWORD
# specifically -- docker-compose.yml does not map DB_PASSWORD to it for you,
# so the postgres container refuses to start without this set too. Keep it
# identical to DB_PASSWORD above.
POSTGRES_PASSWORD=changeme
# DB_HOST and DB_PORT are set automatically by docker-compose.
# Only override these if running services outside of Docker.
# DB_HOST=localhost
# DB_PORT=5432
# ── Dataset export (dataset-export service, deploy/dataset-export/) ─────────
# These go in .env.export, NOT .env. Every service loads .env, including the
# internet-facing SSH and telnet sensors; only dataset-export loads
# .env.export, so a compromised sensor never holds a key that can overwrite
# the public dataset. mirage-crawl's .env.export uses the same values.
# Cloudflare R2 bucket the weekly Parquet files and manifest.json go to. The
# token needs Object Read & Write on this bucket only.
# R2_ACCOUNT_ID=
# R2_ACCESS_KEY_ID=
# R2_SECRET_ACCESS_KEY=
# R2_BUCKET=mirage-dataset
# HMAC key for client_ip in the commands files. Use the same value as the
# IP_SALT GitHub secret, or hashes stop matching earlier published versions.
# IP_SALT=
# ── API ─────────────────────────────────────────────────────────────────────
# Required by mirage-api -- every request must carry this in the X-API-Key
# header (or Authorization: Bearer). Generate one, don't leave the default:
# openssl rand -hex 32
API_KEY=changeme
# ── Bridge / ML Worker ──────────────────────────────────────────────────────
# How often the worker polls PostgreSQL for unenriched sessions (seconds).
BRIDGE_POLL_INTERVAL_S=10
# Number of sessions processed per polling batch.
BRIDGE_BATCH_SIZE=32
# ── Neural Enrichment (optional) ────────────────────────────────────────────
# Uncomment and set these to enable 128-d behavioral embeddings.
# Place trained artifacts in ./artifacts/embedder/ first.
# Without these the worker runs in degraded mode: timing + tool signatures only.
#
# MIRAGE_MODEL_CHECKPOINT=/app/artifacts/embedder/best.pt
# MIRAGE_TOKENIZER_DIR=/app/artifacts/embedder/tokenizer
# MIRAGE_MODEL_VERSION=mirage-emb-v1
# ── Adaptive Deception Policy (optional) ────────────────────────────────────
# Off by default -- the live shell behaves exactly as it does today unless
# you explicitly enable this. Recommended rollout: enable with
# MIRAGE_DECEPTION_APPLY_ACTIONS left at its default (false) first -- that's
# "shadow mode", where every command gets a real decision from the trained
# policy and it's logged to commands.deception_action, but nothing about the
# attacker's shell experience changes. Only flip APPLY_ACTIONS on once you've
# reviewed some shadow-mode decisions.
#
# Read by mirage-core (the Go SSH server):
# MIRAGE_DECEPTION_ENABLED=false
# MIRAGE_DECEPTION_URL=http://mirage-deception:8787
# MIRAGE_DECEPTION_TIMEOUT_MS=200
# MIRAGE_DECEPTION_APPLY_ACTIONS=false
#
# Read by the mirage-deception service (ml/mirage/deception/serve.py).
# Point the checkpoint at whichever policy you want live; all three trained
# checkpoints are committed under ml/mirage/deception/ and ship inside the
# image at /app/ml/mirage/deception/. ppo.pt is the strongest of the three in
# offline evaluation. To run a newer checkpoint without rebuilding the image,
# place it under ./artifacts/deception/ instead and point this there.
# MIRAGE_DECEPTION_CHECKPOINT=/app/ml/mirage/deception/ppo.pt
# MIRAGE_DECEPTION_ALGO=ppo
# MIRAGE_DECEPTION_PORT=8787
# MIRAGE_DECEPTION_MAX_STEPS=25
# MIRAGE_DECEPTION_SESSION_TTL_SECONDS=900
# ── SSH Server: Connection / Timeout Limits ─────────────────────────────────
# These aren't new -- they've always been read via os.Getenv in
# internal/server/server.go -- just documented here for the first time
# alongside TRUST_PROXY_PROTOCOL below.
#
# How long a silent, already-authenticated connection can sit idle before
# being dropped (seconds).
# SSH_IDLE_TIMEOUT_SECONDS=120
#
# How long an SSH handshake has to complete before being abandoned (seconds).
# SSH_HANDSHAKE_TIMEOUT_SECONDS=20
#
# Offer keyboard-interactive auth (one "Password: " prompt) alongside
# password. Off by default because stock Ubuntu 22.04 does not offer it, so
# turning it on changes the advertised method list.
# SSH_KEYBOARD_INTERACTIVE=false
#
# Hard cap on simultaneous in-flight connections; a flood past this point is
# refused outright instead of risking an OOM kill.
# MAX_CONCURRENT_CONNECTIONS=500
# The telnet sensor has its own, lower cap: its sessions may run 4,000
# commands (echo-loaders) and it runs in a 512M container.
# TELNET_MAX_CONCURRENT_CONNECTIONS=100
# ── PROXY Protocol (optional) ────────────────────────────────────────────────
# Off by default. Only turn this on if Mirage sits behind something that
# forwards connections at the TCP layer (a load balancer, a relay/sensor
# node, etc.) and prepends a PROXY protocol v1 header to each one -- when
# enabled, that header's claimed source IP/port is trusted and recorded
# instead of the raw TCP peer address (conn.RemoteAddr()).
#
# NEVER enable this on a listener directly reachable from the open internet:
# anyone connecting straight to that port could hand-craft their own PROXY
# header and spoof any source IP they want, poisoning every downstream
# log/classification/re-identification signal that depends on client_ip.
# TRUST_PROXY_PROTOCOL=false
#
# The telnet sensor has its own flag, since both sensors read this file: one
# shared switch would make a still-public listener start trusting headers the
# moment the other sensor was moved behind a relay.
# TELNET_TRUST_PROXY_PROTOCOL=false
#
# Limits which TCP peers may send a header when either flag above is on:
# comma-separated IPs and CIDRs (IPv4 or IPv6), normally just the relay. A
# header from any other peer is ignored and that connection is recorded as
# direct, so the public port can stay open next to the relay's tunnel.
# Empty means every peer is trusted (logged as a warning at startup).
# PROXY_TRUSTED_PEERS=10.8.0.1,fd00:8::/64
# ── LLM shell completion ────────────────────────────────────────────────────
# Answers commands the simulated shell has no builtin for (uptime, nproc,
# lspci, ...) with generated output instead of "command not found". Off by
# default: it costs real API calls on a publicly reachable service.
#
# Never fires for a builtin, a compound line (`;` `&&` `|` `>` `$()` ...), or
# an egress-flavoured command (wget/curl/nc/ssh/python/apt/...). See
# internal/deception/completion.go and SECURITY.md's rules of engagement.
# MIRAGE_LLM_SHELL_ENABLED=true
# MIRAGE_LLM_SHELL_TIMEOUT_MS=4000
# Providers, as JSON. `api_key_env` names the environment variable holding the
# key -- never put a key in this value itself. `provider` is "anthropic" or
# "openai_compatible"; the latter also covers self-hosted servers speaking the
# OpenAI API (Ollama, vLLM, LM Studio) via base_url.
#
# MIRAGE_LLM_SHELL_PROVIDERS_JSON=[{"name":"fast","provider":"anthropic","model":"claude-haiku-4-5-20251001","api_key_env":"ANTHROPIC_API_KEY"},{"name":"local","provider":"openai_compatible","model":"qwen2.5:7b","base_url":"http://host.docker.internal:11434/v1"}]
# MIRAGE_LLM_SHELL_ACTIVE_PROVIDER=fast
# Cost guards. Per-session cap bounds one attacker's spend; the global limit
# is a sliding window across all sessions. Both refuse rather than queue.
# MIRAGE_LLM_SHELL_MAX_PER_SESSION=25
# MIRAGE_LLM_SHELL_GLOBAL_RATE_LIMIT=600
# ANTHROPIC_API_KEY=
# OPENAI_API_KEY=
# ── Data-validity dashboard ─────────────────────────────────────────────────
# Identifies this deployment's own heartbeat rows (see internal/validity,
# internal/store/migrations/008_sensor_heartbeats.sql). Only matters once more than one sensor
# writes into a shared setup -- a single deployment can leave this unset.
# SENSOR_ID=default
# Additional sensors the dashboard at /dashboard can query, beyond this
# instance's own database (always available as "default"). JSON array of
# {"name": "...", "dsn": "..."}. Forward-looking for a future multi-sensor
# fleet (see the audit roadmap's P5) -- leave unset for a single deployment.
# MIRAGE_EXTRA_SENSORS_JSON=
# ── mirage-fleet (optional) ─────────────────────────────────────────────────
# Off by default -- unset, this sensor behaves exactly as it does today. When
# set, every finished session and heartbeat is also pushed to a central
# mirage-fleet instance (separate repo/service), so multiple mirage-core
# deployments can be seen individually and consolidated from one place. See
# internal/fleet and mirage-fleet's own CLAUDE.md/DECISIONS.md.
#
# Get MIRAGE_FLEET_API_KEY by calling mirage-fleet's
# POST /v1/nodes/register once per deployment (see its README) -- it is not
# generated here.
# MIRAGE_FLEET_URL=http://mirage-fleet:8090
# MIRAGE_FLEET_API_KEY=
# MIRAGE_FLEET_TIMEOUT_MS=3000
# How often a heartbeat is pushed to mirage-fleet and the local retry queue
# (sessions that failed to push, e.g. while mirage-fleet was unreachable) is
# drained. Defaults to the same interval as the local SENSOR_HEARTBEAT_INTERVAL_SECONDS.
# MIRAGE_FLEET_HEARTBEAT_INTERVAL_SECONDS=60
# Where failed session pushes are queued for retry (JSONL, one session per
# line). Needs to survive container restarts to actually guarantee no data
# loss -- see the fleet-queue volume mount in docker-compose.yml.
# MIRAGE_FLEET_QUEUE_PATH=data/fleet_queue.jsonl
# Ceiling on the retry queue file, in bytes (default 64MiB). Past it the oldest
# undelivered sessions are evicted: the local Postgres is authoritative, so the
# freshest undelivered data is the half worth keeping. Prevents a long
# mirage-fleet outage from filling the disk.
# MIRAGE_FLEET_QUEUE_MAX_BYTES=67108864
# Connection-pool bounds for the local Postgres. The default 20 open / 10 idle
# sits well under Postgres's own max_connections (100 by default) while
# MAX_CONCURRENT_CONNECTIONS allows 500 sessions to finalize concurrently --
# without a bound the surplus fails with "too many clients" and SaveSession
# only logs, losing sessions from the store that is authoritative for this node.
# DB_MAX_OPEN_CONNS=20
# DB_MAX_IDLE_CONNS=10
# DB_CONN_MAX_LIFETIME_MINUTES=30