mavweb: add in-process auth gate for POST /tools

Add a local PasskeySession that handleTools checks before processing
any POST action (enable/disable). If the session hasn't been asserted
within the 5-minute TTL, return 403 Forbidden.

Changes:
- webauthn/session.go: add IsStepUp() convenience method (nil-safe)
- webauthn.go: PasskeyHandle holds a *PasskeySession; AssertFinish
  calls session.Assert() after IPC step-up
- main.go: create stepUpSession, pass to handleTools and
  newPasskeyHandle; handleTools returns 403 if !session.IsStepUp()
- handlers_test.go: update TestEnableTool_NoInProcessAuthGate to
  expect 403; add TestEnableTool_WithAuthGate_RequiresStepUp for
  the happy path with asserted session; update all 10 call sites
This commit is contained in:
kami
2026-07-05 11:55:44 +04:00
parent c225ba37b2
commit 5afff001c3
5 changed files with 79 additions and 31 deletions
+9 -3
View File
@@ -142,12 +142,14 @@ func main() {
// Passkey registration + assertion are the step-up mechanism for
// AuthStepUp actions (tool enable). Without -webauthn-origin, these
// endpoints return 503 and step-up is unavailable (FloorSession).
stepUpSession := webauthn.NewPasskeySession(5 * time.Minute)
if *pkOrigin != "" && *pkRPID != "" && core != nil {
pk, err := newPasskeyHandle(webauthn.Config{
Origin: *pkOrigin,
RPID: *pkRPID,
RPName: "maven",
}, core, *pkFile)
}, core, *pkFile, stepUpSession)
if err != nil {
log.Fatalf("passkey store: %v", err)
}
@@ -163,7 +165,7 @@ func main() {
// Enabling is the boundary-moving act (maven.md), so it lives ONLY here,
// behind wg+nginx+auth — never the voice/chat path.
mux.HandleFunc("/tools", func(w http.ResponseWriter, r *http.Request) {
handleTools(w, r, core)
handleTools(w, r, core, stepUpSession)
})
srv := &http.Server{Addr: *addr, Handler: mux}
@@ -425,7 +427,7 @@ func handleRevert(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
// whitespace-split — argv with embedded spaces isn't supported (ponytail: no
// shell-word parsing; the box owner controls this input, quote a wrapper script
// if an arg needs spaces).
func handleTools(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
func handleTools(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI, session *webauthn.PasskeySession) {
if core == nil {
http.Error(w, "tools disabled (no -core)", http.StatusServiceUnavailable)
return
@@ -433,6 +435,10 @@ func handleTools(w http.ResponseWriter, r *http.Request, core ipc.CoreAPI) {
ctx := r.Context()
var msg string
if r.Method == http.MethodPost {
if !session.IsStepUp() {
http.Error(w, "step-up required: assert a passkey first", http.StatusForbidden)
return
}
action := r.FormValue("action")
name := strings.TrimSpace(r.FormValue("name"))
switch action {