omnius-fabric
v0.12.8
Published
Omnius Fabric: install-to-node encrypted cooperative routing fabric with automatic UPnP, bootstrap discovery, peerbook/DHT building, relay fallback, QR-based connect handshake, rolling-key sessions, and E2E addressable messaging.
Downloads
4,460
Maintainers
Readme
Omnius Fabric
Install-to-node encrypted cooperative routing fabric for Node.js. One
npm install turns a machine into a reachable peer: it self-maps a public
port, bootstraps off public NATS rendezvous, advertises an NKN overlay
fallback, relays for NATed peers, and exchanges end-to-end encrypted messages
— addressable by a signed fabric:// ticket.
Architecture
Each node runs a libp2p peer with the services below.
The fabric is self-provisioning: any node that becomes publicly reachable
(via UPnP/NAT-PMP) automatically serves as a circuit relay for peers that
cannot map a port, and public NATS is the default rendezvous that seeds the
DHT on a cold start. NKN runs alongside libp2p as an overlay fallback: when
connected, the daemon advertises an nkn route hint so peers can still send
the same end-to-end encrypted envelope over the public NKN mesh when IP-level
reachability is unavailable.
| Service | Purpose |
|---|---|
| KadDHT | Distributed hash table for peer discovery and route-record storage (/omnius/fabric/kad/1.0.0) |
| GossipSub | Pub/sub mesh for route announcements, peer exchange, and metadata |
| pubsub peer discovery | Peers announce multiaddrs over a well-known topic; learned peers are dialed and seed the DHT |
| Circuit Relay v2 | Relay reservations for NATed peers — client, server, or both (default both) |
| DCUtR | Direct Connection Upgrade through Relay — hole-punches NATed peers after relay rendezvous |
| Noise | Transport-level encryption (XX handshake, Curve25519, AEAD) |
| Yamux | Stream multiplexing over a single connection |
| Identify | Protocol and multiaddr exchange between connected peers |
| mDNS | Local-network peer discovery (LAN/zeroconf) |
| Bootstrap | Static bootstrap peer list for initial DHT seeding (when configured) |
| NKN overlay | Name-resolved public p2p mesh used as an additional encrypted delivery fallback; advertised through signed nkn route hints |
| Ping | Connectivity health checks |
Libp2p transports: TCP, WebSocket, and Circuit-Relay (client/both modes). NATS acts as an out-of-band rendezvous plus encrypted inbox fallback. NKN is a daemon-level overlay fallback carried in signed address route hints, not a libp2p multiaddr transport.
All protocol identifiers use the /omnius/fabric/... namespace:
| Protocol | String |
|---|---|
| DHT | /omnius/fabric/kad/1.0.0 |
| Direct Message | /omnius/fabric/dm/1.0.0 |
| Session | /omnius/fabric/session/1.0.0 |
| Route Sync | /omnius/fabric/route-sync/1.0.0 |
| Connection Upgrade | /omnius/fabric/upgrade/1.0.0 |
| Media Transfer | /omnius/fabric/media/1.0.0 |
| Relay Probe | /omnius/fabric/relay-probe/1.0.0 |
| Meta (pubsub) | /omnius/fabric/meta/1.0.0 |
| Routes (pubsub) | /omnius/fabric/routes/1.0.0 |
| PeerX (pubsub) | /omnius/fabric/peerx/1.0.0 |
| Relays (pubsub) | /omnius/fabric/relays/1.0.0 |
Discovery cascade
Peers are discovered through every available channel:
- Persisted — previously known peers from the local peerbook (
peers.json) - Public NATS rendezvous — ON by default. A fresh node learns peers'
signed addresses over NATS route gossip, dials them, and — because they
speak
/omnius/fabric/kad/1.0.0— seeds its Kademlia routing table. This is the primary cold-start path; generic libp2p/IPFS bootstrap nodes can't seed a custom-protocol DHT. - HTTPS manifests — signed bootstrap manifests from operator-provided remote URLs (opt-in)
- libp2p bootstrap —
@libp2p/bootstrapdials configured multiaddrs - GossipSub peer exchange — route-record gossip + pubsub peer discovery
- DHT walking — KadDHT random-walk fills the routing table
- mDNS — local-network multicast discovery (zeroconf)
- NKN route hints — connected daemons include an
nknaddress in signedfabric://tickets so any discovery channel can teach peers the overlay fallback route - Relay reservation discovery — circuit-relay advertisements
Transport selection
When delivering a message the daemon walks a cascade, preferring the most direct path and falling back only as needed:
- Direct libp2p — dial the peer's
fabric://route multiaddrs (public IP or UPnP-mapped) over TCP/WebSocket. - Circuit Relay v2 + DCUtR — reach NATed peers through a relay, then hole-punch to a direct path where the NATs allow it.
- NATS inbox — brokered fallback. The already-encrypted envelope is published to the recipient's inbox subject; NATS brokers see only opaque ciphertext.
- NKN overlay — universal fallback. If the recipient's signed address
carries an unexpired
nknhint and this daemon's overlay client is live, the same encrypted envelope is sent to the recipient's NKN address. No public IP, open port, relay reservation, or NATS inbox reachability is required between the two peers.
Connection tiers (trust upgrade)
Relationships between peers are tiered and established by a mutual, double-signed handshake — neither side can fabricate a relationship alone:
| Tier | Name | Unlocks | How established |
|---|---|---|---|
| T0 | Discovered | route exchange, relay, basic E2E messaging | automatic — the open fabric |
| T1 | Trusted | richer messaging, scoped capability delegation | requester asks → target approves → both countersign a TrustGrant |
| T2 | Media | file, image, and video sharing in chat (media:share) | requester asks → target approves → both countersign a TrustGrant |
| T3 | Administrative | service exposure and scoped administrative access (e.g. shell:exec) | T2-style handshake at the highest bar; target opts in |
Grants carry an Ed25519 signature from both parties (granter +
grantee), a capability scopes[] list, and an expiry. Either side can
revoke; revocation is gossiped to the peer and the relationship downgrades
immediately. This is exposed today over the token-gated control API:
POST /api/upgrade/request { peerId, tier, scopes?, reason?, address? }
POST /api/upgrade/respond { requestId, decision: approve|deny, scopes? }
GET /api/relationships
POST /api/relationships/revoke { peerId, grantId? }
GET /api/notifications # pending inbound/outbound requests
GET /api/remote/peers # peers who granted us administrative scopeA grant only authorizes the relationship; any privileged action layered on top (such as an SSH passthrough) still performs its own authentication.
Chat media transfer
Tier 2 unlocks media sharing in the web chat. Dropping one or more files onto
an active chat either requests the Tier 2 media upgrade, or, when the
relationship already has media:share, stages the files through the local
daemon in disk-backed chunks. The sender then creates a media-offer control
message over the existing end-to-end encrypted DM channel. The recipient sees
an incoming file bubble and explicitly accepts or denies it. Only after
acceptance does the sender push the bytes over the dedicated libp2p media
stream:
/omnius/fabric/media/1.0.0The media stream is authenticated against the libp2p peer id carried in the
accepted offer's signed route. Files are sent as ordered chunks, reassembled to
a .part file, and promoted only after the receiver verifies the byte count
and SHA-256 hash from the offer. Received files are stored under the local data
directory and downloaded through the token-gated loopback control API without
buffering the whole file in memory. Images show an inline preview after
receipt; all file types can be downloaded.
Media control API:
POST /api/media/offer { peerId, filename, mime, dataBase64, address? }
POST /api/media/upload/start { peerId, filename, mime, size, address? }
POST /api/media/upload/chunk?transferId=<id>&offset=<n> raw bytes
POST /api/media/upload/finish { transferId }
POST /api/media/accept { transferId }
POST /api/media/deny { transferId }
GET /api/media/download/<transferId>Bulk file transfer is intentionally gated on media:share; T0/T1 peers can
still chat, but cannot offer files.
Signed route records
Every node periodically publishes a SignedRouteRecord to the DHT and
GossipSub topics. The record is an Ed25519-signed JSON envelope:
schema: omnius:fabric:route-record:v1
peerId + optional nickname + signing + encryption public keys
directAddrs + relayAddrs (libp2p multiaddrs)
observedAddrs (what other peers see)
preferredRelays, capabilities, role, seq, issuedAt, expiresAt
Ed25519 signature over canonical JSON bodyRecords are stored in the DHT at /omnius/fabric/route/<peerId> and gossiped
on topic /omnius/fabric/routes/1.0.0.
Install
npm install -g omnius-fabricOn first install, the package starts a background fabric node automatically. That node:
- creates
~/.omnius-fabric/default - generates/persists Ed25519 + X25519 identity keys
- starts a libp2p node (TCP + WebSocket transports, KadDHT, GossipSub)
- attempts UPnP/NAT-PMP port mapping by default, and auto-serves as a relay if it succeeds
- bootstraps off public NATS rendezvous by default; HTTPS manifests are opt-in
- starts the NKN overlay by default and stores its seed under the data dir
- publishes a signed route record to DHT + GossipSub
- starts building
peers.jsonas the local peerbook - writes
state.jsonsofabric address/fabric statuswork from any shell - exposes a loopback HTTP control endpoint (token-gated) for
fabric send/fabric connect/ the web console
Upgrades replace the running daemon: the postinstall stops any stale fabric
daemon (graceful SIGTERM, then SIGKILL) and spawns the new version, so a
npm install -g omnius-fabric@latest never leaves an old process running
outdated code.
Running daemons also self-check for npm releases. By default the daemon polls
omnius-fabric@latest once per hour; when a newer version appears it starts
npm install -g omnius-fabric@latest in the background. The package
postinstall then stops the stale daemon and starts the freshly installed
version with the same data directory, so the fabric stays current without a
manual fabric stop && npm install && fabric start cycle. Status is exposed
as autoUpdate in fabric status and in the web console.
Opt out of first-install background start:
OMNIUS_FABRIC_START_ON_INSTALL=0 npm install -g omnius-fabricOpt out of daemon self-updates:
OMNIUS_FABRIC_AUTO_UPDATE=0 fabric start
fabric start --no-auto-updateVerify it is running
fabric status # JSON daemon snapshot
fabric address # just the fabric:// address
fabric peers # learned peer book entries
fabric dht # real KadDHT routing table (or peerbook fallback)
fabric doctor # full diagnostics: libp2p, DHT, connections, bootstrap
fabric ui # open the embedded dark-mode web console
fabric tui # open the terminal-native console (also the no-arg default)
fabric stop # gracefully terminate the daemonWeb console
Every daemon serves a self-contained single-page console on a loopback port.
fabric ui prints (and opens) a one-time URL of the form
http://127.0.0.1:<port>/?token=<token> — the page captures the token from
the query string, strips it from the address bar, then talks to the daemon's
token-gated control API. The control API is bound to 127.0.0.1 only, so
other hosts on your LAN cannot reach it.
The console is a monochrome (black / mid-grey) SPA with:
- a collapsible left sidebar with hash-routed tabs and a live endpoint
list (UPnP, relay, Tor, NATS, NKN, KadDHT); the top-left brand shows the
running
omnius-fabricversion - hash routes for every tab (
#/status,#/peers,#/chat,#/sessions,#/connect,#/privileges,#/graph,#/settings,#/endpoint?kind=tor) with query-param deep-links (#/chat?peer=nf_…,#/connect?token=…) - clickable endpoint detail/config pages for libp2p, KadDHT, NATS, NKN,
Tor, UPnP/NAT-PMP, and Circuit Relay. Long endpoint values such as
.onionaddresses are ellipsized in the sidebar and expanded on the detail page. - a Chat view with per-peer conversation panes, on-disk persistence, and transport-endpoint icons per peer. Peer nicknames advertised by the originator appear in chat and can be used as a search term.
- message delivery details in chat: a small info icon next to each timestamp opens a hover card showing delivery state, transport, route, latency, and raw attempt strings (for example direct libp2p, route multiaddr, circuit relay, or NATS inbox fallback)
- a connection upgrade dropdown in chat for requesting T1, T2, or T3, plus an inline approve/deny banner for inbound upgrade requests
- Tier 2 media chat: attach or drag/drop one or more files/images/videos
after a
media:shareupgrade, auto-request Tier 2 when media is not yet allowed, accept or deny inbound file offers, preview received images, and download accepted files. Large files use disk-backed chunk upload, chunked libp2p transfer, and SHA-256 reassembly verification. - a Sessions view with a quick request row: type a nickname, peer id, or
full
fabric://address, send a session request, approve/deny inbound session requests, and cancel/delete pending or active sessions. - a Settings view to toggle UPnP, Tor, NATS (and its public defaults),
NKN overlay settings, relay mode, capacity, bootstrap, manifest URLs,
automatic update polling, and the node nickname — persisted to
settings.jsonand applied on next restart - automatic update status in Node status, showing current version, latest observed version, last check, next check, and any update error
- a Graph view: a three.js force-directed topology with monochrome splines and animated activity dots
- topbar status pills (online / dht / upnp / nats / nkn)
Terminal UI
fabric tui opens a terminal-native console against the running daemon's
loopback control API. Invoking fabric with no subcommand opens the same TUI
when stdout is a TTY; if no daemon is running, it starts the background daemon
first and then opens the TUI. Non-interactive shells still get command help.
The TUI is read/write where the control API supports it and otherwise mirrors the browser console with keyboard navigation:
- status dashboard with online, DHT, UPnP, NATS, and NKN pills
- peerbook table with nicknames, route counts, and last-seen data
- chat peer list, transcript view, and encrypted DM composer
- sessions and notifications views for rolling-encrypted session state
- read-only settings summary for daemon configuration, including NKN state
REST and MCP agent surface
The loopback control port exposes a machine-readable REST catalog and a Model Context Protocol endpoint for agents. Both surfaces use the same one-time daemon token as the GUI:
Authorization: Bearer <token>
x-fabric-token: <token>
?token=<token>REST discovery:
GET /api/capabilities # REST catalog + MCP connection guidance
GET /api/rest # alias for the catalog
GET /api/endpoints # computed libp2p/upnp/relay/tor/nats/dht states
GET /api/endpoints/<kind> # endpoint detail for libp2p, upnp, relay, tor, nats, dhtThe MCP streamable HTTP endpoint is:
POST /mcpInitialize with protocol version 2025-11-25, then use tools/list,
tools/call, resources/list, resources/read,
resources/templates/list, prompts/list, and prompts/get. The server
returns an MCP-Session-Id header on initialize, supports
MCP-Protocol-Version: 2025-11-25, requires
Accept: application/json, text/event-stream, supports JSON responses,
single-event SSE responses, JSON-RPC batches, notification 202 Accepted
responses, and DELETE /mcp session termination, validates browser Origin
headers for the local endpoint, and exposes REST-equivalent tools for status, peers,
sessions, chat, trust upgrades, media offer/accept, settings, privileges,
WebRTC status, DHT, and endpoint diagnostics.
Agent guidance is available as both a resource and prompt:
resources/read omnius://guide
prompts/get omnius-agent-guideTransport-level encryption is handled by Noise (XX handshake, Curve25519, AEAD). Application payloads are additionally encrypted with the session ratchet (AES-256-GCM + HKDF-SHA512). Relay nodes cannot decrypt application data.
UPnP / NAT-PMP
The daemon attempts UPnP gateway discovery first, then NAT-PMP/PCP as a
fallback (many consumer routers fail SSDP but answer NAT-PMP cleanly). The
discovery budget is 30 s per protocol. The current mapping state is visible
in fabric status and in the web console.
When UPnP succeeds, the mapped external address is added to the node's route
record and published via DHT/GossipSub, making the node reachable from outside
the local network. A reachable node also auto-serves circuit-relay
reservations (relay mode defaults to both), so it immediately helps NATed
peers join the fabric. The mapping maps the actual libp2p-bound TCP port,
which can differ from the requested one if the port was taken at startup.
QR connect handshake (rolling-encrypted sessions)
The simplest path to a rolling-encrypted channel between two peers.
Issue an invite
fabric connectfabric connect prints a scannable QR code in the terminal followed by a
fabric+invite://… token. The invite contains:
- the issuer's signed
fabric://address - a single-use 256-bit pre-shared key (PSK)
- an X25519 ephemeral public key bound to this invite
- a deterministic session id
- issued/expires timestamps
- an Ed25519 signature over the body
The PSK + ephemeral keys are stored locally under
~/.omnius-fabric/default/sessions/<sessionId>.json. The invite is consumed
exactly once by the joiner.
Consume the invite
On the other peer:
fabric join 'fabric+invite://…'This:
- Verifies the invite signature and freshness.
- Generates the joiner's own X25519 ephemeral keypair.
- Derives the session secret with HKDF-SHA512 over two Diffie-Hellman shared secrets (
static×static+ephemeral×ephemeral) salted with the PSK. - Saves a local session record with both directions of the ratchet chain key.
- Sends a signed
session-claimenvelope to the issuer through the libp2p DM protocol.
The issuer's running daemon recognises the claim, matches the session id,
derives the same secret, marks the invite consumed, and emits a signed
session-ack. From that moment both sides hold matching forward-secret chain
keys.
Send rolling-encrypted messages
fabric sessions # list local sessions and their state
fabric send-session <sessionId> hello worldEach message advances a one-way HKDF ratchet: the chain key is overwritten after every send, so an attacker who compromises a chain key cannot decrypt prior messages. Inbound messages are dispatched by the running daemon and printed to its stdout/log.
Under the hood every session message is double-wrapped: the outer envelope is encrypted to the recipient's static X25519 identity (and signed with the sender's Ed25519 identity), and the inner payload is AES-256-GCM with the per-message ratchet key. A relay sees only the outer ciphertext.
Command name
Use the shorthand command:
fabricThe package also exposes the long bin:
omnius-fabricOne-node start
fabric startThe node will:
- generate/persist an Ed25519 + X25519 identity under
~/.omnius-fabric/default - start a libp2p node (TCP + WebSocket, KadDHT, GossipSub, Circuit Relay, DCUtR)
- attempt UPnP/NAT-PMP TCP port mapping automatically
- join bootstrap discovery (HTTPS manifests, libp2p bootstrap) unless disabled
- start the NKN overlay unless disabled, and publish an
nknroute hint when the overlay address is ready - publish a signed route record to DHT + GossipSub
- print a signed
fabric://address ticket - listen for encrypted inbound messages via libp2p DM protocol handler
- discover peers via KadDHT, GossipSub, mDNS, and bootstrap
- persist the local peerbook as it learns routes
- write
~/.omnius-fabric/default/state.jsonso other shells can find it - expose a loopback HTTP control endpoint (token-gated) for
fabric send/fabric connect - poll npm for new releases and replace the daemon automatically unless disabled
Run as a background daemon manually:
fabric start --daemonLogs are written under:
~/.omnius-fabric/logs/Relay mode
The daemon's circuit relay behaviour is controlled by --relay-mode. The
default is both: any node that becomes reachable auto-serves relay
reservations for peers that can't map a port, while still using other relays
itself. NATed nodes pay no cost — no peer dials them for a reservation.
fabric start --relay-mode both # host AND use relays (default)
fabric start --relay-mode server # host reservations only
fabric start --relay-mode client # connect via relays, don't hostWith relay capacity (max concurrent reservations):
fabric start --relay-mode both --relay-capacity 16Bootstrap discovery
By default, fabric start loads bootstrap peers from multiple sources:
- CLI flags:
--relay,--nats(libp2p multiaddrs and/or NATS servers) - env vars:
OMNIUS_FABRIC_RELAYS,OMNIUS_FABRIC_NATS ~/.omnius-fabric/default/bootstrap.json- HTTPS bootstrap manifests (opt-in via
OMNIUS_FABRIC_BOOTSTRAP_URLS) - libp2p bootstrap service (
@libp2p/bootstrapwith configured multiaddrs) - public NATS rendezvous (
demo.nats.io) — merged in by default for every node so DHT seeding works out of the box; runs in the background so daemon readiness is never gated on reaching it - GossipSub peer exchange + pubsub peer discovery + DHT walking (once connected)
Public NATS is the primary cold-start seed because Omnius uses a custom DHT
protocol — generic libp2p bootstrap nodes can't populate it. Run your own
NATS for production reliability (Settings → NATS, or OMNIUS_FABRIC_NATS);
demo.nats.io is a shared, rate-limited public mesh.
Disable the public defaults / bootstrap entirely:
fabric start --no-bootstrap # isolated/local tests
OMNIUS_FABRIC_DEFAULT_NATS=0 fabric start # keep bootstrap, drop public NATSNKN overlay fallback
NKN is enabled by default as an additional p2p delivery path. The daemon
creates or reuses a persistent seed at <dataDir>/nkn-seed, starts an
nkn-sdk MultiClient with the omnius identifier and four sub-clients, and
reports live overlay state in fabric status under nkn.
When the NKN client connects early enough during startup, the daemon includes
an unexpired route hint like this in the signed fabric:// address:
{ "type": "nkn", "role": "nkn", "url": "omnius.<pubkey-hex>", "score": 70 }Delivery still prefers direct libp2p routes, relay/DCUtR, and NATS inboxes
first. NKN is tried after those paths as a universal fallback for peers whose
signed address contains an nkn hint. The payload is the same signed,
end-to-end encrypted envelope used everywhere else; NKN nodes route opaque
ciphertext.
Disable NKN for isolated tests or private/offline deployments:
fabric start --no-nknTwo-node workflow (no QR)
Terminal A:
fabric startCopy the printed fabric://... address.
Terminal B:
fabric send 'fabric://PASTE_ADDRESS_FROM_A' 'hello from B'Terminal A receives and decrypts the message. The daemon delivers it via the
libp2p DM protocol (/omnius/fabric/dm/1.0.0) using the peer's multiaddrs
from the address ticket.
Three-node fabric workflow
Node A acts as a full node and also as a reachable relay server:
fabric start --port 7766 --relay-mode bothNodes B and C join A as relay clients:
fabric start --relay ws://YOUR_PUBLIC_HOST_OR_IP:7766All three now:
- register signed route records in DHT + GossipSub
- discover each other via KadDHT walking, GossipSub peer exchange, and relay discovery
- persist learned peers in
peers.json - can send encrypted messages through direct or relay routes
Inspect learned peers:
fabric peersInspect real KadDHT routing table:
fabric dhtRun full diagnostics:
fabric doctorSend to a full address:
fabric send 'fabric://...' 'message'Send to a learned peer ID:
fabric send nf_abc123... 'message'Diagnostics
The doctor command runs comprehensive diagnostics against the running daemon:
fabric doctorOutput includes:
{
"running": true,
"dataDir": "/home/user/.omnius-fabric/default",
"diagnostics": {
"startedAt": 1716800000000,
"pid": 12345,
"peerId": "nf_abc123...",
"multiaddrs": ["/ip4/192.168.1.5/tcp/7766", "/ip4/127.0.0.1/tcp/7767/ws"],
"connections": 3,
"dht": {
"ready": true,
"routingTableSize": 8,
"peersInTable": ["12D3K...", "16Uiu2..."],
"mode": "server"
},
"bootstrap": {
"sources": ["static-config", "default nats (public bootstrap)"],
"peerCount": 5,
"errors": []
},
"controlPort": 7765,
"upnp": { "ok": true, "externalAddress": "203.0.113.5", "protocol": "nat-pmp" },
"tor": { "available": true, "onionAddress": "...", "socksPort": 9050 },
"nkn": {
"enabled": true,
"connected": true,
"address": "omnius.<pubkey-hex>",
"identifier": "omnius",
"numSubClients": 4
}
}
}The dht command provides a quick view of KadDHT state, or falls back to
peerbook data when no daemon is running:
fabric dhtAutostart
Enable user-level autostart:
fabric autostart enableWith relay/NATS hints:
fabric autostart enable --relay ws://seed.example.com:7766 --nats nats://seed.example.com:4222Status:
fabric autostart statusDisable:
fabric autostart disableAutostart targets:
- Windows: Startup
.cmd - macOS: user LaunchAgent
- Linux: user systemd service file
Linux note: after enabling, run the command printed by the CLI, usually:
systemctl --user daemon-reload && systemctl --user enable --now omnius-fabric.serviceEnvironment variables
OMNIUS_FABRIC_START_ON_INSTALL=0 disable postinstall background start
OMNIUS_FABRIC_DEFAULT_NATS=0 disable default NATS discovery
OMNIUS_FABRIC_BOOTSTRAP=0 disable all automatic bootstrap config
OMNIUS_FABRIC_RELAYS=ws://host:7766,ws://host2:7766
OMNIUS_FABRIC_NATS=nats://host:4222,nats://host2:4222
OMNIUS_FABRIC_NATS_SUBJECT=omnius.fabric.routesLegacy aliases are also accepted:
FABRIC_START_ON_INSTALL
FABRIC_DEFAULT_NATS
FABRIC_BOOTSTRAP
FABRIC_RELAYS
FABRIC_NATS
FABRIC_NATS_SUBJECTPersisted configuration
Beyond env vars and one-off CLI flags, the daemon persists preferences to
~/.omnius-fabric/<data>/settings.json (mode 0600). The Settings tab in the
web console reads and writes these; a CLI flag always overrides the persisted
value for that one invocation. Changes apply on the next daemon restart —
libp2p services are configured at startup and can't be hot-swapped.
| Setting | Default | Effect |
|---|---|---|
| nickname | "" | Public node display name advertised in signed addresses and route records |
| upnpEnabled | true | Attempt UPnP/NAT-PMP port mapping |
| relayMode | both | Circuit-relay role: client / server / both |
| relayCapacity | 16 | Max concurrent relay reservations |
| autoRelayOnReachable | true | Auto-host relay once publicly reachable |
| torEnabled | false | Spawn a Tor onion hidden service |
| useDefaultNats | true | Merge the public NATS rendezvous for bootstrap |
| natsServers | [] | Operator NATS servers (added on top of defaults) |
| nknEnabled | true | Start the NKN overlay and advertise an nkn route hint when connected |
| nknIdentifier | omnius | Prefix for this node's NKN address (<identifier>.<pubkey-hex>) |
| nknNumSubClients | 4 | NKN MultiClient sub-client count for overlay reliability |
| nknRpcServers | [] | Explicit NKN RPC servers; empty uses NKN mainnet defaults |
| bootstrapEnabled | true | Load relay/NATS/manifest bootstrap on start |
| port | 7766 | libp2p listen port |
| remoteShellEnabled | false | Master switch for administrative passthrough (off by default) |
Acceptance tests
npm run test:localThis runs:
node dist/cli.js test-local
node dist/cli.js test-triad
node dist/cli.js test-bootstrap-config
node dist/cli.js test-connecttest-local proves: libp2p node starts, KadDHT reports status, multiaddrs are announced.
test-triad proves: three libp2p nodes start and verify all are running.
test-bootstrap-config proves: first-run bootstrap discovery loads by default and can be disabled.
test-connect proves: invite creation, session key derivation, and ratchet encryption round-trip verified.
Additional local validation used for the web media path starts two runtimes, requests and approves a Tier 2 relationship, stages a file through the chunk-upload API, sends a file offer, accepts it, pushes the chunked media stream, verifies reassembly, and downloads the received file through the control API.
Recent TUI and NKN validation:
npm run build
node dist/cli.js --help
node dist/cli.js start --help
node dist/cli.js start --foreground --port 0 --host 127.0.0.1 --no-upnp --no-bootstrap --no-auto-updatenpm run build type-checks and compiles the terminal UI, NKN transport, and
runtime/status/settings integration. The local acceptance tests pass
nkn: false internally for deterministic offline runs; live NKN validation is
a daemon smoke check. With a default start, fabric status should include
nkn.enabled, nkn.connected, nkn.address, and nkn.identifier; when the
overlay connects before the first address publish, fabric inspect "$(fabric
address)" shows a route with "type": "nkn". fabric tui validates the
terminal console against the same token-gated control API used by the browser
console.
Security model
Omnius Fabric uses two layers of encryption:
Layer 1: Transport (libp2p Noise)
- Noise XX handshake with Curve25519 key agreement
- AEAD (AES-256-GCM or ChaCha20-Poly1305) per-packet encryption
- Authenticates both sides of every connection
- Relay nodes see only Noise-encrypted streams — they cannot inspect payloads
Layer 2: Application (Session Ratchet)
- X25519 static + ephemeral key agreement
- HKDF-SHA512 key derivation from two Diffie-Hellman shared secrets
- AES-256-GCM per-message encryption with rolling ratchet keys
- Forward secrecy: chain key is overwritten after each send
Route records
- Ed25519 signatures over canonical JSON bodies
- Signed
fabric://address tickets - TTL/replay-expiry fields (default 24h)
Trust and media
- T1/T2/T3 upgrades are double-signed by both peers and expire.
- T2 grants include
media:share; media offers still require recipient acceptance before any bytes are transferred. - Media bytes travel over a dedicated libp2p stream only after acceptance, are checked against the offered SHA-256 digest, and are served to the local browser through the token-gated loopback API.
- T3 grants include administrative scopes such as
shell:exec, but privileged features remain disabled unless their own local settings and authentication checks also pass.
Primitives
- Ed25519 identity/signatures
- X25519 key agreement
- HKDF-SHA512 key derivation
- double AES-256-GCM payload encryption (Noise transport + application ratchet)
- signed route records + invite tokens
- double-signed trust grants and revocations
- SHA-256 verification for accepted media transfers
- TTL/replay-expiry fields
Rolling session forward secrecy
Session secret = HKDF-SHA512(static×static ‖ ephem×ephem, salt = invite PSK, info = sessionId)
Per-direction chain key K0 = HKDF(secret, info = "chain:v1:role")
For message n: (msgKey_n, K_{n+1}) = HKDF(K_n, info = "ratchet:v1")
K_n is overwritten with K_{n+1} after each send — the old key is gone.
Replay rejected by counter monotonicity.Relays can see routing metadata such as connection timing, approximate sizes, and destination peer IDs. They cannot decrypt Noise transport payloads (layer 1), and they cannot derive session secrets (layer 2) without one side's static or ephemeral private key.
Guarantee boundary
Valid claim:
If a recipient's fabric:// address contains at least one route reachable by the sender,
the sender can deliver an end-to-end encrypted message to that recipient.Fabric claim:
As more users run the package, reachable nodes automatically become relay nodes,
private nodes reserve relay paths, and KadDHT/GossipSub route propagation compounds
peer knowledge across the network.Invalid claim:
Two isolated devices behind arbitrary hostile NATs can connect with zero reachable third-party path.A cooperative reachable relay path is required for hostile NAT/CGNAT/corporate networks. Circuit Relay v2 + DCUtR handles most consumer NAT scenarios.
Commands
fabric start [--port N] [--host HOST] [--public-url URL]
[--relay URLS] [--nats SERVERS] [--nats-subject SUBJECT]
[--relay-mode client|server|both] [--relay-capacity N]
[--daemon] [--foreground] [--daemon-child]
[--no-server] [--no-upnp] [--no-tor] [--no-bootstrap]
[--no-auto-update] [--no-nkn]
fabric stop [--force]
fabric address
fabric status
fabric doctor
fabric dht
fabric peers
fabric ui [--print]
fabric tui
fabric connect [--ttl-minutes N] [--label TEXT] [--no-qr]
fabric join <fabric+invite://...>
fabric send-session <sessionId> <message>
fabric sessions
fabric send <fabric://address|peerId> <message>
fabric inspect <fabric://address>
fabric autostart enable|disable|status
fabric test-local
fabric test-triad
fabric test-bootstrap-config
fabric test-connectMedia transfer is currently exposed through the web console and local control API rather than a dedicated CLI command.
