fix(cli): validate runtimes before uninstall

This commit is contained in:
ZacharyZhang-NY committed 2026-07-19 16:40:05 -04:00
1 parent 7bf84a35e0
commit 3488ab778c
7 files changed
+250 -87

No files matched your search

+2 -85
View File
@@ -1,7 +1,6 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
@@ -146,37 +145,13 @@ func cmdUninstall(args []string) {
os.Exit(1)
}
runtimeRoot, err := plugins.AgentRuntimeRoot()
resolvedAgentID, agentDirectory, agentDirectoryExists, err := resolveAgentRuntimeForUninstall(*agent, *agentID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if _, err := plugins.RequirePhysicalDirectory(runtimeRoot); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
// Resolve agentID: if not given, scan ~/.elydora/*/config.json for matching agent_name
resolvedAgentID := *agentID
if resolvedAgentID == "" {
resolvedAgentID, err = findAgentIDByName(*agent)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
if resolvedAgentID == "" {
fmt.Fprintf(os.Stderr, "Error: could not find agent ID for %q in ~/.elydora/*/config.json; pass --agent-id explicitly\n", *agent)
os.Exit(1)
}
}
agentDirectory, err := plugins.ResolveAgentRuntimeDirectory(resolvedAgentID)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
agentDirectoryExists, err := plugins.RequirePhysicalDirectory(agentDirectory)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
fmt.Fprintf(os.Stderr, "Error: could not find agent ID for %q in ~/.elydora/*/config.json; pass --agent-id explicitly\n", *agent)
os.Exit(1)
}
@@ -195,64 +170,6 @@ func cmdUninstall(args []string) {
}
}
// findAgentIDByName scans ~/.elydora/*/config.json for a config whose agent_name
// matches the given name and returns the directory name (agent ID).
func findAgentIDByName(agentName string) (string, error) {
elydoraDir, err := plugins.AgentRuntimeRoot()
if err != nil {
return "", err
}
exists, err := plugins.RequirePhysicalDirectory(elydoraDir)
if err != nil {
return "", err
}
if !exists {
return "", nil
}
entries, err := os.ReadDir(elydoraDir)
if err != nil {
return "", fmt.Errorf("read Elydora runtime root %s: %w", elydoraDir, err)
}
foundAgentID := ""
for _, entry := range entries {
if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() {
continue
}
configPath := filepath.Join(elydoraDir, entry.Name(), "config.json")
data, err := os.ReadFile(configPath)
if os.IsNotExist(err) {
continue
}
if err != nil {
return "", fmt.Errorf("read agent config %s: %w", configPath, err)
}
var config map[string]interface{}
if err := json.Unmarshal(data, &config); err != nil {
return "", fmt.Errorf("parse agent config %s: %w", configPath, err)
}
if name, _ := config["agent_name"].(string); name == agentName {
storedAgentID, ok := config["agent_id"].(string)
if !ok || storedAgentID == "" {
return "", fmt.Errorf("agent config %s has an invalid agent_id", configPath)
}
if storedAgentID != entry.Name() {
return "", fmt.Errorf("agent config %s crosses its runtime directory", configPath)
}
if _, err := plugins.ResolveAgentRuntimeDirectory(storedAgentID); err != nil {
return "", fmt.Errorf("validate agent config %s: %w", configPath, err)
}
if foundAgentID != "" {
return "", fmt.Errorf(
"multiple %q agent runtimes found; pass --agent-id explicitly",
agentName,
)
}
foundAgentID = storedAgentID
}
}
return foundAgentID, nil
}
// ---------------------------------------------------------------------------
// status
// ---------------------------------------------------------------------------
+56
View File
@@ -78,3 +78,59 @@ func TestFindAgentIDByNameRejectsAmbiguousRuntimes(t *testing.T) {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
func TestExplicitUninstallRejectsAgentOwnershipMismatch(t *testing.T) {
home := setMainTestHome(t)
runtimeDirectory := filepath.Join(home, ".elydora", "agent-1")
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
config := []byte(`{"agent_name":"codex","agent_id":"agent-1"}`)
if err := os.WriteFile(filepath.Join(runtimeDirectory, "config.json"), config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
_, _, _, err := resolveAgentRuntimeForUninstall("opencode", "agent-1")
if err == nil || !strings.Contains(err.Error(), "belongs to") {
t.Fatalf("resolveAgentRuntimeForUninstall() error = %v", err)
}
}
func TestFindAgentIDByNameRejectsLinkedRuntime(t *testing.T) {
home := setMainTestHome(t)
root := filepath.Join(home, ".elydora")
if err := os.MkdirAll(root, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
target := t.TempDir()
link := filepath.Join(root, "agent-1")
if err := os.Symlink(target, link); err != nil {
t.Skipf("directory symbolic links unavailable: %v", err)
}
_, err := findAgentIDByName("opencode")
if err == nil || !strings.Contains(err.Error(), "physical directory") {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
func TestFindAgentIDByNameRejectsLinkedConfig(t *testing.T) {
home := setMainTestHome(t)
runtimeDirectory := filepath.Join(home, ".elydora", "agent-1")
if err := os.MkdirAll(runtimeDirectory, 0700); err != nil {
t.Fatalf("MkdirAll() error = %v", err)
}
target := filepath.Join(t.TempDir(), "config.json")
config := []byte(`{"agent_name":"opencode","agent_id":"agent-1"}`)
if err := os.WriteFile(target, config, 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
if err := os.Symlink(target, filepath.Join(runtimeDirectory, "config.json")); err != nil {
t.Skipf("file symbolic links unavailable: %v", err)
}
_, err := findAgentIDByName("opencode")
if err == nil || !strings.Contains(err.Error(), "physical file") {
t.Fatalf("findAgentIDByName() error = %v", err)
}
}
+15
View File
@@ -126,3 +126,18 @@ func RequirePhysicalDirectory(path string) (bool, error) {
}
return true, nil
}
// RequirePhysicalFile reports whether a path is an existing physical regular file.
func RequirePhysicalFile(path string) (bool, error) {
info, err := os.Lstat(path)
if os.IsNotExist(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("inspect agent runtime file %s: %w", path, err)
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
return false, fmt.Errorf("agent runtime config is not a physical file: %s", path)
}
return true, nil
}
+16
View File
@@ -93,3 +93,19 @@ func TestResolveAgentRuntimeDirectoryRejectsSymlinkChild(t *testing.T) {
t.Fatalf("ResolveAgentRuntimeDirectory() error = %v", err)
}
}
func TestRequirePhysicalFileRejectsSymlink(t *testing.T) {
setRuntimeTestHome(t)
target := filepath.Join(t.TempDir(), "target.json")
if err := os.WriteFile(target, []byte("{}"), 0600); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
link := filepath.Join(t.TempDir(), "config.json")
if err := os.Symlink(target, link); err != nil {
t.Skipf("file symbolic links unavailable: %v", err)
}
_, err := RequirePhysicalFile(link)
if err == nil || !strings.Contains(err.Error(), "physical file") {
t.Fatalf("RequirePhysicalFile() error = %v", err)
}
}
+159
View File
@@ -0,0 +1,159 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora/plugins"
)
type installedAgentRuntime struct {
agentID string
agentName string
}
type runtimeIdentity struct {
AgentID string `json:"agent_id"`
AgentName string `json:"agent_name"`
}
func readInstalledAgentRuntime(directoryName string) (*installedAgentRuntime, error) {
agentDirectory, err := plugins.ResolveAgentRuntimeDirectory(directoryName)
if err != nil {
return nil, err
}
exists, err := plugins.RequirePhysicalDirectory(agentDirectory)
if err != nil || !exists {
return nil, err
}
configPath := filepath.Join(agentDirectory, "config.json")
configExists, err := plugins.RequirePhysicalFile(configPath)
if err != nil || !configExists {
return nil, err
}
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("read agent config %s: %w", configPath, err)
}
var identity runtimeIdentity
if err := json.Unmarshal(data, &identity); err != nil {
return nil, fmt.Errorf("parse agent config %s: %w", configPath, err)
}
if identity.AgentID == "" || identity.AgentName == "" {
return nil, fmt.Errorf("agent config %s has an invalid runtime identity", configPath)
}
if identity.AgentID != directoryName {
return nil, fmt.Errorf("agent config %s crosses its runtime directory", configPath)
}
if _, err := plugins.ResolveAgentRuntimeDirectory(identity.AgentID); err != nil {
return nil, fmt.Errorf("validate agent config %s: %w", configPath, err)
}
return &installedAgentRuntime{
agentID: identity.AgentID, agentName: identity.AgentName,
}, nil
}
func discoverInstalledAgentRuntimes() ([]installedAgentRuntime, error) {
runtimeRoot, err := plugins.AgentRuntimeRoot()
if err != nil {
return nil, err
}
exists, err := plugins.RequirePhysicalDirectory(runtimeRoot)
if err != nil || !exists {
return nil, err
}
entries, err := os.ReadDir(runtimeRoot)
if err != nil {
return nil, fmt.Errorf("read Elydora runtime root %s: %w", runtimeRoot, err)
}
runtimes := make([]installedAgentRuntime, 0, len(entries))
for _, entry := range entries {
if entry.Type()&os.ModeSymlink != 0 {
return nil, fmt.Errorf(
"agent runtime path is not a physical directory: %s",
filepath.Join(runtimeRoot, entry.Name()),
)
}
if !entry.IsDir() {
continue
}
runtime, err := readInstalledAgentRuntime(entry.Name())
if err != nil {
return nil, err
}
if runtime != nil {
runtimes = append(runtimes, *runtime)
}
}
return runtimes, nil
}
// findAgentIDByName returns one validated runtime identity for an agent name.
func findAgentIDByName(agentName string) (string, error) {
runtimes, err := discoverInstalledAgentRuntimes()
if err != nil {
return "", err
}
foundAgentID := ""
for _, runtime := range runtimes {
if runtime.agentName != agentName {
continue
}
if foundAgentID != "" {
return "", fmt.Errorf(
"multiple %q agent runtimes found; pass --agent-id explicitly",
agentName,
)
}
foundAgentID = runtime.agentID
}
return foundAgentID, nil
}
func resolveAgentRuntimeForUninstall(
agentName, explicitAgentID string,
) (agentID, agentDirectory string, directoryExists bool, err error) {
runtimeRoot, err := plugins.AgentRuntimeRoot()
if err != nil {
return "", "", false, err
}
runtimeRootExists, err := plugins.RequirePhysicalDirectory(runtimeRoot)
if err != nil {
return "", "", false, err
}
agentID = explicitAgentID
if agentID == "" {
agentID, err = findAgentIDByName(agentName)
if err != nil || agentID == "" {
return agentID, "", false, err
}
}
agentDirectory, err = plugins.ResolveAgentRuntimeDirectory(agentID)
if err != nil {
return "", "", false, err
}
if runtimeRootExists {
directoryExists, err = plugins.RequirePhysicalDirectory(agentDirectory)
if err != nil {
return "", "", false, err
}
}
if directoryExists {
runtime, err := readInstalledAgentRuntime(agentID)
if err != nil {
return "", "", false, err
}
if runtime != nil && runtime.agentName != agentName {
return "", "", false, fmt.Errorf(
"agent runtime %q belongs to %q, not %q",
agentID, runtime.agentName, agentName,
)
}
}
return agentID, agentDirectory, directoryExists, nil
}