open source · fail-closed on purpose · receipts for everything

the SSH bastion
that keeps the receipts.

Omni-SAG is the bouncer for your servers. Every login checked, every tunnel authorized, every byte accounted for — with a tamper-evident paper trail you can verify offline, even with the gateway switched off.

Go · AD+MFA · single-dialer authz · Merkle-chained evidence · recording · ICAP scan · CyberArk · four-eyes approvals · API + TUI

Grab it on GitHub → Run the demo MIT · v1 · PRs welcome
two users, one tunnel — only one gets through
ssh -L 5432:db1.lab.local:5432 alice@gateway     # alice ∈ dba
✔ ALLOWED · role=dba · tunnel open · evidence: tunnel_decision allow=true

ssh -L 5432:db1.lab.local:5432 bob@gateway       # bob ∉ dba
✘ administratively prohibited · evidence: tunnel_decision allow=false

omni-verify -bundle ./evidence -pubkey $KEY
records: 4 · segments: 4 · checkpoints: 4
PASS — evidence bundle is intact and authentic.
# flip one byte…
FAIL — TAMPER: record hash mismatch. loudly.
// what even is this

A gateway that assumes everyone's a little bit sus.

Point your team at the bastion instead of straight at your boxes. It authenticates them against AD (with MFA), decides what they're allowed to reach, and writes down everything in a way nobody can quietly edit later. Default answer is always no — access is something you earn, per connection.

🧍‍♂️

You are the bouncer

One front door. AD + MFA at the threshold, a per-target allow-list behind it. No badge, no entry.

🧾

Receipts for everything

A hash-chained, Merkle-rooted, Ed25519-signed evidence log. Tamper a byte and the offline verifier screams.

🚪

Fails shut, never open

Vault down? Scanner down? Directory down? The answer is “no.” A 29-row fail-closed matrix says so, with tests.

// the toolbox

Batteries included. Paranoia standard.

🔐 AD + MFA

LDAPS against Active Directory, then RADIUS MS-CHAPv2 (never PAP). Push approve → in; deny → out.

authn

🎯 One dialer, one rule

Only one place opens sockets to targets — authorized first, with an SSRF guard that blocks the metadata IP & rebinding.

dialer

🧾 Tamper-evident evidence

Per-emitter epoch+seq, Merkle chain, signed checkpoints, WORM archive. Verify offline with omni-verify.

evidence

🎥 Session recording

Asciicast recording of shells (replay in the TUI). Forwarding refused on “must-record” targets, so nothing sneaks past.

recording

🦠 Content inspection

SFTP uploads streamed through ICAP (AV/DLP). Blocked or unscannable? Quarantined to WORM, transfer refused.

inspect

🔑 CyberArk injection

Fetch a target secret just-in-time; the user never sees it. Vault down = session refused. No silent downgrade, ever.

credential

👀 Four-eyes approvals

Crown-jewel targets block until a second human approves — never yourself, and now scoped to your own AD group (live LDAP check). Approved SFTP uploads sit in WORM quarantine until the uploader pulls them back through a time-boxed, identity-checked /releases directory.

approval

🛰️ API + TUI

OpenAPI control plane + a Bubble Tea terminal UI. Kill the API? SSH keeps humming — it's genuinely out-of-band.

control-plane

📦 Ships nicely

UBI9 non-root image, Helm chart (restricted-v2), graceful drain, Prometheus metrics, SBOM + cosign, FIPS mode.

ops

🔍 Tunnel protocol ID

Fingerprint the opening bytes of every -L/-D tunnel — log the protocol actually riding it, or enforce an expect_protocol allow-list and kill mismatches before a byte lands.

tunnel

📡 Event export / SIEM

Stream every evidence event to your SIEM in real time — json/ecs/cef over file/syslog/http, best-effort, and a dead SIEM never stalls a session.

export

📈 OpenTelemetry

