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


The following commit(s) were added to refs/heads/master by this push:
     new 3eb88f3  fix(agent): detect agents under a custom npm prefix
3eb88f3 is described below

commit 3eb88f3f8efb52e899e4890ef5e4ff2a051219a0
Author: Yang Luo <[email protected]>
AuthorDate: Thu Aug 20 10:08:44 2026 +0800

    fix(agent): detect agents under a custom npm prefix
---
 agent/scan_common.go  | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++
 agent/scan_unix.go    |  2 +-
 agent/scan_windows.go |  6 ++--
 3 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/agent/scan_common.go b/agent/scan_common.go
index aa72c53..b25c12d 100644
--- a/agent/scan_common.go
+++ b/agent/scan_common.go
@@ -21,6 +21,7 @@ import (
        "os/exec"
        "os/user"
        "path/filepath"
+       "regexp"
        "runtime"
        "sort"
        "strings"
@@ -31,6 +32,12 @@ import (
 // maxPackageJSONSize limits reads from glob-matched manifests.
 const maxPackageJSONSize = 1024 * 1024
 
+// maxNpmrcSize limits reads from npmrc files.
+const maxNpmrcSize = 64 * 1024
+
+// npmrcEnvRef matches the ${VAR} references npm expands in npmrc values.
+var npmrcEnvRef = regexp.MustCompile(`\$\{([^{}]+)\}`)
+
 // scanNpmPatterns validates npm packages found by known glob patterns.
 func scanNpmPatterns(ctx context.Context, fingerprint *Fingerprint, patterns 
[]string, owner string, ownerFor func(string) string) []Installation {
        if fingerprint.NpmPackage == "" {
@@ -79,6 +86,79 @@ func (f *Fingerprint) npmPackagePath() string {
        return filepath.FromSlash(f.NpmPackage)
 }
 
+// isCurrentHome reports whether path is the home of the user this process runs
+// as, the only profile the environment describes.
+func isCurrentHome(path string) bool {
+       current, err := os.UserHomeDir()
+       if err != nil {
+               return false
+       }
+       current, path = filepath.Clean(current), filepath.Clean(path)
+       if runtime.GOOS == "windows" {
+               return strings.EqualFold(current, path)
+       }
+       return current == path
+}
+
+// npmrcPrefix returns the "prefix" setting of an npmrc file, later keys 
winning
+// as in npm's own ini parsing.
+func npmrcPrefix(path string) string {
+       info, err := os.Stat(path)
+       if err != nil || !info.Mode().IsRegular() || info.Size() > maxNpmrcSize 
{
+               return ""
+       }
+       data, err := os.ReadFile(path)
+       if err != nil {
+               return ""
+       }
+       prefix := ""
+       for _, line := range strings.Split(string(data), "\n") {
+               key, value, ok := strings.Cut(line, "=")
+               if !ok || strings.TrimSpace(key) != "prefix" {
+                       continue
+               }
+               prefix = strings.Trim(strings.TrimSpace(value), `"'`)
+       }
+       return npmrcEnvRef.ReplaceAllStringFunc(prefix, func(ref string) string 
{
+               return os.Getenv(ref[2 : len(ref)-1])
+       })
+}
+
+// npmPrefixPatterns cover a global root relocated with "npm config set 
prefix",
+// which falls outside every fixed layout. Windows keeps the packages directly
+// under the prefix, Unix under lib/.
+func npmPrefixPatterns(fingerprint *Fingerprint, home string) []string {
+       var prefixes []string
+       if isCurrentHome(home) {
+               // npm accepts either case for its environment config.
+               prefixes = append(prefixes, os.Getenv("npm_config_prefix"), 
os.Getenv("NPM_CONFIG_PREFIX"))
+       }
+       prefixes = append(prefixes, npmrcPrefix(filepath.Join(home, ".npmrc")))
+
+       pkg := fingerprint.npmPackagePath()
+       seen := map[string]bool{}
+       var patterns []string
+       for _, prefix := range prefixes {
+               prefix = filepath.Clean(strings.TrimSpace(prefix))
+               if !filepath.IsAbs(prefix) {
+                       continue
+               }
+               key := prefix
+               if runtime.GOOS == "windows" {
+                       key = strings.ToLower(key)
+               }
+               if seen[key] {
+                       continue
+               }
+               seen[key] = true
+               patterns = append(patterns,
+                       filepath.Join(prefix, "node_modules", pkg, 
"package.json"),
+                       filepath.Join(prefix, "lib", "node_modules", pkg, 
"package.json"),
+               )
+       }
+       return patterns
+}
+
 func stampAgentId(installations []Installation, mark int, agentId string) {
        for i := mark; i < len(installations); i++ {
                installations[i].AgentId = agentId
diff --git a/agent/scan_unix.go b/agent/scan_unix.go
index 8883c06..ca8e58c 100644
--- a/agent/scan_unix.go
+++ b/agent/scan_unix.go
@@ -73,7 +73,7 @@ func userNpmPatterns(fingerprint *Fingerprint, home string) 
[]string {
        for _, dir := range fingerprint.ExtraUnixNpmDirs {
                patterns = append(patterns, filepath.Join(home, 
filepath.FromSlash(dir), "node_modules", pkg, "package.json"))
        }
-       return patterns
+       return append(patterns, npmPrefixPatterns(fingerprint, home)...)
 }
 
 func fileOwner(path string) string {
diff --git a/agent/scan_windows.go b/agent/scan_windows.go
index f9feb43..ae30d36 100644
--- a/agent/scan_windows.go
+++ b/agent/scan_windows.go
@@ -147,7 +147,7 @@ func windowsHomes(ctx context.Context) []homeDir {
 
 // appDataDir respects relocated AppData paths for the current user.
 func appDataDir(home homeDir, kind, variable string) string {
-       if current, err := os.UserHomeDir(); err == nil && 
strings.EqualFold(filepath.Clean(current), filepath.Clean(home.path)) {
+       if isCurrentHome(home.path) {
                if configured := os.Getenv(variable); configured != "" {
                        return configured
                }
@@ -160,8 +160,7 @@ func roamingAppData(home homeDir) string { return 
appDataDir(home, "Roaming", "A
 
 func scanHermesWindows(home homeDir) []Installation {
        hermesHome := filepath.Join(localAppData(home), "hermes")
-       if current, err := os.UserHomeDir(); err == nil &&
-               strings.EqualFold(filepath.Clean(current), 
filepath.Clean(home.path)) {
+       if isCurrentHome(home.path) {
                if configured := os.Getenv("HERMES_HOME"); configured != "" {
                        hermesHome = configured
                }
@@ -329,5 +328,6 @@ func scanWindowsNpm(ctx context.Context, fingerprint 
*Fingerprint, home homeDir)
        for _, dir := range fingerprint.ExtraWindowsNpmDirs {
                patterns = append(patterns, filepath.Join(local, 
filepath.FromSlash(dir), "node_modules", pkg, "package.json"))
        }
+       patterns = append(patterns, npmPrefixPatterns(fingerprint, 
home.path)...)
        return scanNpmPatterns(ctx, fingerprint, patterns, home.owner, nil)
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to