← View All Cases Project RasnuOS · Security

Everyone's talking about AI in DevOps.
Nobody's talking about your SSH keys.

The Conference Scene

It's another event. The kind with lanyard badges, lukewarm coffee, and a stage that costs more than the hotel rooms. A speaker — impressive slides, strong voice — kicks off with something like "AI is going to transform DevOps." The crowd nods. The next speaker says the exact same thing, with slightly different adjectives.

And honestly? They're right. AI is genuinely changing the game in infrastructure automation. We've watched it happen, we've listened carefully, and we've taken notes. What we heard was real — and exciting.

"Everyone at every event was talking about what AI can do. What we never heard — not once — was what AI can access along the way."

This document exists because of that silence. Because we kept waiting for the slide that says "wait, but where do your credentials go?" — and it never came.

What Everyone Said AI Can Do for DevOps

To be fair, the list is genuinely impressive. Here's what we heard across multiple talks and panels:

Automated Pipeline Generation — You type "deploy my Node.js app to Hetzner on every push to main" and the YAML appears. Admittedly cool.
Anomaly Detection & Self-Healing — AI watching your metrics and auto-restarting services. "Your Node.js process crashed at 3 AM and nobody had to wake up." Every on-call engineer's dream.
Intelligent Scaling Decisions — "Just say 'add two Hetzner CX21 workers to the load balancer' and it happens." Right. And who has the Hetzner API key?
Zero-Downtime Cloud Migrations — "Move your PostgreSQL from AWS to Hetzner and the AI handles it." That involves SSH access to both servers, database credentials, DNS tokens. Great for productivity. Terrifying for security.

The problem isn't what AI can do. The problem is what AI needs access to in order to do it.

The Question Nobody Asked

Every demo showed the output. The deployed app. The generated pipeline. The self-healed service. Beautiful. Smooth. Impressive. What they didn't show: the input.

"To deploy your app, the AI needs to reach your server. To reach your server, it needs credentials. Where do those credentials go?"

If you've used any cloud-based AI DevOps tool that actually runs things on your server, you've accepted one of two realities:

Reality A

The AI uses SSH credentials you provided

In this model, the platform now holds a reusable path into your production machine. Every command the AI executes passes through infrastructure you don't control.

Reality B

The AI uses cloud API keys stored in their system

Terraform for AWS, Hetzner Cloud API, Cloudflare DNS — all sitting in some third-party platform's database. If they get breached, so do you.

In architectures where the AI platform brokers SSH or API access, credentials and commands pass through infrastructure you don't own, can't audit, and can't verify. Every command, every terraform apply — routed through third-party servers. Whether that's the case for any specific tool depends on its architecture, but it's the default model for most cloud-hosted AI DevOps platforms.

The Real Threat Model

CONVENTIONAL AI-DEVOPS THREAT MODELDeveloper+ SSH Key+ AWS Token+ DB Passwordsends credentialsAI Platform CloudCredentials stored hereCommands routed hereSecrets transit hereunknown region / encryptionSSH into Port 22Your Serverprod-api-01PORT 22 OPEN — ATTACK SURFACEPlatform Breach =ALL secrets exposedStolen SSH Key =permanent access+ insider threat · rogue employee · MITM · insecure logging · subpoena
01

Credential Transit Exposure

Every command traveling from AI platform to your machine — potentially carrying secrets — is observable in transit if not end-to-end verified.

DOCKER_REGISTRY_PASSWORD=s3cr3t123 docker login registry.example.com # This exact string exists in the AI platform's execution logs.
02

Platform Breach = Your Breach

A breach of their system is a breach of your infrastructure. Not eventually. Immediately. This isn't hypothetical — it's SolarWinds logic applied to AI tooling.

03

The Open Port Is the Permanent Door

Port 22 doesn't know which connections come from your AI tool and which come from an attacker. If it's open, it's open to everyone.

$ nmap -sV your-prod-server.example.com22/tcp open ssh OpenSSH 9.3
04

No Audit Trail You Control