Opt-in OTLP traces correlated to evidence IDs, plus optional metrics/logs that coexist with Prometheus. Disabled = zero cost on the data path.

otel
// how it was built

13 thin slices. Each one demoable on its own.

No five-half-finished-branches energy. Every slice ends with something you can run in front of a person — and every slice went through an adversarial review gate before moving on. A few of those reviews caught real bugs. That's the point.

13
slices, 0 → GA
18
focused packages
6
real bugs caught by review
29
fail-closed cases, tested
SLICE 01

Walking skeleton

✔ live

SSH → LDAPS → policy → dialer → evidence. Two AD users, one tunnel, only one gets through.

SLICE 02–03

MFA + the evidence chain

✔ live

RADIUS MS-CHAPv2, then the real deal: epoch+seq, Merkle, signed checkpoints, WORM, offline verifier.

SLICE 04–05

SFTP, recording & ICAP scanning

Record sessions, refuse forwarding on must-record targets, stream uploads through AV/DLP, quarantine the nasties.

SLICE 06–08

CyberArk, the API & four-eyes

Just-in-time secret injection (no downgrade), an out-of-band control plane, and durable second-human approvals.

SLICE 09–10

TUI + packaging

A terminal cockpit (with a “why can Alice reach X?” tracer) plus UBI9 images, Helm, drain, and metrics.

SLICE 11–12

Hardening + a full audit

Fuzzers, property tests, SSRF guard, brute-force lockout, FIPS, chaos matrix — then a 12-auditor final sweep.

POST-GA

Real targets + group-scoped four-eyes

✔ v1.0.1

The second SSH leg to the real target shipped (shell + SFTP, no more stand-ins), plus group-scoped approvals and pull-based release downloads. Real bugs caught along the way — see PR #3 and PR #4.

POST-GA

The v1.1 → v1.7 arc

✔ v1.7.0

CIDR policy rules, nested AD group resolution, a +pcode role selector & tunnel-keeper window, legacy scp -O, real-time event export / SIEM, tunnel protocol identification (observe + enforce), OpenTelemetry (OTLP) export, and FIPS-approved TLS routed through every listener — each landed as its own reviewed, adversarially-checked slice.

// 60 seconds to “whoa”

Clone it. Watch the bouncer work.

Spins up a throwaway lab — Samba AD, MinIO, FreeRADIUS — then two SSH users where only the DBA gets the tunnel, with both attempts in the evidence log.

bash
git clone https://github.com/rupivbluegreen/omni-sag
cd omni-sag

make lab-up        # samba-AD + MinIO + FreeRADIUS
make lab-seed      # create alice (dba) and bob (not)
make binaries      # build omni-sag, omnisag-ctl, omni-verify

./bin/omni-sag_linux_amd64 -config deploy/compose/config.example.yaml
# then: ssh -L … alice@gw  → allowed
#       ssh -L … bob@gw    → administratively prohibited
# and:  ./bin/omni-verify_linux_amd64 -bundle ./evidence
not a mockup — an actual recorded run
// under the hood

Two planes. The data plane never phones the control plane.

A modular monolith with CI-enforced package boundaries. Kill the API and existing SSH sessions keep running; new ones still connect. That's not a slogan — it's a test.

client ──ssh──▶ [ session ] ──▶ authn + ratelimit ──▶ policy ──▶ [ dialer ] ──▶ target │ │ (the ONLY socket to a target, │ │ authorized + SSRF-guarded first) ▼ ▼ [ evidence ] ◀── hash-chain · Merkle · Ed25519 · WORM ──▶ omni-verify (offline) ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ out-of-band ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ operator ──mTLS──▶ [ api ] ──▶ sessions · policy · approvals · supervision (never imported by the data path)omnisag-ctl / TUI

🛠️ Built in the open — come tinker.

Open source, for the community, and for anyone who thinks access should come with receipts. Issues, ideas, and PRs are all welcome. No enterprise sales call required.