Skip to content

Security Contract

Assertions

SEC-001 - One master performs at most one authentication

Contract: One OpenSSHMaster instance MUST start at most one deliberate SSH authentication. A second start, readiness loss, or failed master MUST NOT reconnect or spawn another authentication attempt.

Evidence:

SEC-002 - Secondary channels are mux-only with no fallback

Contract: Every secondary channel MUST require the existing private control socket, disable new authentication methods, set a failing proxy fallback, and keep authority and remote program values as separate process arguments.

Evidence:

SEC-003 - Forwarding and configured commands remain disabled

Contract: Initial and secondary SSH invocations MUST disable forwarding, agent sharing, X11, configured local commands, and configured remote commands. The private control directory MUST use mode 0700 and cleanup MUST remove only owned runtime state. Native configuration MUST NOT background owned SSH processes, enable tunnel forwarding, or discard a mux ownership channel's stdin. Control path selection MUST reserve space for OpenSSH's 17-byte temporary mux suffix, and literal percent tokens MUST NOT redirect the owned socket path.

Evidence:

SEC-004 - Session recovery is narrow and initial-only

Contract: Linux session recovery MUST probe bounded owned user-session state, MUST recover only the documented allowlist, MUST preserve inherited non-empty values, MUST reject invalid runtime directories, and MUST pass the result only to the initial master subprocess without mutating process state. JSON or legacy POSIX-quoted values MUST be decoded only as data; recovered runtime directories MUST themselves be validated, not only the probe path. Timeout and cancellation MUST reap owned probe groups even if their leaders exit before descendants holding output pipes.

Evidence:

SEC-005 - Diagnostics are continuously drained and bounded

Contract: Master stderr and supervised child output MUST be drained without blocking. Master retention MUST use a fixed bound; supervised child retention MUST remain bounded under the documented positive tail_bytes precondition. Public SSH errors MUST NOT expose raw stderr, executable arguments, identity files, or private paths. Control-operation output MUST also be bounded and continuously drained. Control checks MUST obey the startup deadline, and cancellation during local process creation or a control check MUST NOT abandon the subprocess.

Evidence:

SEC-006 - Supervision cleans only its owned process group

Contract: OwnedRemoteProcess MUST encode argv as data, start one new remote process group, and terminate only that group after owner EOF, malformed heartbeat, lease expiry, cancelled startup, or teardown. Cleanup MUST include surviving members even when the group leader exits first or another member ignores SIGTERM. Natural child exit and termination signals MUST be observed independently of the heartbeat interval; a completed child MUST NOT wait for the next heartbeat.

Evidence:

SEC-007 - Real OpenSSH acceptance uses isolated ephemeral identity

Contract: Linux CI MUST exercise the actual library against an ephemeral loopback sshd with newly generated keys, isolated configuration and known hosts, no existing identity or agent, exactly one accepted authentication, one master connection, mux-only commands, no fallback after loss, bounded output, and selective owned-group cleanup. The server MUST run as a non-root user in its own container PID namespace with a read-only root, dropped capabilities, no-new-privileges and a dynamically assigned port bound only to 127.0.0.1 on the host. The native client MUST remain on the host; host sshd MUST NOT be required or installed by the gate. The same Make-owned test image and fixture MUST serve local and CI acceptance. Missing required tools or a failed server MUST fail the gate, not silently skip it. Container startup, cancellation and scenario failure MUST reclaim the owned container; remote process checks and signals MUST execute only inside its PID namespace. PID 1 and orphan reaping MUST be supplied by the pinned server image. Engine-provided init injection MUST be disabled, including when enabled in host engine defaults; host catatonit, tini or docker-init MUST NOT be required by the live gates. Engine metadata MUST be checked before attempting container creation. Podman MUST be rootless and use direct per-container pasta networking; it MUST NOT be forced through the shared rootless bridge namespace. Docker MUST retain its native bridge network. Unknown engine metadata, unavailable pasta, startup and cleanup failures MUST fail without a fallback network or security relaxation. The gate MUST NOT change host AppArmor profiles or require sudo.

Evidence:

SEC-008 - Hardware FIDO acceptance is explicit and operator-assisted

Contract: make test-fido MUST require an explicitly supplied existing OpenSSH security-key identity and its public key. It MUST use only a temporary loopback server, newly generated host key, isolated known hosts and authorization, and the same one-auth, mux, no-fallback and cleanup checks as automated acceptance. It MUST NOT generate or modify a hardware credential or request PINs through the library API. Automatic CI, make ci, and make test-acceptance MUST NOT select hardware tests; the operator MUST approve native prompts and touch the key. Ordinary and FIDO gates MUST reuse one live scenario and the same container server. Only the selected public authorization key MAY be passed to that server; client private identities, agent sockets, hardware devices and the checkout MUST NOT be mounted or copied into the container. Native prompt and session routing MUST remain available to the host client; D-Bus cleanup MUST apply only to engine subprocesses.

Evidence: