This is an automated email from the ASF dual-hosted git repository. hsluoyz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit 417a18e9539f99e54346c41f5712de62eef37e43 Author: Yang Luo <[email protected]> AuthorDate: Sat Aug 15 14:01:19 2026 +0800 fix(agentpatch): resolve the owner home strictly and verify patch targets homeOf fell back to os.UserHomeDir() whenever user.Lookup failed. Lookup routinely fails for DOMAIN\user on Windows, so patching an installation owned by another account silently wrote the hook into the home of whichever account runs Gateway: the operator saw Patched while nothing was collected and an unrelated settings.json had been rewritten. An unresolvable owner is now an error, with a fallback only when the owner really is the current account. PatchAgent and UnpatchAgent also took the agentId/path/owner triple straight from the request body. Combined with the home lookup that is a write primitive into any account's home, so the target is now matched against the discovered installations before it is used. --- agentpatch/home.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++---- controllers/agent.go | 25 +++++++++++++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/agentpatch/home.go b/agentpatch/home.go index 7ae7263..24659ca 100644 --- a/agentpatch/home.go +++ b/agentpatch/home.go @@ -15,15 +15,74 @@ package agentpatch import ( + "fmt" "os" "os/user" + "path/filepath" + "strings" ) +// homeOf resolves the home directory of the installation owner. Patching writes +// into that home, so guessing here would silently modify the wrong account's +// configuration: an unresolvable owner is an error rather than a fallback to +// whichever account happens to run Gateway. func homeOf(target Target) (string, error) { - if target.Owner != "" { - if account, err := user.Lookup(target.Owner); err == nil && account.HomeDir != "" { - return account.HomeDir, nil + owner := strings.TrimSpace(target.Owner) + if owner == "" { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("resolve home directory: %w", err) } + return home, nil } - return os.UserHomeDir() + + if account, err := lookupAccount(owner); err == nil { + if account.HomeDir == "" { + return "", fmt.Errorf("account %q has no home directory", owner) + } + return account.HomeDir, nil + } + + // user.Lookup is unreliable for domain-qualified names on Windows and for + // directory-backed accounts on Linux, so fall back to the process home only + // when the owner really is the account Gateway runs as. + if current, err := user.Current(); err == nil && sameAccount(owner, current.Username) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("resolve home directory of %q: %w", owner, err) + } + return home, nil + } + return "", fmt.Errorf("cannot resolve the home directory of %q; run Gateway as that user to patch this installation", owner) +} + +// lookupAccount resolves an owner name, retrying with the bare account name so +// that DOMAIN\user and user@domain forms also resolve. +func lookupAccount(owner string) (*user.User, error) { + account, err := user.Lookup(owner) + if err == nil { + return account, nil + } + if bare := accountName(owner); bare != owner && bare != "" { + if account, bareErr := user.Lookup(bare); bareErr == nil { + return account, nil + } + } + return nil, err +} + +func sameAccount(left, right string) bool { + return strings.EqualFold(accountName(left), accountName(right)) +} + +// accountName strips any domain qualifier from DOMAIN\user or user@domain. +func accountName(value string) string { + value = strings.TrimSpace(value) + if index := strings.LastIndexAny(value, `\/`); index >= 0 { + value = value[index+1:] + } + if index := strings.Index(value, "@"); index > 0 { + value = value[:index] + } + return filepath.Base(value) } diff --git a/controllers/agent.go b/controllers/agent.go index 1c41af5..cd036ff 100644 --- a/controllers/agent.go +++ b/controllers/agent.go @@ -17,7 +17,9 @@ package controllers import ( "encoding/json" "net" + "path/filepath" "strconv" + "strings" "github.com/apache/casbin-gateway/agent" "github.com/apache/casbin-gateway/agentmonitor" @@ -147,13 +149,34 @@ func (c *ApiController) AddAgentRecord() { c.ResponseOk() } +// readAgentPatchTarget resolves the request body against the installations that +// were actually discovered. Patching writes into the owner's home directory, so +// an unverified body would let a caller name any account on the host. func (c *ApiController) readAgentPatchTarget() (agentpatch.Target, bool) { var target agentpatch.Target if err := json.Unmarshal(c.Ctx.Input.RequestBody, &target); err != nil { c.ResponseError(err.Error()) return target, false } - return target, true + + installations, err := agent.Scan(false) + if err != nil { + c.ResponseError(err.Error()) + return target, false + } + for _, installation := range installations { + if candidate := targetOf(installation); matchesTarget(candidate, target) { + return candidate, true + } + } + c.ResponseError("no discovered agent installation matches this target") + return target, false +} + +func matchesTarget(discovered, requested agentpatch.Target) bool { + return discovered.AgentId == requested.AgentId && + strings.EqualFold(filepath.Clean(discovered.Path), filepath.Clean(requested.Path)) && + strings.EqualFold(discovered.Owner, requested.Owner) } func targetOf(installation agent.Installation) agentpatch.Target { --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