When the AI runs a command on your server, who logs it? The AI platform does. In their system. Your forensic record is whatever they decide to show you.

05

Secret Logging is Silent and Universal

Does the AI strip the password from mysql -padmin123 -u root production before writing to the log? Maybe. Probably? Most systems don't specify.

How RasnuOS Was Built Around This Problem

RasnuOS started from a simple but uncompromising architectural question: What if the AI system could manage your infrastructure without ever needing SSH access, without ever storing your credentials, and without your secrets ever leaving your server?

RASNUOS ZERO-TRUST ARCHITECTUREos.rasnu.comControl Plane / AI AutoPilotNo credentials storedSecret values never stored or logged hereprod-api-01Go Agent · outbound-onlyPORT 22 CLOSEDdb-primaryGo Agent · outbound-onlyPORT 22 CLOSEDoutbound mTLSoutbound mTLSNo inbound connections · Secrets in-RAM only · never logged · auto-wiped

Turn the connection model inside out. Your servers reach out to the control plane — the control plane never reaches in. A compromise of the RasnuOS control plane does not give an attacker an inbound network path to your servers — because there is no persistent listener to exploit. Your servers remain unreachable through the network, even if the platform were compromised, as long as your firewall blocks unsolicited inbound traffic.

0
Persistent inbound ports required
0
Credentials stored or logged on our servers
0
Secret values that ever touch our disk or logs

The Go Binary That Changes Everything

It's not an AI agent. It doesn't reason, decide, or learn. It's a smart, deterministic, security-hardened Go daemon — a courier that carries verified instructions from the control plane to your OS and sends verified results back. Fully open-source. Read every line before installing.

What it is NOT
  • An AI that decides what to do
  • A black-box binary with root access
  • A server accepting inbound connections
  • Something that phones home your secrets
What it IS
  • A deterministic, auditable Go daemon
  • Fully open-source — read every line
  • Outbound-only — no listening ports
  • RBAC-bounded with concurrency limiter

7 Security Layers

01
Outbound-Only WebSocket

The agent opens a WebSocket on startup and holds it. It never opens a TCP listener. Your firewall can block all inbound traffic — the agent still works perfectly.

02
Per-Channel Bearer Authentication

Every WebSocket channel subscription requires a server-signed auth token. No unauthorized entity can push commands to your agent.

03
Secrets Never in Command-Line Arguments

The agent token is read from a chmod 600 env file, not CLI flags. On Linux, ps aux exposes every CLI argument to every user — this design prevents that. Environment variables are not exposed in the argument list, which is the critical protection.

rasnuos-agent --token=eyJhbGci... # dangerous — visible in ps aux argsrasnuos-agent # safe — token in /etc/rasnuos/agent.env (chmod 600)
04
TLS Certificate Pinning (SHA-256 SPKI)

When a pin is configured, the agent applies SHA-256 SPKI pinning to every outbound connection. Even a MITM attack with a valid CA-signed certificate fails.

05
Concurrency Limit as DDoS Protection

A buffered channel of size 10 acts as a semaphore. After 10 concurrent slots are taken, further commands are rejected — not queued to exhaust memory.

06
Input Validation Against Command Injection

Service names validated against ^[a-zA-Z0-9._@\-]+$. PIDs must match ^[0-9]+$. Neither is interpolated into a shell string. nginx; rm -rf / rejected before any system call.

07
Signed Self-Updates — Fail Closed

New binary must be verified against an embedded Ed25519 public key. If verification fails, update aborts with a "SECURITY ALERT". An unsigned binary is never installed — even from our own servers.

The In-RAM Secrets Engine

"Your secrets are never persisted or logged on our servers. They are delivered transiently to your agent over an encrypted channel. Here is precisely how that works."

The mechanism is an in-RAM secret store (secret_store.go) with a single-use, auto-expiring, zero-persistence model:

01

You store a secret in the dashboard

Encrypted at rest via HashiCorp Vault Transit Engine (AES-256-GCM). RasnuOS engineers cannot read it.

02

Vault decrypts it and delivers it to the agent over the authenticated channel

