Operations and Hardening - krazybeastx1/omenhound GitHub Wiki
OmenHound handles live credentials and can act on Active Directory, so it treats itself
as a high-value target. This page is the operator-facing summary; the full analysis lives
in docs/THREAT-MODEL.md.
| Control | Default | How to change |
|---|---|---|
| Plaintext LDAP collection | refused |
--allow-plaintext-ldap (lab only) |
AD writes (unicodePwd) |
disabled | --enable-credential-ops |
| Write API (ingest/credentials) | disabled | --api-token <secret> |
| Write rate limit | 120/min, burst 30 |
--write-rate-limit, --write-burst
|
| Listen address | 127.0.0.1:8080 |
--addr |
| TLS verification | on |
--insecure (lab only, loudly warned) |
- Secrets are held as
[]byte, never written to disk, and explicitly zeroed after use (creds.Zero, withruntime.KeepAliveto defeat dead-store elimination). The caller's buffer is copied, never aliased. - The session credential store is memory-only, TTL-scoped (15 min) and capped (64 entries, max 4096 bytes per secret); a background janitor sweeps expired entries.
- The audit logger redacts any field whose name matches
password/secret/token/hash/ โฆ before writing. - Prefer
--password-stdinor$OMENHOUND_PASSWORDover--password, which is visible in process listings.
- Collection uses LDAPS (636). AD writes refuse to run without an encrypted
channel โ
internal/actreturnsErrLDAPSRequiredand dialsldaps://with a minimum of TLS 1.2. -
--ldap-channel-bindingis accepted for BloodHound.py parity but is a no-op for LDAP simple bind over TLS (channel binding applies to NTLM/Kerberos). OmenHound tells you so rather than silently ignoring the flag.
Every write endpoint enforces a bearer token with a constant-time comparison.
POST /api/v1/ingest (merge by default; ?mode=replace clears first)
DELETE /api/v1/graph (confirmation required: ?confirm=true)
POST /api/v1/credentials
DELETE /api/v1/credentials/{id}
POST /api/v1/credentials/{id}/change-password
- If
--api-tokenis empty, writes return 403 "write API is disabled". - A bad/missing token returns 401 unauthorized and is audited as a denied auth event.
-
DELETE /api/v1/graphis destructive, so it also requires?confirm=true; a bare delete is refused with 400. In--neo4j-urimode it clears the whole configured database. - The password-change endpoint has an additional per-action approval gate: it requires
the header
X-Omenhound-Approval: yes. Without it, the request is refused with 428 Precondition Required and audited as denied.
# 1) start the server with credential ops enabled (authorized lab only)
export OMENHOUND_API_TOKEN="$(openssl rand -hex 32)"
./bin/omenhound serve --collector none --addr 127.0.0.1:8080 \
--api-token "$OMENHOUND_API_TOKEN" \
--enable-credential-ops \
--act-server dc01.corp.local \
--audit-log /var/log/omenhound/audit.jsonl
# 2) stage the credential (one-shot; TTL-scoped, memory only)
curl -sS -X POST http://127.0.0.1:8080/api/v1/credentials \
-H "Authorization: Bearer $OMENHOUND_API_TOKEN" \
-d '{"domain":"corp.local","username":"svc","password":"..."}'
# -> {"session_id":"...","expires":"..."}
# 3) change the target's password โ requires explicit approval
curl -sS -X POST http://127.0.0.1:8080/api/v1/credentials/<session_id>/change-password \
-H "Authorization: Bearer $OMENHOUND_API_TOKEN" \
-H "X-Omenhound-Approval: yes" \
-d '{"target_dn":"CN=Jane,OU=Users,DC=corp,DC=local","target_sid":"S-1-5-...","new_password":"...","reason":"TICKET-123"}'The staged credential is taken (one-shot) by the change operation and destroyed afterwards.
Every write and auth decision is written as a JSON line with actor, action, target,
result, reason, remote address, and selected fields. Steer it to a file with
--audit-log; otherwise it goes to stderr.
{"time":"...","actor":"api","action":"credential.change_password","target":"CN=Jane,...",
"result":"ok","reason":"TICKET-123","remote_addr":"127.0.0.1:51234","fields":{"target_sid":"S-1-5-..."}}- The server binds to loopback by default. If you expose it, put it behind a TLS reverse proxy and keep the bearer token secret.
- The API sets
Content-Security-Policy,X-Content-Type-Options: nosniff, and emitsStrict-Transport-Securitywhen serving over TLS. - A per-IP token-bucket limiter protects all endpoints; writes have their own limiter.
The client IP is derived from the socket and ignores
X-Forwarded-Forto avoid spoofed-limit bypass.
OmenHound's abuse templates include per-technique OPSEC and event IDs. In general:
- LDAP enumeration is read-only but noisy under advanced audit policy (4662 for object reads, 1644 with expensive/LDAP diagnostics).
- Password resets hit 4724; ACL writes hit 5136/5137; replication hits 4662; Kerberos abuse shows in 4768/4769.
- Pace collection (
--timeout, method selection) and prefer DCOnly-style minimal collection when you only need paths.
- Written authorization, scope, and a defined time window.
- Dedicated, least-privilege bind account; rotate it after.
- LDAPS only; never
--allow-plaintext-ldapoutside a lab. - Keep credential ops off unless you intend to change passwords, and use the approval header deliberately.
- Ship the audit log to a system you don't control.
- Clean up: delete staged credentials, revert any AD changes, report findings.
Next: API Reference โ