The tool runs an operator console that sits on the internet during an engagement. Two separate things matter here: the tradecraft that keeps the attack quiet, and the hardening that keeps the console itself from becoming a liability if someone finds it. Both are covered below.
The console holds live tokens for the client tenant, so treat the host it runs on as sensitive.
Authentication. Operator passwords are hashed with argon2id. SHA-256 hashes from earlier builds still verify and are re-hashed to argon2id on the next successful login, so existing accounts keep working with no migration step. Password comparison is constant-time.
Login throttling. Five failed logins for an account, or twenty from one IP, within 15 minutes locks further attempts for 15 minutes and returns 429. The counters live in memory and reset on restart. If you lock yourself out, entraith reset-admin from the CLI still works.
Encryption at rest. Captured access and refresh tokens, PRTs and their session keys, SMTP passwords, OTP secrets, device-certificate private keys and Windows Hello keys are encrypted with AES-256-GCM before they reach SQLite. If the database file leaks, none of those come out readable. Decryption happens when the app reads a row, so nothing in the attack path changes.
Exposure. /health returns only {"status":"ok"} with no engagement or operator identifiers. Session cookies are HttpOnly, SameSite=Strict, and Secure by default. Console pages set a CSP plus the usual anti-clickjacking and nosniff headers. The target-facing landing pages (/qr, /intune) get none of that, on purpose, so they stay unremarkable to a scanner.
Webhook controls. /receive is the always-on beacon receiver and stays public. The control endpoints (/webhook/start, /webhook/stop, /webhook/status, /webhook/logs) require an operator session.
Token listener. The token-intake server (POST /token) runs on its own port and is unauthenticated by design — an external AiTM proxy or phishing page has to be able to reach it. Bind it where only your infrastructure can, or front it with a redirector; it is not covered by server.ip_allowlist (that guards the console). Its /api/token-listener/* controls require an operator session. Ingested tokens are encrypted at rest like any capture, and the token_listener.log audit trail records only redacted token fingerprints (prefix + length), never usable token material.
Network restriction. server.ip_allowlist limits the console to known source IPs while leaving the phishing and beacon endpoints open. A non-allowed source gets a 404 instead of a login page.
The at-rest key comes from one of two places:
auth.secret_keyin the config, if you set it. Any length; it is run through SHA-256 to derive the AES key.- Otherwise a random 32-byte key written to
<parent of artifacts_path>/.entraith.key(mode 0600) on first run.
Pick one source and keep it for the life of the database. Switch sources, or lose the key file, and the rows encrypted under the old key can no longer be decrypted, which means the tokens are gone. Back the key file up with the rest of the engagement data. A database written before encryption was added still works: legacy plaintext rows are read as-is and re-encrypted the next time they change.
server.secure_cookies defaults to on. Behind a TLS proxy or with server.tls, leave it on. Browsers also send Secure cookies to http://localhost, so local testing is fine. Set it to false only when you reach the console over plain HTTP on a non-localhost address, or the browser will drop the session cookie and login will look like it silently fails.
golang.org/x/crypto is pinned to v0.36.0 so the module stays on the go 1.24 toolchain; newer releases require go 1.25. Build with GOTOOLCHAIN=local if your installed Go is older than the toolchain directive.
All outbound HTTP requests to Microsoft endpoints use a realistic Windows browser User-Agent. One UA is chosen at Engine creation and used consistently for all requests in that campaign.
Each polling goroutine sleeps interval ± 30% between polls, computed independently. slow_down responses trigger additional jittered backoff (base 10s ± 30%).
Campaign launch issues device code requests sequentially with a random 800ms–3000ms delay between each — avoiding burst patterns in Microsoft's auth logs.
Message-ID— 16 bytescrypto/rand, scoped to sender domain, unique per messageDate— RFC 5322 from actual send time- MIME boundary — 12 bytes
crypto/randper message - No
X-MailerorX-Originating-IP
All HTML files served to targets (qrlanding.html) and publicly reachable pages (login.html) are stripped of operator comments, tool-identifying strings in HTML comments, and phishing-indicative JS annotations. Pages behind authentication (dashboard.html, infra.html, tools.html) are also comment-clean. If you modify any HTML, avoid re-introducing comments that reveal tool purpose or operator intent.
| Control | Recommendation |
|---|---|
| Infrastructure | Dedicated VPS per engagement, not shared or reused |
| SMTP account | Purpose-registered domain with SPF, DKIM, DMARC aligned to from_address |
| Domain age | Register phishing domain weeks before the engagement |
| TLS | Run behind Caddy or nginx with a real certificate |
| Egress | Route operator traffic through a VPN or SSH tunnel |
| Access | Bind to 0.0.0.0 to expose publicly; put TLS termination (Caddy/nginx) in front if not using a direct cert |
| Cleanup | Export campaign data, then Delete to wipe the database before leaving infrastructure |