Closes #5.
What was wrong
Serve accepted every connection and started a goroutine and a process for each
session channel, with no handshake deadline. A client that opened a socket and
said nothing held a connection slot, a goroutine and a file descriptor
indefinitely; a client that opened channels held a goroutine each. The host key
was only hardened by vole serve on the way in, so the SSH package on its own
would load a 0644 key.
What this does
- Handshake deadline (
ssh_handshake_timeout_seconds, default 20) set on the raw connection beforeNewServerConnand cleared afterwards, so a clone or a push is not cut. Test proves a session that outlives the deadline still works. - Connection cap (
ssh_max_connections, default 64): one token per open connection; past it the connection is closed before any handshake, goroutine or process is spent on it, and the accept loop keeps running. The full-listener warning is throttled to once per 10 s so a flood cannot fill the disk. - Channel cap: eight session channels per connection, taken from a per-connection token channel, so a flood is rejected at the channel layer.
- Process cap (
ssh_max_git_processes, default 16) across all connections, coveringgit-upload-pack,git-receive-packandvole-change-merge. A command waits up to 30 s for a slot, then is refused with a retry message. - Payload bounds: exec lines over 4 KiB are refused before parsing; a
vole-runnerrequest line over 1 MiB ends the session with an error. The runner bound is a per-line reader, sojson.Decoderbuffering cannot hide a large request and a long-lived runner is not limited in total. - Host key mode: loading an existing key tightens it to
0600and warns; a mode that cannot be tightened fails startup.vole servestill tightens first viaTightenSecrets, so the warning only appears when something changed the mode after startup.
Per-address backoff for repeated unknown keys is intentionally not here: it is a quota rather than a transport bound, and belongs with the abuse controls, as the issue itself says (“Consider…”). Nothing added requires tuning to be safe by default.
Verification
gofmt,go vet ./...,go test -race ./...clean../vole workflow checkand./vole workflow run verifypass.
Tests (server/ssh/limits_test.go)
TestHandshakeSlowlorisIsCutOff: withmax_connections = 1, an idle connection holds the only slot, a second connection is refused without an answer, the idle one is dropped at its deadline, and its slot comes back for a real client.TestChannelFloodIsRejected: eight channels accepted, the ninth rejected, and closing one releases a slot.TestOversizedCommandIsRefused,TestRunnerRequestLineIsBounded,TestLineLimitReaderIsPerLine.TestGitProcessCapRefusesAfterAWait: a full pool refuses after the bounded wait, and a release frees a slot.TestLooseHostKeyModeIsTightenedandTestHandshakeDeadlineIsClearedForTheSession.
Docs: the three settings are in docs/configuration.md, summarised in
docs/operations.md under “Bounded before authentication”, and shown on the
status page’s read-only configuration view.