Dispatched as a [BACKEND_INTENT:inject_secret] command over the authenticated, TLS-encrypted WebSocket, with a one-time scope token. The secret value travels over this encrypted channel to the agent's RAM. It is never written to the control plane's disk, never logged, and the control plane holds it only transiently during dispatch.

03

The agent stores it in RAM only — never on disk

A sync.Map protected by a mutex holds the secret with a 5-minute TTL. Nothing is written to the filesystem.

04

The command runs with the secret as an OS env var

The secret is passed to exec.Command as an environment variable — never in a shell string, never interpolated, never logged.

05

The secret is wiped immediately after first use

retrieveAndWipeSecrets() deletes the entry the moment it's read. If unused, auto-expires after 5 minutes. Zero lingering.

secret_store.go — actual execution log output
[VAULT] Stored 2 secret(s) for token scope. TTL: 5m0s
[VAULT] Keys: [DOCKER_REGISTRY_PASSWORD AWS_SECRET_KEY]
# Only key NAMES logged. Values NEVER written anywhere.
[EXEC] Running: docker login registry.example.com
[VAULT] Retrieved and wiped 2 secret(s). Single-use retrieval enforced.
Raw command
mysql -padmin123 -u root prod MYSQL_PASSWORD=s3cr3t myapp
What gets logged
mysql -p***REDACTED*** -u root prod MYSQL_PASSWORD=***REDACTED*** myapp

Append-Only Audit Trail — On Your Server

With RasnuOS, every action taken by the agent is written to a local audit.jsonl file — a JSON Lines append-only log on your server. Yours to own, ship to your SIEM, S3, or ELK stack. The agent only ever opens the file with os.O_APPEND — it never truncates or overwrites. For cryptographic immutability, forward the log to a WORM-capable storage system of your choice.

# /var/log/rasnuos/audit.jsonl{"timestamp":"2026-09-30T03:14:01Z","action":"execute_command","command":"systemctl restart api-worker","success":true}{"timestamp":"2026-09-30T04:00:00Z","action":"execute_command","command":"mysql -p***REDACTED*** backup.sql","run_as_user":"deploy","success":true}

Migration Security — In Depth

Every AI DevOps demo shows migration as a magical one-click operation. The part they skip is what's actually happening at the network level — which server is listening, who can connect to it, how credentials are passed, and whether any data transits a third-party system. Let's go through this in detail.

RasnuOS implements three distinct migration mechanisms, each purpose-built for a different use case, and all sharing the same core security model: ephemeral certificates, cryptographic fingerprint verification, token-based pre-authentication, and zero data through the control plane.

Mode 1 — File Migration (rsync over mTLS)

Used when moving files, directories, or database dumps between two servers. The transport is rsync, but running through a mutually authenticated TLS proxy that the receiver generates on demand — not a port left open waiting for connections.

01

Receiver generates an ECDSA P-256 TLS certificate in RAM

Created via crypto/rand using the standard Go crypto library. Valid for exactly 24 hours with a 1-hour backdated notBefore to tolerate clock skew. The private key never leaves the receiver's process memory.

// migration_engine.go — generateSelfSignedCert()priv, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)notBefore := time.Now().Add(-1 * time.Hour) // clock skew tolerancenotAfter := time.Now().Add(24 * time.Hour) // 24h hard expiryderBytes, _ := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)// Only fingerprint is shared. Private key never leaves this process.
02

Receiver computes SHA-256 fingerprint of the raw DER certificate

The fingerprint is hex.EncodeToString(sha256.Sum256(cert.Certificate[0])) — the hash of the raw bytes of the certificate itself, not the public key alone. This is sent to the dashboard, which forwards it to the sender. RasnuOS only ever sees the hash — not the cert, not the key.

03

Receiver generates a 32-char cryptographically random rsync password

16 bytes from crypto/rand, hex-encoded to 32 chars. Written to a chmod 600 temp file referenced by the rsyncd config. This file path is never logged.

