Limen-Capital is an experimental SNN-HFT research stack. It is not a production exchange gateway. Default configuration assumes a single-user, loopback-trust host.
| Surface | Default | Trust assumption |
|---|---|---|
| Binary ZMQ MarketPulse / ReadoutPacket | tcp://127.0.0.1:5555 / :5556 |
Only local processes publish; plain ZMQ (no CURVE) |
| JSON TradeSignal adapter | User-scoped IPC under $XDG_RUNTIME_DIR/limen-capital/ (or /tmp/limen-capital-$UID/) |
Same OS UID; override with validated LIMEN_JSON_IPC |
| Ghost wallet (metabolic-ledger) | Local JSONL paper ledger | No exchange credentials |
| MarketPulse floats | Finite required; prices > 0 rejected otherwise; vols clamped to [0, 1] (accepted after clamp, not fail-closed) |
Non-finite / non-positive price fail closed |
- Live capital / mainnet order routing
- Exchange API key management
- Multi-tenant host hardening beyond user-scoped IPC defaults
- ZAP / client public-key allowlisting for CURVE (see open issue on optional ZAP)
Research default is plain ZMQ on loopback.
Not end-to-end yet: only the Julia brain path currently has CURVE hooks. The bundled Rust publisher/subscriber peers still use plain ZMQ. Setting ZMQ_CURVE=1 on the brain alone will break MarketPulse / ReadoutPacket links until both sides use compatible CURVE roles and keys. Treat this as a future dual-side upgrade (tracked with optional ZAP allowlisting).
When both ends support CURVE:
- Set
ZMQ_CURVE=1on every peer (brain and muscle). - Provide real CURVE material via env only (never commit keys):
ZMQ_SERVER_KEY/ZMQ_CLIENT_KEY(and any peer public keys your build expects).
- Do not treat CURVE alone as multi-user authentication until an allowlist (ZAP) lands.
Do not use a world-predictable path such as /tmp/spikenaut_signals.ipc.
- Default:
$XDG_RUNTIME_DIR/limen-capital/signals.ipcwhenXDG_RUNTIME_DIRis set; otherwise/tmp/limen-capital-$UID/signals.ipc(numeric OS UID). For defaults only, Capital creates the directory and requires mode0700(failures abort setup). - Override:
LIMEN_JSON_IPCmust beipc://+ absolute filesystem path with no..segments (e.g.ipc:///run/user/1000/limen-capital/signals.ipc). Invalid overrides are rejected. An empty override is treated as unset (falls back to defaults). Same-UID isolation applies to default endpoints; override parents are not auto-created orchmod’d — the operator must secure them. - IPC ownership: before
ipc://bind, the publisher holdsflock(LOCK_EX|LOCK_NB)on*.owner.lockfor the process lifetime. This counters libzmq’s unlink-on-bind steal of a live socket among cooperative Capital publishers. The lock file is left on disk (not deleted on shutdown) so ownership stays tied to a stable inode; the flock ends when the process exits orshutdowncloses the fd. Non-flock binders on the same path are outside this protocol and are not protected. - Publisher (
strategy/signal_broadcaster.jl) and consumer (execution,LIMEN_WIRE=json) must agree.
If other users can run processes on the same machine:
- Prefer binary loopback with host firewall / namespace isolation, or disable unused listeners.
- Always use user-scoped IPC (defaults above) or a private path via
LIMEN_JSON_IPC. - Treat any non-loopback bind as out of scope for this research threat model.
- Do not place secrets in the tree; use env vars and ignore rules (
.env, vault dirs).
- No ZAP allowlist — CURVE encrypts but does not yet restrict which client keys are accepted.
- Loopback is not multi-tenant security — co-located attackers with the same privileges can still talk to local sockets.
- Ghost ledger only — accidental wiring to a live broker is operator error; this repo does not ship exchange keys.
Private vulnerability reports: use GitHub Security Advisories for this repository —
https://github.com/rmems/Limen-Capital/security/advisories/new
Do not file public issues that include live credentials or private keys.