internal/listen/listen.go
1
// Package listen resolves the address a local koment server binds to. It is
2
// shared by the MCP server and the web UI so the two cannot drift apart on the
3
// one decision that has a security consequence.
4
package listen
6
import (
7
"errors"
8
"fmt"
9
"io"
10
"net"
11
)
13
const loopback = "127.0.0.1"
15
// Address fills in a missing host with the loopback interface, so that a bare
16
// port never publishes a repository to the local network.
17
func Address(address string) (string, error) {
18
host, port, err := net.SplitHostPort(address)
19
if err != nil {
20
bare, bareErr := barePort(address)
21
if bareErr != nil {
22
return "", fmt.Errorf("%q is not a valid address or port: %w", address, err)
23
}
24
return net.JoinHostPort(loopback, bare), nil
25
}
27
if host == "" {
28
return net.JoinHostPort(loopback, port), nil
29
}
30
return address, nil
31
}
33
func barePort(address string) (string, error) {
34
if address == "" {
35
return "", errors.New("no port given")
36
}
37
if _, err := net.LookupPort("tcp", address); err != nil {
38
return "", err
39
}
40
return address, nil
41
}
43
// WarnIfPublic says so, loudly, when a bind address is reachable from beyond
44
// this machine. Neither server authenticates.
45
func WarnIfPublic(address string, stderr io.Writer) {
46
if IsLoopback(address) {
47
return
48
}
50
fmt.Fprintf(stderr,
51
"koment: WARNING serving on %s, which is not loopback. There is no authentication; "+
52
"anyone who can reach this port can read every annotation in the repository.\n", address)
53
}
55
// IsLoopback reports whether an address is confined to the local machine.
56
func IsLoopback(address string) bool {
57
host, _, err := net.SplitHostPort(address)
58
if err != nil {
59
return false
60
}
61
if parsed := net.ParseIP(host); parsed != nil && parsed.IsLoopback() {
62
return true
63
}
64
return host == "localhost"
65
}