mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-18 12:14:24 +00:00
feat(secure-cli): check-binary endpoint, agent select dropdown, fix ambiguous column
Backend: - Add POST /v1/cli-credentials/check-binary to resolve binary via exec.LookPath - Security: safeBinaryNameRe regex prevents filesystem probing - Fix ambiguous column in LookupByBinary LEFT JOIN (secureCLISelectColsAliased) - Add diagnostic logging to lookupCredentialedBinary Frontend: - Replace agent ID text input with Select dropdown (reuse useAgents hook) - Add Check Binary button next to binary name (auto-fills resolved path) - Add binary path hint explaining auto-detection from PATH - Update i18n keys (en/vi/zh) for new UI elements
This commit is contained in:
1 parent
0370cabdc9
commit
10cd911dc7
7 files changed
+150
-30
No files matched your search
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -19,6 +20,10 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
||||
)
|
||||
|
||||
// safeBinaryNameRe allows only simple binary names: alphanumeric, hyphens, underscores, dots.
|
||||
// No path separators or shell metacharacters — prevents filesystem probing via LookPath.
|
||||
var safeBinaryNameRe = regexp.MustCompile(`^[a-zA-Z0-9][a-zA-Z0-9._-]{0,63}$`)
|
||||
|
||||
// SecureCLIHandler handles secure CLI binary credential CRUD endpoints.
|
||||
type SecureCLIHandler struct {
|
||||
store store.SecureCLIStore
|
||||
@@ -35,6 +40,7 @@ func (h *SecureCLIHandler) RegisterRoutes(mux *http.ServeMux) {
|
||||
mux.HandleFunc("GET /v1/cli-credentials", h.auth(h.handleList))
|
||||
mux.HandleFunc("POST /v1/cli-credentials", h.auth(h.handleCreate))
|
||||
mux.HandleFunc("GET /v1/cli-credentials/presets", h.auth(h.handlePresets))
|
||||
mux.HandleFunc("POST /v1/cli-credentials/check-binary", h.auth(h.handleCheckBinary))
|
||||
mux.HandleFunc("GET /v1/cli-credentials/{id}", h.auth(h.handleGet))
|
||||
mux.HandleFunc("PUT /v1/cli-credentials/{id}", h.auth(h.handleUpdate))
|
||||
mux.HandleFunc("DELETE /v1/cli-credentials/{id}", h.auth(h.handleDelete))
|
||||
@@ -343,6 +349,34 @@ func (h *SecureCLIHandler) handlePresets(w http.ResponseWriter, _ *http.Request)
|
||||
writeJSON(w, http.StatusOK, map[string]any{"presets": tools.CLIPresets})
|
||||
}
|
||||
|
||||
// handleCheckBinary resolves a binary name to its absolute path via exec.LookPath.
|
||||
func (h *SecureCLIHandler) handleCheckBinary(w http.ResponseWriter, r *http.Request) {
|
||||
locale := store.LocaleFromContext(r.Context())
|
||||
var req struct {
|
||||
BinaryName string `json:"binary_name"`
|
||||
}
|
||||
if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgInvalidJSON)})
|
||||
return
|
||||
}
|
||||
if req.BinaryName == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": i18n.T(locale, i18n.MsgRequired, "binary_name")})
|
||||
return
|
||||
}
|
||||
// Security: only allow simple binary names (no path separators, no shell metacharacters).
|
||||
// This prevents probing arbitrary filesystem paths via LookPath.
|
||||
if !safeBinaryNameRe.MatchString(req.BinaryName) {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid binary name"})
|
||||
return
|
||||
}
|
||||
absPath, err := exec.LookPath(req.BinaryName)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"found": false, "error": fmt.Sprintf("binary %q not found in PATH", req.BinaryName)})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"found": true, "path": absPath})
|
||||
}
|
||||
|
||||
// dryRunRequest tests commands against deny patterns.
|
||||
type dryRunRequest struct {
|
||||
TestCommands []string `json:"test_commands"`
|
||||
|
||||
Reference in new issue
Block a user