// migration_engine.gorandBytes := make([]byte, 16)rand.Read(randBytes)rsyncPassword := hex.EncodeToString(randBytes) // 32-char hex string// Written to secrets file — mode 0600 (root-readable only)os.WriteFile(secretsFile, []byte("rasnuos:"+rsyncPassword+"\n"), 0600)
04

Sender verifies the fingerprint before opening any data stream

The sender sets InsecureSkipVerify: true (to bypass CA validation of a self-signed cert) but installs a custom VerifyPeerCertificate callback that computes the SHA-256 hash of the raw cert and compares it to the expected fingerprint character-by-character. If they don't match, the connection is immediately rejected.

// migration_engine.go — executeMigrationSender() tlsConfig := &tls.Config{ InsecureSkipVerify: true, // Skip CA chain (self-signed) VerifyPeerCertificate: func(rawCerts [][]byte, _ [][]*x509.Certificate) error { hash := sha256.Sum256(rawCerts[0])certFingerprint := hex.EncodeToString(hash[:]) if certFingerprint != fingerprint { return fmt.Errorf("fingerprint mismatch! Expected %s, got %s", fingerprint, certFingerprint) } return nil // ✓ Verified }, }
05

rsync runs with the password as an OS env var — not a CLI argument

If the password were passed as --password=xyz, it would appear in ps aux — visible to any user on the sender machine who can read the process argument list. Instead, it's set as RSYNC_PASSWORD in the child process's environment, which is not exposed through the command-line argument list.

// migration_engine.go cmd := execCommand("rsync", rsyncArgs...) cmd.Env = append(os.Environ(), fmt.Sprintf("RSYNC_PASSWORD=%s", rsyncPassword))// Password NOT in rsyncArgs — not visible in ps aux// rsync reads it from its own process environment instead
06

Up to 5 retry attempts — then hard 4-hour timeout kills the receiver

The sender retries rsync up to 5 times (2-second gap between attempts) to handle transient network issues. On the receiver side, a goroutine fires after 4 hours to call shutdownMigrationServer() — which kills the rsync daemon, closes the TLS listener, removes the secrets file, and closes the firewall hole. Nothing persists.

// migration_engine.go — cleanup on shutdownms.listener.Close()os_adapter.Current().CloseFirewallHole(ms.port)ms.cmd.Process.Kill() // rsync daemonos.Remove(secretsFile) // chmod 600 file — goneos.Remove(configFile) os.Remove(lockFile)

Mode 2 — P2P Stream Migration

Used for streaming arbitrary data between servers — for example, piping a live pg_dump directly into psql on the destination without touching the filesystem at all. This mode adds an extra layer: token-based pre-authentication on top of the TLS handshake.

01

Same ephemeral cert + fingerprint flow as Mode 1

ECDSA P-256, generated in RAM, fingerprint shared via the dashboard. Receiver side starts a TLS listener. Sender verifies fingerprint before sending any data.

02

Sender must send a 64-byte authentication token before the receiver will proceed

After TLS handshake succeeds, the receiver reads exactly 64 bytes and compares them to the expected token. If they don't match — or if the sender times out within 10 seconds — the connection is dropped silently. No error response, no data exchange, nothing.

// migration_engine.go — prepareP2pReceiver() tokenBuf := make([]byte, 64) conn.SetReadDeadline(time.Now().Add(10 * time.Second)) io.ReadFull(conn, tokenBuf) conn.SetReadDeadline(time.Time{}) // reset deadline after auth if string(tokenBuf) != token { log.Printf("P2P auth failed: Invalid token received")return // Connection dropped. No response sent. }
03

Data streams directly — stdin → TLS conn → stdout — never buffered to disk

The send command's stdout is piped directly into the TLS connection. The receive command reads from the same connection as stdin. A progressWriter wraps the stream to emit progress updates back to the UI. At no point is data written to a temp file or buffered anywhere on the RasnuOS platform.

04

Only one connection is accepted — then the listener closes

The P2P receiver's goroutine calls shutdownMigrationServer(token) via a defer after exactly one connection is handled. The port is punched through the firewall, used once, then the hole is closed. No persistent listener, no re-use window.

