package credhelper import ( "os" "strings" ) // ImageRef is a parsed container image reference type ImageRef struct { Host string Identity string Repo string Tag string Raw string } // detectImageRef walks the process tree looking for an image reference // that matches the given registry host. It starts from the parent process // and walks up to 5 ancestors to handle wrapper scripts (make, bash -c, etc.). // // Returns nil if no matching image reference is found — callers should // fall back to the active account. func detectImageRef(registryHost string) *ImageRef { // Normalize the registry host for matching matchHost := strings.TrimPrefix(registryHost, "https://") matchHost = strings.TrimPrefix(matchHost, "http://") matchHost = strings.TrimSuffix(matchHost, "/") pid := os.Getppid() for range 5 { args, err := getProcessArgs(pid) if err != nil { break } for _, arg := range args { if ref := parseImageRef(arg, matchHost); ref != nil { return ref } } ppid, err := getParentPID(pid) if err != nil || ppid == pid || ppid <= 1 { break } pid = ppid } return nil } // parseImageRef tries to parse a string as a container image reference. // Expected format: host/identity/repo:tag or host/identity/repo // // Handles: // - docker:// and oci:// transport prefixes (skopeo) // - Flags (- prefix), paths (/ or . prefix), shell artifacts (|, &, ;) // - Optional tag (defaults to "latest") // - Host must look like a domain (contains ., or is localhost, or has :port) // - If matchHost is non-empty, only returns refs matching that host func parseImageRef(s string, matchHost string) *ImageRef { // Skip flags, absolute paths, relative paths if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "/") || strings.HasPrefix(s, ".") { return nil } // Strip docker:// or oci:// transport prefixes (skopeo) s = strings.TrimPrefix(s, "docker://") s = strings.TrimPrefix(s, "oci://") // Skip other transport schemes if strings.Contains(s, "://") { return nil } // Must contain at least one slash if !strings.Contains(s, "/") { return nil } // Skip things that look like shell commands if strings.ContainsAny(s, " |&;") { return nil } // Split off tag tag := "latest" refPart := s if atIdx := strings.LastIndex(s, ":"); atIdx != -1 { lastSlash := strings.LastIndex(s, "/") if atIdx > lastSlash { tag = s[atIdx+1:] refPart = s[:atIdx] } } parts := strings.Split(refPart, "/") // Registry pattern requires host/identity/repo (3+ parts) if len(parts) < 3 { return nil } host := parts[0] identity := parts[1] repo := strings.Join(parts[2:], "/") // Host must look like a domain if !strings.Contains(host, ".") && host != "localhost" && !strings.Contains(host, ":") { return nil } // If a specific host was requested, enforce it if matchHost != "" && host != matchHost { return nil } return &ImageRef{ Host: host, Identity: identity, Repo: repo, Tag: tag, Raw: s, } }