4b70e31cf4
Critical fixes:
- logs --job: validate ^[A-Za-z0-9_-]+$ + shellQuote (was %q backtick RCE)
- pprof: isLoopback treats empty host as bind-all (was :6060 bypass)
- backup restore: filepath.Rel containment check (was tar-slip via a/../..)
- WebAuthn reg auth deferred to P04 (requires session infra)
High fixes:
- txn rollback/show/apply: validate ^T-[0-9a-f]{16}$ + shellQuote
- nft diff --against: validate txn ID before filepath.Join
- drain stopAlloc: validate allocID ^[A-Za-z0-9_-]+$
- cluster_compat: shellQuote peer dir name
- podman image: shellQuote (was %q backtick injection)
- nft TrustedProbes: net.ParseIP/CIDR validation + split v4/v6 sets
- sudoers: validate --proxmox-user/--proxmox-role ^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$
fixed path /etc/sudoers.d/orca; shellQuote pveum/useradd; validateSudoers
checks actual file
- nft country block: validate ^[A-Z]{2}$ (was len==2 only)
New file: internal/cli/validate.go (shared validators + shellQuote)
All 38 Go test packages pass. go vet + gofmt clean.
---ci---
project: orca
phase: 2
milestone: v0.13
status: complete
requirements:
covered: [150]
---/ci---
88 lines
2.7 KiB
Go
88 lines
2.7 KiB
Go
package daemon
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"net/http/pprof"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// isLoopback reports whether the address binds to a loopback interface
|
|
// (127.0.0.1, ::1, localhost). REQ-123: pprof must be loopback-only.
|
|
//
|
|
// An empty host (e.g. ":6060") binds ALL interfaces and is therefore
|
|
// treated as NON-loopback (F2: loopback-bypass fix). Only an explicit
|
|
// loopback IP or the "localhost" name is accepted.
|
|
func isLoopback(addr string) bool {
|
|
host, _, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
host = addr
|
|
}
|
|
host = strings.TrimSpace(host)
|
|
// F2: empty host (":6060") binds all interfaces — reject.
|
|
if host == "" {
|
|
return false
|
|
}
|
|
if host == "localhost" {
|
|
return true
|
|
}
|
|
ip := net.ParseIP(host)
|
|
if ip != nil {
|
|
return ip.IsLoopback()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// StartPprof starts the pprof HTTP server on addr. REQ-123: pprof is
|
|
// unauthenticated and MUST bind to a loopback interface only; this is a
|
|
// hard invariant (F2: the --pprof-allow-public override was a phantom flag
|
|
// that was never implemented and has been removed — non-loopback binds are
|
|
// always refused).
|
|
func StartPprof(addr string, log *slog.Logger) (*http.Server, error) {
|
|
if addr == "" {
|
|
return nil, nil
|
|
}
|
|
// REQ-123: pprof must bind to loopback only. This is a hard
|
|
// invariant; there is no public-bind override.
|
|
if !isLoopback(addr) {
|
|
log.Error("pprof refuses non-loopback bind",
|
|
slog.String("addr", addr),
|
|
slog.String("reason", "REQ-123: pprof is unauthenticated; loopback-only is a hard invariant"))
|
|
return nil, fmt.Errorf("pprof: refusing non-loopback bind %s (REQ-123; unauthenticated; loopback-only is a hard invariant)", addr)
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
|
|
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
|
|
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
|
|
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
|
|
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
|
|
|
|
server := &http.Server{
|
|
Addr: addr,
|
|
Handler: mux,
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
log.Warn("pprof endpoint exposed",
|
|
slog.String("addr", addr),
|
|
slog.String("warning", "unauthenticated, operator-only — do not expose publicly"))
|
|
|
|
go func() {
|
|
err := server.ListenAndServe()
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
log.Error("pprof server stopped", slog.String("addr", addr), slog.Any("err", err))
|
|
}
|
|
}()
|
|
|
|
return server, nil
|
|
}
|