Mode 3 — Port Tunnel (yamux over mTLS)

Used to expose a local service on one server through another server's public IP — for example, testing a local dev environment through a Hetzner node's public address. The security model is the same certificate + fingerprint + 64-byte token approach, but adds a multiplexed stream layer via yamux.

01

Host generates cert + fingerprint, starts two listeners

A private mTLS control listener on a random port (for the agent to connect to) and a public listener on the requested port (for incoming traffic). Both ports are punched through the local firewall. Both are closed and holes are filled on shutdown.

02

Client agent connects to the control port — fingerprint verified, token sent

Same pattern: TLS dial with custom VerifyPeerCertificate callback, then the 64-byte token is written within a 10-second deadline. If token verification fails on the host side, the connection is logged and dropped — the loop continues accepting new attempts.

// tunnel_engine.go — prepareTunnelHost() if string(tokenBuf) != token { log.Printf("UNAUTHORIZED mTLS TUNNEL CONNECTION ATTEMPT from %s", conn.RemoteAddr()) conn.Close() continue // Loop — keep listening }
03

yamux multiplexes all public traffic over the single authenticated control channel

Once authenticated, the control connection is promoted to a yamux session. Every public connection that arrives on the host's listener is handled by opening a new yamux stream on that session — which the client agent receives and proxies to the local port. Multiple concurrent connections are handled cleanly without new TLS handshakes.

// tunnel_engine.go — host sidesession, _ = yamux.Server(conn, yamux.DefaultConfig())// For every public connection that arrives: stream, _ := ts.session.Open() // opens a yamux streamproxyConn(pubConn, stream) // bidirectional proxy// tunnel_engine.go — client sidesession, _ = yamux.Client(conn, yamux.DefaultConfig()) stream, _ := session.Accept() // receives stream from host localConn, _ := net.Dial("tcp", "127.0.0.1:localPort") proxyConn(stream, localConn) // forwards to local service

All Three Modes — Security Properties at a Glance

PropertyFile (rsync)P2P StreamPort Tunnel
Ephemeral TLS cert✓✓✓
SHA-256 fingerprint verification✓✓✓
64-byte token pre-auth—✓✓
Password via env var (not CLI flag)✓——
Secrets file chmod 600✓——
Data through RasnuOS serversNeverNeverNever
Auto-cleanup on completion✓✓✓
Hard 4-hour timeout✓✓—
Firewall hole punch + close✓✓✓
Audit log entry✓✓✓

What We Built. And Why.

The AI DevOps revolution is real. But capabilities require access — and access is where the conversation has been disturbingly thin. RasnuOS was built around a different bet: what if you could radically minimize the trust you place in the AI platform?

The RasnuOS Security Guarantee

  • ✓No persistent inbound ports. Port 22 not required for normal agent operation. The agent calls out — nothing calls in during regular operation. Temporary ports are opened and immediately closed for migrations only.
  • ✓Credentials encrypted at rest via HashiCorp Vault Transit. Plaintext values are not exposed to the application database, logs, or persistent storage. Secret values are delivered transiently over the encrypted channel and never persisted on our infrastructure.
  • ✓Secret values never stored, logged, or persisted through us. In-RAM injection, single-use, auto-wiped. The control plane dispatches secrets over the encrypted channel — they never touch our disk or logs.
  • ✓No migration data through our servers. Direct tunnel, fingerprint-verified, encrypted end-to-end. RasnuOS coordinates — it does not relay data.
  • ✓No black boxes. Fully open-source. Compile it yourself. Read every line.
  • ✓Your audit trail, on your server. Append-only JSONL. You own the forensics. Forward to your SIEM for cryptographic chaining if required.
  • ✓Signed updates, fail closed. Ed25519 verified. Unsigned binaries never installed in production.
"AI can change DevOps. But it shouldn't change your threat model for the worse. The right answer isn't to avoid AI in infrastructure — it's to build the architecture so you don't have to trust the AI system with your keys."